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:
Casper
2020-12-07 18:37:51 -05:00
committed by GitHub
parent 9064072e8c
commit 442949bc11
21 changed files with 1861 additions and 305 deletions

View File

@@ -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());

View File

@@ -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")

View File

@@ -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

View File

@@ -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);