Skip to content

Commit b4dfe40

Browse files
committed
Harden NetIPC memory-safety edge cases
1 parent cf8eaf9 commit b4dfe40

28 files changed

Lines changed: 1423 additions & 175 deletions

‎.agents/sow/done/SOW-0026-20260628-netipc-memory-safety-scout-findings.md‎

Lines changed: 456 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
# SOW-0027 - Netdata Vendor Memory-Safety Update
2+
3+
## Status
4+
5+
Status: open
6+
7+
Sub-state: not started; tracks propagation of SOW-0026 source fixes to the Netdata vendored copy.
8+
9+
## Requirements
10+
11+
### Purpose
12+
13+
Keep NetIPC memory-safety fixes source-owned in `plugin-ipc` while ensuring the Netdata vendored copy receives those fixes through the normal vendor/update path.
14+
15+
### User Request
16+
17+
The user asked to fix NetIPC library issues in `plugin-ipc`, not directly in the Netdata PR, because this repository is the source of truth.
18+
19+
### Assistant Understanding
20+
21+
Facts:
22+
23+
- SOW-0026 implements source fixes for NetIPC memory-safety scout findings in `plugin-ipc`.
24+
- Netdata consumes NetIPC through a vendored copy.
25+
- Directly patching Netdata's vendored NetIPC copy would create source-of-truth drift.
26+
27+
Inferences:
28+
29+
- After SOW-0026 lands, the next safe step is a focused vendor/update pass against the relevant Netdata checkout.
30+
31+
Unknowns:
32+
33+
- The exact Netdata checkout, branch, and PR target for propagation must be confirmed before implementation starts.
34+
35+
### Acceptance Criteria
36+
37+
- The selected Netdata checkout is confirmed before implementation.
38+
- The vendored NetIPC copy is updated from `plugin-ipc` rather than manually patched.
39+
- The project-local vendor diff/checker is run and its result is recorded.
40+
- Netdata build or targeted tests covering the touched NetIPC integration paths are run or a blocker is recorded with evidence.
41+
- No unrelated Netdata changes are included.
42+
43+
## Analysis
44+
45+
Sources checked:
46+
47+
- SOW-0026 source-ownership decision.
48+
- `docs/netipc-integrator-skill.md` source-of-truth guidance.
49+
- Prior vendor synchronization SOWs in `.agents/sow/done/`.
50+
51+
Current state:
52+
53+
- Source fixes are expected to land in `plugin-ipc` through SOW-0026 first.
54+
- No Netdata checkout has been selected for this SOW yet.
55+
56+
Risks:
57+
58+
- Copying files manually can introduce import-path or layout mistakes.
59+
- Updating the wrong Netdata checkout can create unrelated branch drift.
60+
- Skipping the vendor diff can hide missing language-specific updates.
61+
62+
## Pre-Implementation Gate
63+
64+
Status: needs-user-decision
65+
66+
Problem / root-cause model:
67+
68+
- NetIPC fixes must be propagated to the downstream Netdata vendored copy, but that work belongs in a separate focused pass after the source repository is committed and pushed.
69+
70+
Evidence reviewed:
71+
72+
- SOW-0026 records that NetIPC source ownership belongs to `plugin-ipc`.
73+
- Historical vendor-sync SOWs use the project-local `diff-netdata-vendor.sh` checker.
74+
75+
Affected contracts and surfaces:
76+
77+
- Netdata vendored C, Rust, and Go NetIPC sources.
78+
- Netdata build/test paths that consume NetIPC.
79+
- Vendor synchronization evidence in this SOW.
80+
81+
Existing patterns to reuse:
82+
83+
- `diff-netdata-vendor.sh`
84+
- Prior SOW-0003 and SOW-0008 vendor synchronization flow.
85+
86+
Risk and blast radius:
87+
88+
- Medium: changes land in a consumer repository and may affect Netdata build/test behavior.
89+
- Keep scope limited to NetIPC vendor propagation and required validation.
90+
91+
Sensitive data handling plan:
92+
93+
- No secrets, customer data, credentials, production logs, or private endpoints are required.
94+
- Evidence will use source paths, commands, commit hashes, and sanitized summaries only.
95+
96+
Implementation plan:
97+
98+
1. Confirm the target Netdata checkout and branch.
99+
2. Propagate the committed `plugin-ipc` source changes through the normal vendor/update path.
100+
3. Run the vendor diff/checker and targeted Netdata validation.
101+
4. Commit only the vendor update and required tracking artifacts.
102+
103+
Validation plan:
104+
105+
- Run the vendor diff/checker against the selected Netdata checkout.
106+
- Run targeted Netdata build/tests for touched C/Rust/Go NetIPC integration paths.
107+
- Run same-failure searches for the SOW-0026 finding classes in the Netdata vendored copy.
108+
109+
Artifact impact plan:
110+
111+
- AGENTS.md: no expected update.
112+
- Runtime project skills: no expected update.
113+
- Specs: no expected update unless propagation exposes source/doc drift.
114+
- End-user/operator docs: no expected update.
115+
- End-user/operator skills: no expected update.
116+
- SOW lifecycle: this SOW remains open until the user selects the Netdata checkout.
117+
118+
Open-source reference evidence:
119+
120+
- None checked yet; this SOW is a local vendor propagation task.
121+
122+
Open decisions:
123+
124+
1. Select the target Netdata checkout, branch, and PR context before implementation.
125+
126+
## Implications And Decisions
127+
128+
- No implementation decisions have been made yet.
129+
130+
## Plan
131+
132+
1. Confirm target checkout and branch.
133+
2. Run vendor propagation.
134+
3. Validate vendor parity and targeted Netdata behavior.
135+
4. Commit and push when validated.
136+
137+
## Execution Log
138+
139+
### 2026-06-29
140+
141+
- Created as the tracked follow-up for SOW-0026 vendor propagation.
142+
- No implementation started.
143+
144+
## Validation
145+
146+
Acceptance criteria evidence:
147+
148+
- Not started.
149+
150+
Tests or equivalent validation:
151+
152+
- Not started.
153+
154+
Real-use evidence:
155+
156+
- Not started.
157+
158+
Reviewer findings:
159+
160+
- Not started.
161+
162+
Same-failure scan:
163+
164+
- Not started.
165+
166+
Sensitive data gate:
167+
168+
- The SOW contains only source paths and workflow descriptions. No secrets or customer data are included.
169+
170+
Artifact maintenance gate:
171+
172+
- AGENTS.md: no update needed for this tracking SOW.
173+
- Runtime project skills: none exist.
174+
- Specs: no update needed until implementation changes behavior.
175+
- End-user/operator docs: no update needed until implementation changes behavior.
176+
- End-user/operator skills: no update needed until implementation changes behavior.
177+
- SOW lifecycle: created as open in `.agents/sow/pending/`.
178+
179+
Specs update:
180+
181+
- Not started.
182+
183+
Project skills update:
184+
185+
- Not started.
186+
187+
End-user/operator docs update:
188+
189+
- Not started.
190+
191+
End-user/operator skills update:
192+
193+
- Not started.
194+
195+
Lessons:
196+
197+
- None yet.
198+
199+
Follow-up mapping:
200+
201+
- This SOW tracks the SOW-0026 Netdata vendor propagation item.
202+
203+
## Outcome
204+
205+
Pending.
206+
207+
## Lessons Extracted
208+
209+
Pending.
210+
211+
## Followup
212+
213+
None yet.
214+
215+
## Regression Log
216+
217+
None yet.

