Skip to content

Commit c9eb329

Browse files
committed
Reserve lookup overflow suffix capacity
1 parent f46913e commit c9eb329

21 files changed

Lines changed: 1498 additions & 18 deletions

.agents/sow/current/SOW-0021-20260613-netipc-at-scale.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,6 +1037,13 @@ Tests or equivalent validation:
10371037
- C Windows now validates the same no-progress `PAYLOAD_EXCEEDED` rejection for APPS_LOOKUP and CGROUPS_LOOKUP.
10381038
- Go POSIX/Windows, Rust POSIX/Windows, and C POSIX/Windows validate logical response-byte ceilings for APPS_LOOKUP and CGROUPS_LOOKUP.
10391039
- C POSIX also validates a final stitched response-buffer allocation fault; Go and Rust use explicit logical response-byte ceilings as the deterministic memory-pressure simulation available in those test harnesses.
1040+
- Large response split/stitch suffix-reservation validation:
1041+
- Added C, Rust, and Go POSIX/Windows tests where one logical APPS_LOOKUP or CGROUPS_LOOKUP request receives large labeled known items that cannot fit in one response payload, requiring transparent `PAYLOAD_EXCEEDED` suffix retries and final response stitching.
1042+
- The first C POSIX fail-first run exposed a real builder gap: lookup builders could accept one more full known item and then have too little buffer left to encode `PAYLOAD_EXCEEDED` for the remaining suffix. The fix reserves space for the compact overflow suffix before committing another full item.
1043+
- C protocol/server dispatch now provides per-request compact suffix item lengths to APPS_LOOKUP and CGROUPS_LOOKUP builders; Go and Rust use the same reservation model in both protocol dispatch helpers and raw service dispatchers.
1044+
- Focused POSIX validation passed: `cmake --build build-coverage --target test_service -j12 && /usr/bin/ctest --test-dir build-coverage --output-on-failure -R '^test_service$'`; `cd src/go && go test -count=1 -timeout=300s ./pkg/netipc/service/raw -run '^TestLookupLargeResponseSplit$'`; `cargo test --manifest-path src/crates/netipc/Cargo.toml large_response_split -- --nocapture`.
1045+
- Focused Windows validation passed on `win11`: `NIPC_TEST_FILTER=large_response_split timeout 600 build-windows-focused/bin/test_win_service_extra.exe`; `cd src/go && "/c/Program Files/Go/bin/go.exe" test -count=1 -timeout=300s ./pkg/netipc/service/raw -run "^TestWinLookupLargeResponseSplit$"`; `/c/Users/costa/.cargo/bin/cargo.exe test --manifest-path src/crates/netipc/Cargo.toml large_response_split -- --nocapture`.
1046+
- Protocol-level validation also passed: C `test_protocol`, Go `./pkg/netipc/protocol`, and Rust `protocol::lookup` tests.
10401047
- Note: `ctest` on `$PATH` resolved to a broken local Python wrapper missing the `cmake` module; validation used `/usr/bin/ctest`.
10411048

10421049
Real-use evidence:

src/crates/netipc/src/protocol/lookup/apps_lookup.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,7 @@ pub struct AppsLookupBuilder<'a> {
450450
data_offset: usize,
451451
error: Option<NipcError>,
452452
payload_exceeded_suffix: bool,
453+
payload_exceeded_item_lens: Vec<usize>,
453454
}
454455

455456
impl<'a> AppsLookupBuilder<'a> {
@@ -470,13 +471,18 @@ impl<'a> AppsLookupBuilder<'a> {
470471
data_offset,
471472
error: None,
472473
payload_exceeded_suffix: false,
474+
payload_exceeded_item_lens: Vec::new(),
473475
}
474476
}
475477

476478
pub fn set_generation(&mut self, generation: u64) {
477479
self.generation = generation;
478480
}
479481

