Skip to content

Commit 13ac570

Browse files
committed
test: consolidate encoded_len* tests
Move the encoded_len_limit tests to the tests-overflow crate, as well as the encoded_len.proto file used to generate the testbed. I was getting carried away with testing these limits, up to adding Kani proofs, but the added CI complexity is not worth it. It could have been a boolean encoded_len_limited method for what matters, as the number of fields in the message is limited enough so that overflowing unchecked arithmetic on lengths of the numeric fields should never be a concern in practice. Still, the tests-overflow crate is close enough thematically and is not saddled with a dependency on protobuf which takes a long time to build.
1 parent de05493 commit 13ac570

File tree

9 files changed

+373
-380
lines changed

9 files changed

+373
-380
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,14 +169,14 @@ jobs:
169169
- name: test for integer overflows
170170
run: |
171171
cargo test -p tests-overflow --target i686-unknown-linux-gnu -- \
172-
--skip encoded_len::field::int32::encoded_len_packed_can_overflow_u32 \
173-
--skip encoded_len::field::int32::encoded_len_repeated_can_overflow_u32 \
172+
--skip encoded_len::overflow::field::int32::encoded_len_packed_can_overflow_u32 \
173+
--skip encoded_len::overflow::field::int32::encoded_len_repeated_can_overflow_u32 \
174174
--include-ignored --test-threads 1
175175
- name: run tests with large allocations in isolation
176176
run: |
177177
for test in \
178-
encoded_len::field::int32::encoded_len_packed_can_overflow_u32 \
179-
encoded_len::field::int32::encoded_len_repeated_can_overflow_u32 \
178+
encoded_len::overflow::field::int32::encoded_len_packed_can_overflow_u32 \
179+
encoded_len::overflow::field::int32::encoded_len_repeated_can_overflow_u32 \
180180
;
181181
do
182182
cargo test -p tests-overflow --target i686-unknown-linux-gnu -- \

