Skip to content

Commit 6b703cf

Browse files
authored
silent overflow of run_word_count when casting to 1 byte (#622)
* repro silent overflow of run_word_count when casting to 1 byte * fix overflow of run_word_count
1 parent 2ae271a commit 6b703cf

File tree

3 files changed

+95
-8
lines changed

3 files changed

+95
-8
lines changed

capnp-futures/src/serialize_packed.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -395,8 +395,8 @@ where
395395
if *buf_pos == *packed_buf_size {
396396
if packed_buf[0] == 0 {
397397
// see how long of a run we can make
398-
let mut words_in_run = inbuf.len() / 8;
399-
for (idx, inb) in inbuf.iter().enumerate() {
398+
let mut words_in_run = (inbuf.len() / 8).min(u8::MAX.into());
399+
for (idx, inb) in inbuf[..words_in_run * 8].iter().enumerate() {
400400
if *inb != 0 {
401401
words_in_run = idx / 8;
402402
break;
@@ -408,10 +408,9 @@ where
408408
// See how long of a run we can make.
409409
// We look for at least two zeros because that's the point
410410
// where our compression scheme becomes a net win.
411-
let mut words_in_run = inbuf.len() / 8;
412-
411+
let mut words_in_run = (inbuf.len() / 8).min(u8::MAX.into());
413412
let mut zero_bytes_in_word = 0;
414-
for (idx, inb) in inbuf.iter().enumerate() {
413+
for (idx, inb) in inbuf[..words_in_run * 8].iter().enumerate() {
415414
if idx % 8 == 0 {
416415
zero_bytes_in_word = 0;
417416
}
@@ -432,9 +431,12 @@ where
432431
}
433432
}
434433
PackedWriteStage::WriteRunWordCount => {
435-
match Pin::new(&mut *inner)
436-
.poll_write(cx, &[(*run_bytes_remaining / 8) as u8])?
437-
{
434+
match Pin::new(&mut *inner).poll_write(
435+
cx,
436+
&[(*run_bytes_remaining / 8)
437+
.try_into()
438+
.expect("overflow writing run word count")],
439+
)? {
438440
Poll::Pending => {
439441
if inbuf_bytes_consumed == 0 {
440442
return Poll::Pending;

capnp-futures/test/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,7 @@ capnp-futures = {path = "./../"}
1919
capnp = { path = "../../capnp" }
2020
futures = "0.3.0"
2121
async-byte-channel = {path = "./../../async-byte-channel"}
22+
23+
[[test]]
24+
name = "overflow_test"
25+
path = "overflow_test.rs"
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Regression test for the async PackedWrite run-length overflow bug.
2+
//
3+
// When a message contains more than 255 consecutive non-zero (or zero) words,
4+
// the run count byte must be capped at 255 to avoid silent truncation via
5+
// `as u8`. This test verifies that the async packed writer produces output
6+
// that can be correctly read back by both the async and synchronous packed
7+
// readers, even for large messages that exceed the 255-word run threshold.
8+
9+
capnp::generated_code!(pub mod addressbook_capnp);
10+
11+
#[cfg(test)]
12+
mod tests {
13+
use crate::addressbook_capnp::address_book;
14+
use capnp::message;
15+
use capnp::message::HeapAllocator;
16+
use capnp::serialize::OwnedSegments;
17+
18+
fn populate_large_address_book(address_book: address_book::Builder) {
19+
let mut people = address_book.init_people(1);
20+
let mut entry = people.reborrow().get(0);
21+
22+
// A long name ensures a big contiguous non-zero region.
23+
let long_name: String = "A".repeat(100_000);
24+
entry.set_name(&long_name);
25+
}
26+
27+
fn verify_large_address_book(reader: address_book::Reader) {
28+
let people = reader.get_people().unwrap();
29+
assert_eq!(people.len(), 1);
30+
let entry = people.get(0);
31+
32+
let name = entry.get_name().unwrap().to_str().unwrap();
33+
assert_eq!(name.len(), 100_000);
34+
assert!(name.chars().all(|c| c == 'A'));
35+
}
36+
37+
fn write_sync(builder: &message::Builder<HeapAllocator>) -> Vec<u8> {
38+
let mut buf: Vec<u8> = Vec::new();
39+
capnp::serialize_packed::write_message(&mut buf, builder).unwrap();
40+
buf
41+
}
42+
43+
fn write_async(builder: &message::Builder<HeapAllocator>) -> Vec<u8> {
44+
futures::executor::block_on(async {
45+
let mut buf: Vec<u8> = Vec::new();
46+
capnp_futures::serialize_packed::write_message(&mut buf, builder)
47+
.await
48+
.unwrap();
49+
buf
50+
})
51+
}
52+
53+
fn read_sync(buf: &[u8]) -> message::Reader<OwnedSegments> {
54+
capnp::serialize_packed::read_message(buf, message::DEFAULT_READER_OPTIONS).unwrap()
55+
}
56+
57+
fn read_async(buf: &[u8]) -> message::Reader<OwnedSegments> {
58+
futures::executor::block_on(async {
59+
capnp_futures::serialize_packed::read_message(buf, message::DEFAULT_READER_OPTIONS)
60+
.await
61+
.unwrap()
62+
})
63+
}
64+
65+
#[test]
66+
fn test_write_sync_write_async_equivalence() {
67+
let mut builder = message::Builder::new(HeapAllocator::new());
68+
populate_large_address_book(builder.init_root());
69+
70+
let sync_buf = write_sync(&builder);
71+
let async_buf = write_async(&builder);
72+
73+
assert_eq!(sync_buf, async_buf);
74+
75+
verify_large_address_book(read_sync(&sync_buf).get_root().unwrap());
76+
verify_large_address_book(read_sync(&async_buf).get_root().unwrap());
77+
78+
verify_large_address_book(read_async(&sync_buf).get_root().unwrap());
79+
verify_large_address_book(read_async(&async_buf).get_root().unwrap());
80+
}
81+
}

0 commit comments

Comments
 (0)