482+
pub fn set_payload_exceeded_item_lens(&mut self, item_lens: Vec<usize>) {
483+
self.payload_exceeded_item_lens = item_lens;
484+
}
485+
480486
#[allow(clippy::too_many_arguments)]
481487
pub fn add(
482488
&mut self,
@@ -570,6 +576,15 @@ impl<'a> AppsLookupBuilder<'a> {
570576
Some(v) if v <= self.buf.len() => v,
571577
_ => return self.note_item_overflow(pid),
572578
};
579+
if !payload_exceeded_suffix_fits(
580+
self.buf.len(),
581+
item_end,
582+
&self.payload_exceeded_item_lens,
583+
self.item_count + 1,
584+
self.max_items,
585+
) {
586+
return self.note_item_overflow(pid);
587+
}
573588
if item_start > self.data_offset {
574589
self.buf[self.data_offset..item_start].fill(0);
575590
}
@@ -764,6 +779,12 @@ where
764779
return Err(NipcError::Overflow);
765780
}
766781
let mut builder = AppsLookupBuilder::new(resp, request.item_count, 0);
782+
if request.item_count > 0 {
783+
builder.set_payload_exceeded_item_lens(vec![
784+
APPS_LOOKUP_ITEM_HDR_SIZE + 3;
785+
request.item_count as usize
786+
]);
787+
}
767788
if !handler(&request, &mut builder) {
768789
return Err(builder.error().unwrap_or(NipcError::BadLayout));
769790
}

src/crates/netipc/src/protocol/lookup/cgroups_lookup.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@ pub struct CgroupsLookupBuilder<'a> {
324324
data_offset: usize,
325325
error: Option<NipcError>,
326326
payload_exceeded_suffix: bool,
327+
payload_exceeded_item_lens: Vec<usize>,
327328
}
328329

329330
impl<'a> CgroupsLookupBuilder<'a> {
@@ -344,13 +345,18 @@ impl<'a> CgroupsLookupBuilder<'a> {
344345
data_offset,
345346
error: None,
346347
payload_exceeded_suffix: false,
348+
payload_exceeded_item_lens: Vec::new(),
347349
}
348350
}
349351

350352
pub fn set_generation(&mut self, generation: u64) {
351353
self.generation = generation;
352354
}
353355

356+
pub fn set_payload_exceeded_item_lens(&mut self, item_lens: Vec<usize>) {
357+
self.payload_exceeded_item_lens = item_lens;
358+
}
359+
354360
pub fn add(
355361
&mut self,
356362
status: u16,
@@ -423,6 +429,15 @@ impl<'a> CgroupsLookupBuilder<'a> {
423429
Some(v) if v <= self.buf.len() => v,
424430
_ => return self.note_item_overflow(path),
425431
};
432+
if !payload_exceeded_suffix_fits(
433+
self.buf.len(),
434+
item_end,
435+
&self.payload_exceeded_item_lens,
436+
self.item_count + 1,
437+
self.max_items,
438+
) {
439+
return self.note_item_overflow(path);
440+
}
426441
if item_start > self.data_offset {
427442
self.buf[self.data_offset..item_start].fill(0);
428443
}
@@ -603,6 +618,18 @@ where
603618
return Err(NipcError::Overflow);
604619
}
605620
let mut builder = CgroupsLookupBuilder::new(resp, request.item_count, 0);
621+
if request.item_count > 0 {
622+
let mut payload_exceeded_item_lens = Vec::with_capacity(request.item_count as usize);
623+
for i in 0..request.item_count {
624+
let item = request.item(i)?;
625+
let item_len = CGROUPS_LOOKUP_ITEM_HDR_SIZE
626+
.checked_add(item.as_bytes().len())
627+
.and_then(|v| v.checked_add(2))
628+
.ok_or(NipcError::Overflow)?;
629+
payload_exceeded_item_lens.push(item_len);
630+
}
631+
builder.set_payload_exceeded_item_lens(payload_exceeded_item_lens);
632+
}
606633
if !handler(&request, &mut builder) {
607634
return Err(builder.error().unwrap_or(NipcError::BadLayout));
608635
}

src/crates/netipc/src/protocol/lookup/common.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,38 @@ pub(super) fn lookup_dir_entry_offset(hdr_size: usize, index: u32) -> Result<usi
166166
.ok_or(NipcError::BadItemCount)
167167
}
168168