‎docs/level1-posix-uds.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ fallback.
3535
If a stale socket file exists from a dead process, the server
3636
unlinks it and recreates. If the socket is actively held by a live
3737
process, the server fails with address-in-use.
38+
If two servers start concurrently for the same service name, one may
39+
lose the probe/bind race and fail with address-in-use. The contract is
40+
one owner per service endpoint.
3841

3942
2. **Client**: connects to the socket path. The OS delivers a connected
4043
SEQPACKET file descriptor.

‎docs/level1-transport.md‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,18 @@ mandatory capability, not an optional enhancement.
527527
Level 1. Level 2 managed server mode provides an opinionated threading
528528
model on top.
529529

530+
## Raw session thread ownership
531+
532+
Level 1 raw session objects are mutable transport state. A single session
533+
object is single-owner unless the caller serializes access externally.
534+
535+
- Multiple clients may use independent sessions concurrently.
536+
- A listener may accept multiple independent sessions concurrently.
537+
- Concurrent send/receive/close calls on the same raw session object require
538+
caller-provided synchronization.
539+
- Code that needs a library-owned concurrency model should use the Level 2
540+
managed server or typed client APIs instead of sharing raw Level 1 sessions.
541+
530542
## Stale endpoint recovery
531543

532544
Service endpoints (socket files, named pipe names, SHM regions) may become
@@ -547,6 +559,10 @@ stale if a server process crashes without cleanup. Level 1 handles this:
547559
- `run_dir` is expected to be the embedding service's private runtime
548560
directory (e.g. netdata's run dir); its permissions do not gate stale
549561
recovery.
562+
- Concurrent startup of two servers for the same POSIX socket can still race
563+
between stale-file probe and bind. Level 1 treats this as an address-in-use
564+
startup conflict, not as a steady-state safety issue. Operators should run one
565+
owner for a service endpoint.
550566

551567
## Testing requirements
552568

‎docs/level1-windows-shm.md‎

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -143,12 +143,14 @@ Uses two named kernel events (`req_event`, `resp_event`) created via
143143
once more (avoid race), then `WaitForSingleObject(req_event,
144144
timeout)`.
145145
3. Clear `req_server_waiting` after waking.
146-
4. Read `req_len`. If `req_len` is 0, report a protocol error — `send`
147-
rejects zero-length messages, so this indicates SHM corruption.
148-
5. Validate `req_len` against `request_capacity`. If `req_len` exceeds
149-
the capacity, discard the message and report an error. This prevents
146+
4. Once `req_seq` has advanced, read `req_len`, copy the message bytes if
147+
they fit the caller buffer, then re-read both `req_seq` and `req_len`.
148+
If either value changed during the copy, report a protocol error.
149+
5. If `req_len` is 0, report a protocol error — `send` rejects zero-length
150+
messages, so this indicates SHM corruption.
151+
6. Validate `req_len` against `request_capacity`. If `req_len` exceeds the
152+
capacity, discard the message and report an error. This prevents
150153
out-of-bounds reads from a malicious or buggy peer.
151-
6. Read the message bytes from the request area.
152154

153155
#### Server sends a response
154156

@@ -163,11 +165,12 @@ Uses two named kernel events (`req_event`, `resp_event`) created via
163165
2. If not advanced: set `resp_client_waiting = 1`, check `resp_seq`
164166
once more, then `WaitForSingleObject(resp_event, timeout)`.
165167
3. Clear `resp_client_waiting` after waking.
166-
4. Read `resp_len`. If `resp_len` is 0, report a protocol error
167-
(SHM corruption).
168-
5. Validate `resp_len` against `response_capacity`. If `resp_len`
169-
exceeds the capacity, discard the message and report an error.
170-
6. Read the message bytes from the response area.
168+
4. Once `resp_seq` has advanced, read `resp_len`, copy the message bytes if
169+
they fit the caller buffer, then re-read both `resp_seq` and `resp_len`.
170+
If either value changed during the copy, report a protocol error.
171+
5. If `resp_len` is 0, report a protocol error (SHM corruption).
172+
6. Validate `resp_len` against `response_capacity`. If `resp_len` exceeds
173+
the capacity, discard the message and report an error.
171174

172175
### SHM_BUSYWAIT (pure spin, no kernel events)
173176

@@ -177,12 +180,12 @@ sequence numbers.
177180
- The publication protocol is the same as SHM_HYBRID except:
178181
- No `SetEvent` calls.
179182
- No `WaitForSingleObject` calls.
180-
- The waiter spins indefinitely (with deadline polling every
181-
`BUSYWAIT_DEADLINE_POLL_MASK + 1` iterations to check for timeout
182-
expiry).
183+
- The waiter spins indefinitely, polling every
184+
`BUSYWAIT_DEADLINE_POLL_MASK + 1` iterations to check timeout/peer-close
185+
state and yield the current thread.
183186

184-
This profile burns full CPU on both client and server but achieves the
185-
lowest possible latency.
187+
This profile is CPU-intensive on both client and server, but the implementation
188+
must periodically yield so an infinite wait does not monopolize a core.
186189

187190
## Close protocol
188191

‎docs/netipc-integrator-skill.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ Non-negotiable rule:
157157
- retries / reconnects
158158
- raw payload encode/decode
159159
- batch and chunk rules
160+
- single-owner or externally locked access to each raw session object
160161

161162
Use L1 for:
162163

‎src/crates/netipc/src/protocol/string_reverse.rs‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ pub fn string_reverse_decode(buf: &[u8]) -> Result<StringReverseView<'_>, NipcEr
4444
return Err(NipcError::Truncated);
4545
}
4646
let str_offset = u32::from_ne_bytes(buf[0..4].try_into().unwrap()) as usize;
47-
if str_offset < STRING_REVERSE_HDR_SIZE {
47+
if str_offset != STRING_REVERSE_HDR_SIZE {
4848
return Err(NipcError::BadLayout);
4949
}
5050
let str_length = u32::from_ne_bytes(buf[4..8].try_into().unwrap()) as usize;
@@ -145,6 +145,19 @@ mod tests {
145145
));
146146
}
147147

148+
#[test]
149+
fn decode_bad_offset() {
150+
let mut buf = [0u8; 16];
151+
buf[0..4].copy_from_slice(&9u32.to_ne_bytes());
152+
buf[4..8].copy_from_slice(&1u32.to_ne_bytes());
153+
buf[9] = b'x';
154+
buf[10] = 0;
155+
assert!(matches!(
156+
string_reverse_decode(&buf),
157+
Err(NipcError::BadLayout)
158+
));
159+
}
160+
148161
#[test]
149162
fn dispatch_resp_too_small() {
150163
let s = b"hello";

0 commit comments

Comments
 (0)