mirror of
https://github.com/google/flatbuffers.git
synced 2026-06-15 16:57:29 +00:00
Rust Flatbuffers Verifier (#6269)
* Updated comments and fixed a fundemental type error. * bump rust flatbuffers semver * Initial commit with verifier, need to clean up * Verifier tested. Needs clean up and refactoring. * Display for InvalidFlatbuffer and better errors for strings * SimpleToVerify, some refactoring * Combined VerifierType TableAccessorFuncBody into FollowType * scrub todos * Update Rust get_root functions. There are 6 variants, with verifier options, default verifier options and no verification "fast". * Rename root fns * inline * Update to use thiserror * fix for bad compiler * improve error formatting * Replace multiply with saturating_multiply * saturating adds too * Add docs disclaiming experimental verification system Co-authored-by: Casper Neo <cneo@google.com>
This commit is contained in:
@@ -131,7 +131,7 @@ fn main() {
|
||||
|
||||
// do many reads, forcing them to execute by using assert_eq:
|
||||
{
|
||||
let m = my_game::example::get_root_as_monster(buf);
|
||||
let m = unsafe { my_game::example::root_as_monster_unchecked(buf) };
|
||||
assert_eq!(80, m.hp());
|
||||
assert_eq!(150, m.mana());
|
||||
assert_eq!("MyMonster", m.name());
|
||||
|
||||
@@ -20,7 +20,7 @@ fn main() {
|
||||
let mut buf = Vec::new();
|
||||
f.read_to_end(&mut buf).expect("file reading failed");
|
||||
|
||||
let monster = my_game::example::get_root_as_monster(&buf[..]);
|
||||
let monster = my_game::example::root_as_monster(&buf[..]).unwrap();
|
||||
println!("{}", monster.hp()); // `80`
|
||||
println!("{}", monster.mana()); // default value of `150`
|
||||
println!("{:?}", monster.name()); // Some("MyMonster")
|
||||
|
||||
@@ -177,9 +177,9 @@ fn serialized_example_is_accessible_and_correct(bytes: &[u8], identifier_require
|
||||
}
|
||||
|
||||
let m = if size_prefixed {
|
||||
my_game::example::get_size_prefixed_root_as_monster(bytes)
|
||||
my_game::example::size_prefixed_root_as_monster(bytes).unwrap()
|
||||
} else {
|
||||
my_game::example::get_root_as_monster(bytes)
|
||||
my_game::example::root_as_monster(bytes).unwrap()
|
||||
};
|
||||
|
||||
check_eq!(m.hp(), 80)?;
|
||||
@@ -245,6 +245,106 @@ fn builder_collapses_into_vec() {
|
||||
serialized_example_is_accessible_and_correct(&backing_buf[head..], true, false).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verifier_one_byte_errors_do_not_crash() {
|
||||
let mut b = flatbuffers::FlatBufferBuilder::new();
|
||||
create_serialized_example_with_library_code(&mut b);
|
||||
let mut badbuf = b.finished_data().to_vec();
|
||||
// If the verifier says a buffer is okay then using it won't cause a crash.
|
||||
// We use write_fmt since Debug visits all the fields - but there's no need to store anything.
|
||||
struct ForgetfulWriter;
|
||||
use std::fmt::Write;
|
||||
impl Write for ForgetfulWriter {
|
||||
fn write_str(&mut self, _: &str) -> Result<(), std::fmt::Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
let mut w = ForgetfulWriter;
|
||||
for d in 1..=255u8 {
|
||||
for i in 0..badbuf.len() {
|
||||
let orig = badbuf[i];
|
||||
badbuf[i] = badbuf[i].wrapping_add(d);
|
||||
if let Ok(m) = flatbuffers::root::<my_game::example::Monster>(&badbuf) {
|
||||
w.write_fmt(format_args!("{:?}", m)).unwrap()
|
||||
}
|
||||
badbuf[i] = orig;
|
||||
}
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn verifier_too_many_tables() {
|
||||
use my_game::example::*;
|
||||
let b = &mut flatbuffers::FlatBufferBuilder::new();
|
||||
let r = Referrable::create(b, &ReferrableArgs { id: 42 });
|
||||
let rs = b.create_vector(&vec![r; 500]);
|
||||
let name = Some(b.create_string("foo"));
|
||||
let m = Monster::create(b, &MonsterArgs {
|
||||
vector_of_referrables: Some(rs),
|
||||
name, // required field.
|
||||
..Default::default()
|
||||
});
|
||||
b.finish(m, None);
|
||||
|
||||
let data = b.finished_data();
|
||||
let mut opts = flatbuffers::VerifierOptions::default();
|
||||
|
||||
opts.max_tables = 500;
|
||||
let res = flatbuffers::root_with_opts::<Monster>(&opts, data);
|
||||
assert_eq!(res.unwrap_err(), flatbuffers::InvalidFlatbuffer::TooManyTables);
|
||||
|
||||
opts.max_tables += 2;
|
||||
assert!(flatbuffers::root_with_opts::<Monster>(&opts, data).is_ok());
|
||||
}
|
||||
#[test]
|
||||
fn verifier_apparent_size_too_large() {
|
||||
use my_game::example::*;
|
||||
let b = &mut flatbuffers::FlatBufferBuilder::new();
|
||||
let name = Some(b.create_string("foo"));
|
||||
// String amplification attack.
|
||||
let s = b.create_string(&(std::iter::repeat("X").take(1000).collect::<String>()));
|
||||
let testarrayofstring = Some(b.create_vector(&vec![s; 1000]));
|
||||
let m = Monster::create(b, &MonsterArgs {
|
||||
testarrayofstring,
|
||||
name, // required field.
|
||||
..Default::default()
|
||||
});
|
||||
b.finish(m, None);
|
||||
let data = b.finished_data();
|
||||
assert!(data.len() < 5100); // est 4000 for the vector + 1000 for the string + 100 overhead.
|
||||
let mut opts = flatbuffers::VerifierOptions::default();
|
||||
opts.max_apparent_size = 1_000_000;
|
||||
|
||||
let res = flatbuffers::root_with_opts::<Monster>(&opts, data);
|
||||
assert_eq!(res.unwrap_err(), flatbuffers::InvalidFlatbuffer::ApparentSizeTooLarge);
|
||||
|
||||
opts.max_apparent_size += 20_000;
|
||||
assert!(flatbuffers::root_with_opts::<Monster>(&opts, data).is_ok());
|
||||
}
|
||||
#[test]
|
||||
fn verifier_in_too_deep() {
|
||||
use my_game::example::*;
|
||||
let b = &mut flatbuffers::FlatBufferBuilder::new();
|
||||
let name = Some(b.create_string("foo"));
|
||||
let mut prev_monster = None;
|
||||
for _ in 0..11 {
|
||||
prev_monster = Some(Monster::create(b, &MonsterArgs {
|
||||
enemy: prev_monster,
|
||||
name, // required field.
|
||||
..Default::default()
|
||||
}));
|
||||
};
|
||||
b.finish(prev_monster.unwrap(), None);
|
||||
let mut opts = flatbuffers::VerifierOptions::default();
|
||||
opts.max_depth = 10;
|
||||
|
||||
let data = b.finished_data();
|
||||
let res = flatbuffers::root_with_opts::<Monster>(&opts, data);
|
||||
assert_eq!(res.unwrap_err(), flatbuffers::InvalidFlatbuffer::DepthLimitReached);
|
||||
|
||||
opts.max_depth += 1;
|
||||
assert!(flatbuffers::root_with_opts::<Monster>(&opts, data).is_ok());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod generated_constants {
|
||||
extern crate flatbuffers;
|
||||
@@ -316,7 +416,7 @@ mod lifetime_correctness {
|
||||
let slice: &[u8] = &buf;
|
||||
let slice: &'static [u8] = unsafe { mem::transmute(slice) };
|
||||
// make sure values retrieved from the 'static buffer are themselves 'static
|
||||
let monster: my_game::example::Monster<'static> = my_game::example::get_root_as_monster(slice);
|
||||
let monster: my_game::example::Monster<'static> = my_game::example::root_as_monster(slice).unwrap();
|
||||
// this line should compile:
|
||||
let name: Option<&'static str> = monster._tab.get::<flatbuffers::ForwardsUOffset<&str>>(my_game::example::Monster::VT_NAME, None);
|
||||
assert_eq!(name, Some("MyMonster"));
|
||||
@@ -334,7 +434,7 @@ mod lifetime_correctness {
|
||||
fn table_object_self_lifetime_in_closure() {
|
||||
// This test is designed to ensure that lifetimes for temporary intermediate tables aren't inflated beyond where the need to be.
|
||||
let buf = load_file("../monsterdata_test.mon").expect("missing monsterdata_test.mon");
|
||||
let monster = my_game::example::get_root_as_monster(&buf);
|
||||
let monster = my_game::example::root_as_monster(&buf).unwrap();
|
||||
let enemy: Option<my_game::example::Monster> = monster.enemy();
|
||||
// This line won't compile if "self" is required to live for the lifetime of buf above as the borrow disappears at the end of the closure.
|
||||
let enemy_of_my_enemy = enemy.map(|e| {
|
||||
@@ -357,7 +457,7 @@ mod roundtrip_generated_code {
|
||||
fn build_mon<'a, 'b>(builder: &'a mut flatbuffers::FlatBufferBuilder, args: &'b my_game::example::MonsterArgs) -> my_game::example::Monster<'a> {
|
||||
let mon = my_game::example::Monster::create(builder, &args);
|
||||
my_game::example::finish_monster_buffer(builder, mon);
|
||||
my_game::example::get_root_as_monster(builder.finished_data())
|
||||
my_game::example::root_as_monster(builder.finished_data()).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -437,7 +537,7 @@ mod roundtrip_generated_code {
|
||||
my_game::example::finish_monster_buffer(b, outer);
|
||||
}
|
||||
|
||||
let mon = my_game::example::get_root_as_monster(b.finished_data());
|
||||
let mon = my_game::example::root_as_monster(b.finished_data()).unwrap();
|
||||
assert_eq!(mon.name(), "bar");
|
||||
assert_eq!(mon.test_type(), my_game::example::Any::Monster);
|
||||
assert_eq!(my_game::example::Monster::init_from_table(mon.test().unwrap()).name(),
|
||||
@@ -473,7 +573,7 @@ mod roundtrip_generated_code {
|
||||
my_game::example::finish_monster_buffer(b, outer);
|
||||
}
|
||||
|
||||
let mon = my_game::example::get_root_as_monster(b.finished_data());
|
||||
let mon = my_game::example::root_as_monster(b.finished_data()).unwrap();
|
||||
assert_eq!(mon.name(), "bar");
|
||||
assert_eq!(mon.enemy().unwrap().name(), "foo");
|
||||
}
|
||||
@@ -503,7 +603,7 @@ mod roundtrip_generated_code {
|
||||
my_game::example::finish_monster_buffer(b, outer);
|
||||
}
|
||||
|
||||
let mon = my_game::example::get_root_as_monster(b.finished_data());
|
||||
let mon = my_game::example::root_as_monster(b.finished_data()).unwrap();
|
||||
assert_eq!(mon.name(), "bar");
|
||||
assert_eq!(mon.testempty().unwrap().id(), Some("foo"));
|
||||
}
|
||||
@@ -540,12 +640,12 @@ mod roundtrip_generated_code {
|
||||
b1
|
||||
};
|
||||
|
||||
let m = my_game::example::get_root_as_monster(b1.finished_data());
|
||||
let m = my_game::example::root_as_monster(b1.finished_data()).unwrap();
|
||||
|
||||
assert!(m.testnestedflatbuffer().is_some());
|
||||
assert_eq!(m.testnestedflatbuffer().unwrap(), b0.finished_data());
|
||||
|
||||
let m2_a = my_game::example::get_root_as_monster(m.testnestedflatbuffer().unwrap());
|
||||
let m2_a = my_game::example::root_as_monster(m.testnestedflatbuffer().unwrap()).unwrap();
|
||||
assert_eq!(m2_a.hp(), 123);
|
||||
assert_eq!(m2_a.name(), "foobar");
|
||||
|
||||
@@ -799,7 +899,7 @@ mod generated_code_alignment_and_padding {
|
||||
my_game::example::finish_monster_buffer(b, mon);
|
||||
}
|
||||
let buf = b.finished_data();
|
||||
let mon = my_game::example::get_root_as_monster(buf);
|
||||
let mon = my_game::example::root_as_monster(buf).unwrap();
|
||||
let vec3 = mon.pos().unwrap();
|
||||
|
||||
let start_ptr = buf.as_ptr() as usize;
|
||||
@@ -835,7 +935,7 @@ mod generated_code_alignment_and_padding {
|
||||
my_game::example::finish_monster_buffer(b, mon);
|
||||
}
|
||||
let buf = b.finished_data();
|
||||
let mon = my_game::example::get_root_as_monster(buf);
|
||||
let mon = my_game::example::root_as_monster(buf).unwrap();
|
||||
let abilities = mon.testarrayofsortedstruct().unwrap();
|
||||
|
||||
let start_ptr = buf.as_ptr() as usize;
|
||||
@@ -1142,7 +1242,7 @@ mod framing_format {
|
||||
|
||||
// Access it.
|
||||
let buf = b.finished_data();
|
||||
let m = flatbuffers::get_size_prefixed_root::<my_game::example::Monster>(buf);
|
||||
let m = flatbuffers::size_prefixed_root::<my_game::example::Monster>(buf).unwrap();
|
||||
assert_eq!(m.mana(), 200);
|
||||
assert_eq!(m.hp(), 300);
|
||||
assert_eq!(m.name(), "bob");
|
||||
@@ -1538,7 +1638,7 @@ mod write_and_read_examples {
|
||||
create_serialized_example_with_generated_code(b);
|
||||
let buf = b.finished_data();
|
||||
serialized_example_is_accessible_and_correct(&buf, true, false).unwrap();
|
||||
let m = super::my_game::example::get_root_as_monster(buf);
|
||||
let m = super::my_game::example::root_as_monster(buf).unwrap();
|
||||
assert_eq!(
|
||||
format!("{:.5?}", &m),
|
||||
"Monster { pos: Some(Vec3 { x: 1.00000, y: 2.00000, z: 3.00000, \
|
||||
@@ -1728,7 +1828,7 @@ mod generated_key_comparisons {
|
||||
let builder = &mut flatbuffers::FlatBufferBuilder::new();
|
||||
super::create_serialized_example_with_library_code(builder);
|
||||
let buf = builder.finished_data();
|
||||
let a = my_game::example::get_root_as_monster(buf);
|
||||
let a = my_game::example::root_as_monster(buf).unwrap();
|
||||
|
||||
// preconditions
|
||||
assert_eq!(a.name(), "MyMonster");
|
||||
@@ -1744,7 +1844,7 @@ mod generated_key_comparisons {
|
||||
let builder = &mut flatbuffers::FlatBufferBuilder::new();
|
||||
super::create_serialized_example_with_library_code(builder);
|
||||
let buf = builder.finished_data();
|
||||
let a = my_game::example::get_root_as_monster(buf);
|
||||
let a = my_game::example::root_as_monster(buf).unwrap();
|
||||
let b = a.test_as_monster().unwrap();
|
||||
|
||||
// preconditions
|
||||
|
||||
@@ -27,13 +27,13 @@ macro_rules! make_test {
|
||||
);
|
||||
builder.finish(ss, None);
|
||||
|
||||
let s = flatbuffers::get_root::<ScalarStuff>(builder.finished_data());
|
||||
let s = flatbuffers::root::<ScalarStuff>(builder.finished_data()).unwrap();
|
||||
assert_eq!(s.$just(), $five);
|
||||
assert_eq!(s.$default(), $five);
|
||||
assert_eq!(s.$maybe(), Some($five));
|
||||
|
||||
// Test defaults are used when not specified.
|
||||
let s = flatbuffers::get_root::<ScalarStuff>(&[0; 8]);
|
||||
let s = flatbuffers::root::<ScalarStuff>(&[0; 8]).unwrap();
|
||||
assert_eq!(s.$just(), $zero);
|
||||
assert_eq!(s.$default(), $fortytwo);
|
||||
assert_eq!(s.$maybe(), None);
|
||||
|
||||
Reference in New Issue
Block a user