169+
pub(super) fn payload_exceeded_suffix_fits(
170+
buf_len: usize,
171+
mut data_offset: usize,
172+
item_lens: &[usize],
173+
first_index: u32,
174+
max_items: u32,
175+
) -> bool {
176+
if item_lens.len() != max_items as usize {
177+
return true;
178+
}
179+
for item_len in item_lens
180+
.iter()
181+
.take(max_items as usize)
182+
.skip(first_index as usize)
183+
{
184+
let Some(item_start) = data_offset.checked_add(7).map(|v| v & !7) else {
185+
return false;
186+
};
187+
if item_start > buf_len {
188+
return false;
189+
}
190+
let Some(item_end) = item_start.checked_add(*item_len) else {
191+
return false;
192+
};
193+
if item_end > buf_len {
194+
return false;
195+
}
196+
data_offset = item_end;
197+
}
198+
true
199+
}
200+
169201
pub(super) fn validate_labels(
170202
item: &[u8],
171203
hdr_size: usize,

src/crates/netipc/src/service/raw/apps_lookup.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ use super::common::lookup_raw_response_size;
33
use super::dispatch::{DispatchError, DispatchHandler};
44
use crate::protocol::{
55
self, AppsLookupBuilder, AppsLookupRequestView, AppsLookupResponseView, NipcError,
6-
APPS_LOOKUP_KEY_SIZE, APPS_LOOKUP_REQ_HDR_SIZE, APPS_LOOKUP_RESP_HDR_SIZE,
7-
LOOKUP_DIR_ENTRY_SIZE, METHOD_APPS_LOOKUP, PID_LOOKUP_PAYLOAD_EXCEEDED,
6+
APPS_LOOKUP_ITEM_HDR_SIZE, APPS_LOOKUP_KEY_SIZE, APPS_LOOKUP_REQ_HDR_SIZE,
7+
APPS_LOOKUP_RESP_HDR_SIZE, LOOKUP_DIR_ENTRY_SIZE, METHOD_APPS_LOOKUP,
8+
PID_LOOKUP_PAYLOAD_EXCEEDED,
89
};
910
use std::sync::Arc;
1011

@@ -193,6 +194,12 @@ pub fn apps_lookup_dispatch(handler: AppsLookupHandler) -> DispatchHandler {
193194
return Err(DispatchError::Overflow);
194195
}
195196
let mut builder = AppsLookupBuilder::new(response_buf, request.item_count, 0);
197+
if request.item_count > 0 {
198+
builder.set_payload_exceeded_item_lens(vec![
199+
APPS_LOOKUP_ITEM_HDR_SIZE + 3;
200+
request.item_count as usize
201+
]);
202+
}
196203
if !handler(&request, &mut builder) {
197204
return Err(DispatchError::HandlerFailed);
198205
}

src/crates/netipc/src/service/raw/cgroups_lookup.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,18 @@ pub fn cgroups_lookup_dispatch(handler: CgroupsLookupHandler) -> DispatchHandler
245245
return Err(DispatchError::Overflow);
246246
}
247247
let mut builder = CgroupsLookupBuilder::new(response_buf, request.item_count, 0);
248+
if request.item_count > 0 {
249+
let mut payload_exceeded_item_lens = Vec::with_capacity(request.item_count as usize);
250+
for i in 0..request.item_count {
251+
let item = request.item(i).map_err(|_| DispatchError::BadEnvelope)?;
252+
let item_len = CGROUPS_LOOKUP_ITEM_HDR_SIZE
253+
.checked_add(item.as_bytes().len())
254+
.and_then(|v| v.checked_add(2))
255+
.ok_or(DispatchError::Overflow)?;
256+
payload_exceeded_item_lens.push(item_len);
257+
}
258+
builder.set_payload_exceeded_item_lens(payload_exceeded_item_lens);
259+
}
248260
if !handler(&request, &mut builder) {
249261
return Err(DispatchError::HandlerFailed);
250262
}

0 commit comments

Comments
 (0)