tests/build.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,6 @@ fn main() {
102102
.compile_protos(&[src.join("default_string_escape.proto")], includes)
103103
.unwrap();
104104

105-
config
106-
.compile_protos(&[src.join("encoded_len.proto")], includes)
107-
.unwrap();
108-
109105
prost_build::Config::new()
110106
.skip_debug(["custom_debug.Msg"])
111107
.compile_protos(&[src.join("custom_debug.proto")], includes)

tests/overflow/build.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@ use std::path::PathBuf;
33
fn main() {
44
env_logger::init();
55

6-
let src = PathBuf::from("../../tests/src");
6+
let src = PathBuf::from("src");
77
let includes = &[src.clone()];
88

99
let mut config = prost_build::Config::new();
10-
config.btree_map(["."]);
11-
1210
config
11+
.btree_map(["."])
1312
.compile_protos(&[src.join("encoded_len.proto")], includes)
1413
.unwrap();
1514
}
File renamed without changes.

tests/overflow/src/encoded_len.rs

Lines changed: 2 additions & 328 deletions
Original file line numberDiff line numberDiff line change
@@ -1,332 +1,6 @@
1-
//! Tests for integer overflow behavior of encoded_len* functions.
2-
//!
3-
//! Many of the tests in this module allocate and fill large amounts of RAM,
4-
//! so they should better be run in isolation and in a sufficiently
5-
//! memory-budgeted environment. Particularly on 32-bit platforms, the tests
6-
//! should not be run in parallel as the combined allocation requests can exceed
7-
//! the addressable memory.
8-
91
mod proto {
102
include!(concat!(env!("OUT_DIR"), "/encoded_len.rs"));
113
}
124

13-
use prost::alloc::vec;
14-
15-
#[cfg(target_pointer_width = "64")]
16-
fn verify_overflowing_encoded_len(actual: usize, expected: u64) -> bool {
17-
if actual as u64 == expected {
18-
true
19-
} else {
20-
cfg_if! {
21-
if #[cfg(feature = "std")] {
22-
eprintln!("expected {} but the function returned {}", expected, actual);
23-
}
24-
}
25-
false
26-
}
27-
}
28-
29-
#[cfg(target_pointer_width = "32")]
30-
fn verify_overflowing_encoded_len(actual: usize, _expected: u64) -> bool {
31-
// Tests calling this function are expected to panic on 32-bit platforms
32-
// before this check is called. Returning true here allows the
33-
// #[should_panic] tests to fail.
34-
cfg_if! {
35-
if #[cfg(feature = "std")] {
36-
eprintln!("expected panic, but the function returned {actual}");
37-
}
38-
}
39-
true
40-
}
41-
42-
mod field {
43-
// Test encoded_len* functions in prost::encoding submodules for various field types.
44-
45-
use super::*;
46-
47-
mod bool {
48-
use super::*;
49-
use prost::encoding::{bool, MAX_TAG};
50-
51-
#[test]
52-
#[ignore = "allocates and fills about 666 MiB"]
53-
#[cfg_attr(target_pointer_width = "32", should_panic)]
54-
fn encoded_len_repeated_can_overflow_u32() {
55-
let filler = false;
56-
let filler_len = bool::encoded_len(MAX_TAG, &filler);
57-
let bomb32 = vec![filler; u32::MAX as usize / filler_len + 1];
58-
let encoded_len = bool::encoded_len_repeated(MAX_TAG, &bomb32);
59-
let expected_len = bomb32.len() as u64 * filler_len as u64;
60-
assert!(verify_overflowing_encoded_len(encoded_len, expected_len));
61-
}
62-
}
63-
64-
// These tests may abort on the large allocations when built on a 32-bit
65-
// target and run in company with some other tests, even with
66-
// --test-threads=1.
67-
// As heap fragmentation seemingly becomes a problem, these tests are best
68-
// run in isolation.
69-
mod int32 {
70-
use super::*;
71-
use prost::encoding::{int32, MAX_TAG};
72-
73-
#[test]
74-
#[ignore = "allocates and fills more than 1 GiB"]
75-
#[cfg_attr(target_pointer_width = "32", should_panic)]
76-
fn encoded_len_repeated_can_overflow_u32() {
77-
let filler = -1i32;
78-
let filler_len = int32::encoded_len(MAX_TAG, &filler);
79-
let bomb32 = vec![filler; u32::MAX as usize / filler_len + 1];
80-
let encoded_len = int32::encoded_len_repeated(MAX_TAG, &bomb32);
81-
let expected_len = bomb32.len() as u64 * filler_len as u64;
82-
assert!(verify_overflowing_encoded_len(encoded_len, expected_len));
83-
}
84-
85-
#[test]
86-
#[ignore = "allocates and fills about 1.6 GiB"]
87-
#[cfg_attr(target_pointer_width = "32", should_panic)]
88-
fn encoded_len_packed_can_overflow_u32() {
89-
use prost::encoding::{encoded_len_varint, key_len};
90-
91-
let filler = -1i32;
92-
let filler_len = encoded_len_varint(filler as u64);
93-
let bomb_len = (u32::MAX as usize - key_len(MAX_TAG) - 5) / filler_len + 1;
94-
let bomb32 = vec![filler; bomb_len];
95-
let encoded_len = int32::encoded_len_packed(MAX_TAG, &bomb32);
96-
let expected_data_len = bomb_len as u64 * filler_len as u64;
97-
let expected_len = key_len(MAX_TAG) as u64
98-
+ encoded_len_varint(expected_data_len) as u64
99-
+ expected_data_len;
100-
assert!(verify_overflowing_encoded_len(encoded_len, expected_len));
101-
}
102-
}
103-
104-
mod message {
105-
use super::*;
106-
use crate::encoded_len::proto;
107-
use prost::encoding::{encoded_len_varint, key_len, message, MAX_TAG};
108-
109-
#[test]
110-
#[cfg_attr(target_pointer_width = "32", should_panic)]
111-
fn encoded_len_can_overflow_u32() {
112-
let filler = proto::Empty {};
113-
let filler_len = message::encoded_len(MAX_TAG, &filler);
114-
let subcritical = vec![filler; u32::MAX as usize / filler_len];
115-
let payload_len = subcritical.len() * filler_len;
116-
assert_eq!(encoded_len_varint(payload_len as u64), 5);
117-
assert!(key_len(MAX_TAG) + 5 >= filler_len);
118-
let bomb32 = proto::Testbed {
119-
repeated_empty: subcritical,
120-
..Default::default()
121-
};
122-
let encoded_len = message::encoded_len(MAX_TAG, &bomb32);
123-
let expected_len = (key_len(MAX_TAG) + 5) as u64 + payload_len as u64;
124-
assert!(verify_overflowing_encoded_len(encoded_len, expected_len));
125-
}
126-
127-
#[test]
128-
#[cfg_attr(target_pointer_width = "32", should_panic)]
129-
fn encoded_len_repeated_can_overflow_u32() {
130-
let filler = proto::Empty {};
131-
let filler_len = message::encoded_len(MAX_TAG, &filler);
132-
let bomb32 = vec![filler; u32::MAX as usize / filler_len + 1];
133-
let encoded_len = message::encoded_len_repeated(MAX_TAG, &bomb32);
134-
let expected_len = bomb32.len() as u64 * filler_len as u64;
135-
assert!(verify_overflowing_encoded_len(encoded_len, expected_len));
136-
}
137-
}
138-
139-
macro_rules! test_map {
140-
($map_mod:ident, $map_init:expr) => {
141-
use prost::encoding::{int32, message, $map_mod, MAX_TAG};
142-
143-
#[test]
144-
#[ignore = "allocates and fills about 1 GiB"]
145-
#[cfg_attr(target_pointer_width = "32", should_panic)]
146-
fn encoded_len_can_overflow_u32() {
147-
let encoded_entry_len = 5 + 1 + 1 + 10;
148-
let num_entries = u32::MAX as usize / encoded_entry_len + 1;
149-
let mut map = $map_init(num_entries);
150-
map.extend((-(num_entries as i32)..0).map(|i| (i, proto::Empty {})));
151-
let encoded_len =
152-
$map_mod::encoded_len(int32::encoded_len, message::encoded_len, MAX_TAG, &map);
153-
let expected_len = num_entries as u64 * encoded_entry_len as u64;
154-
assert!(verify_overflowing_encoded_len(encoded_len, expected_len));
155-
}
156-
};
157-
}
158-
159-
mod btree_map {
160-
use super::*;
161-
use prost::alloc::collections::BTreeMap;
162-
163-
test_map!(btree_map, |_| BTreeMap::new());
164-
}
165-
166-
#[cfg(feature = "std")]
167-
mod hash_map {
168-
use super::*;
169-
use std::collections::HashMap;
170-
171-
test_map!(hash_map, HashMap::with_capacity);
172-
}
173-
}
174-
175-
mod derived {
176-
// Test overflow behavior of Message::encoded_len method implementations
177-
// generated by prost-derive.
178-
179-
use super::*;
180-
use crate::encoded_len::proto;
181-
use prost::alloc::collections::BTreeMap;
182-
use prost::alloc::string::String;
183-
use prost::alloc::vec::Vec;
184-
use prost::encoding::{message, MAX_TAG};
185-
use prost::Message;
186-
187-
// Initializes all scalar fields so as to give the largest possible
188-
// encodings for these fields.
189-
const FATTEST_SCALARS: proto::Testbed = proto::Testbed {
190-
int32: -1,
191-
int64: -1,
192-
uint32: u32::MAX,
193-
uint64: u64::MAX,
194-
sint32: i32::MIN,
195-
sint64: i64::MIN,
196-
fixed32: 1,
197-
fixed64: 1,
198-
sfixed32: -1,
199-
sfixed64: -1,
200-
float: 1.0,
201-
double: 1.0,
202-
bool: true,
203-
enumeration: proto::BadEnum::Long as i32,
204-
string: String::new(),
205-
bytes: Vec::new(),
206-
packed_int32: vec![],
207-
map: BTreeMap::new(),
208-
repeated_empty: vec![],
209-
};
210-
211-
const SCALAR_ENCODED_LEN_LIMITS: &[usize] = &[
212-
10, // int32
213-
10, // int64
214-
5, // uint32
215-
10, // uint64
216-
5, // sint32
217-
10, // sint64
218-
4, // fixed32
219-
8, // fixed64
220-
4, // sfixed32
221-
8, // sfixed64
222-
4, // float
223-
8, // double
224-
1, // bool
225-
10, // enumeration
226-
];
227-
228-
#[test]
229-
fn limited_length_scalar_encodings_are_accounted_for() {
230-
assert_eq!(
231-
FATTEST_SCALARS.encoded_len(),
232-
SCALAR_ENCODED_LEN_LIMITS
233-
.iter()
234-
.cloned()
235-
.map(|len| 1 + len)
236-
.sum()
237-
);
238-
}
239-
240-
#[test]
241-
#[cfg_attr(target_pointer_width = "32", should_panic)]
242-
fn encoded_len_can_overflow_u32_with_repeated_field() {
243-
let filler = proto::Empty {};
244-
let filler_len = message::encoded_len(MAX_TAG, &filler);
245-
let supercritical =
246-
vec![filler; (u32::MAX as usize - FATTEST_SCALARS.encoded_len()) / filler_len + 1];
247-
let payload_len = supercritical.len() as u64 * filler_len as u64;
248-
let bomb32 = proto::Testbed {
249-
repeated_empty: supercritical,
250-
..FATTEST_SCALARS
251-
};
252-
let encoded_len = bomb32.encoded_len();
253-
let expected_len = FATTEST_SCALARS.encoded_len() as u64 + payload_len as u64;
254-
assert!(verify_overflowing_encoded_len(encoded_len, expected_len));
255-
}
256-
257-
#[test]
258-
#[cfg_attr(target_pointer_width = "32", should_panic)]
259-
fn encoded_len_can_overflow_u32_with_string() {
260-
let filler = proto::Empty {};
261-
let filler_len = message::encoded_len(MAX_TAG, &filler);
262-
let padding =
263-
vec![filler; (u32::MAX as usize - FATTEST_SCALARS.encoded_len()) / filler_len];
264-
let padding_len = padding.len() * filler_len;
265-
let bomb32 = proto::Testbed {
266-
repeated_empty: padding,
267-
string: " ".repeat(filler_len - 2 - 1),
268-
..FATTEST_SCALARS
269-
};
270-
let encoded_len = bomb32.encoded_len();
271-
let expected_len =
272-
FATTEST_SCALARS.encoded_len() as u64 + padding_len as u64 + filler_len as u64;
273-
assert!(verify_overflowing_encoded_len(encoded_len, expected_len));
274-
}
275-
276-
#[test]
277-
#[cfg_attr(target_pointer_width = "32", should_panic)]
278-
fn encoded_len_can_overflow_u32_with_bytes() {
279-
let filler = proto::Empty {};
280-
let filler_len = message::encoded_len(MAX_TAG, &filler);
281-
let padding =
282-
vec![filler; (u32::MAX as usize - FATTEST_SCALARS.encoded_len()) / filler_len];
283-
let padding_len = padding.len() * filler_len;
284-
let bomb32 = proto::Testbed {
285-
repeated_empty: padding,
286-
bytes: b" ".repeat(filler_len - 2 - 1),
287-
..FATTEST_SCALARS
288-
};
289-
let encoded_len = bomb32.encoded_len();
290-
let expected_len =
291-
FATTEST_SCALARS.encoded_len() as u64 + padding_len as u64 + filler_len as u64;
292-
assert!(verify_overflowing_encoded_len(encoded_len, expected_len));
293-
}
294-
295-
#[test]
296-
#[cfg_attr(target_pointer_width = "32", should_panic)]
297-
fn encoded_len_can_overflow_u32_with_packed_varint() {
298-
let filler = proto::Empty {};
299-
let filler_len = message::encoded_len(MAX_TAG, &filler);
300-
let padding =
301-
vec![filler; (u32::MAX as usize - FATTEST_SCALARS.encoded_len()) / filler_len];
302-
let padding_len = padding.len() * filler_len;
303-
let bomb32 = proto::Testbed {
304-
repeated_empty: padding,
305-
packed_int32: vec![0; filler_len - 2 - 1],
306-
..FATTEST_SCALARS
307-
};
308-
let encoded_len = bomb32.encoded_len();
309-
let expected_len =
310-
FATTEST_SCALARS.encoded_len() as u64 + padding_len as u64 + filler_len as u64;
311-
assert!(verify_overflowing_encoded_len(encoded_len, expected_len));
312-
}
313-
314-
#[test]
315-
#[cfg_attr(target_pointer_width = "32", should_panic)]
316-
fn encoded_len_can_overflow_u32_with_map() {
317-
let filler = proto::Empty {};
318-
let filler_len = message::encoded_len(MAX_TAG, &filler);
319-
let padding =
320-
vec![filler; (u32::MAX as usize - FATTEST_SCALARS.encoded_len()) / filler_len];
321-
let padding_len = padding.len() * filler_len;
322-
let map = [(0, -1)].iter().cloned().collect();
323-
let bomb32 = proto::Testbed {
324-
repeated_empty: padding,
325-
map,
326-
..FATTEST_SCALARS
327-
};
328-
let encoded_len = bomb32.encoded_len();
329-
let expected_len = FATTEST_SCALARS.encoded_len() as u64 + padding_len as u64 + 14;
330-
assert!(verify_overflowing_encoded_len(encoded_len, expected_len));
331-
}
332-
}
5+
mod limit;
6+
mod overflow;

0 commit comments

Comments
 (0)