Problem
When parsing into a struct with serde_json, I expect it to accept json records with key -> value mappings. However, it appears serde_json also accepts arrays.
This is a relatively large issue as it causes a couple things:
- rearranging fields, adding new fields, etc. are now breaking changes
- an entire class of user input is unexpectedly valid, which makes it significantly harder to parse JSON according to a spec.
In my case, I'm working on a file format that lots of programming languages need to interop with. I've chosen a JSON representation and although there is a spec in practice, whatever the program accepts IS the spec to end-users.[0]
Since there's seemingly no way to disable this behaviour, this means that my spec has to accept this as input and needlessly define a "correct" ordering of keys, when I don't think one even really makes sense here.
Example Code
#[derive(serde::Deserialize, Debug)]
struct Foo {
bloop: u32,
goop: u32,
}
#[derive(serde::Deserialize, Debug)]
struct User {
username: String,
age: u32,
is_cool: bool,
more_data: String,
}
fn main() {
let foo: Foo = serde_json::from_str(r#"[182, 37]"#).unwrap();
// this is accepted?
println!("{:?}", foo);
// this is all accepted too
let user: User = serde_json::from_str(r#"["bloopgoop", 138, true, "googblorb"]"#).unwrap();
println!("{:?}", user);
}
Playground link: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&code=%23%5Bderive%28serde%3A%3ADeserialize%2C+Debug%29%5D%0Astruct+Foo+%7B%0A++++bloop%3A+u32%2C%0A++++goop%3A+u32%2C%0A%7D%0A%0A%23%5Bderive%28serde%3A%3ADeserialize%2C+Debug%29%5D%0Astruct+User+%7B%0A++++username%3A+String%2C%0A++++age%3A+u32%2C%0A++++is_cool%3A+bool%2C%0A++++more_data%3A+String%2C%0A%7D%0A%0Afn+main%28%29+%7B%0A++++let+foo%3A+Foo+%3D+serde_json%3A%3Afrom_str%28r%23%22%5B182%2C+37%5D%22%23%29.unwrap%28%29%3B%0A++++println%21%28%22%7B%3A%3F%7D%22%2C+foo%29%3B%0A++++%0A++++let+user%3A+User+%3D+serde_json%3A%3Afrom_str%28r%23%22%5B%22bloopgoop%22%2C+138%2C+true%2C+%22googblorb%22%5D%22%23%29.unwrap%28%29%3B%0A++++println%21%28%22%7B%3A%3F%7D%22%2C+user%29%3B%0A%7D
As a footgun
I'd guess that the vast majority of serde_json consumers don't know about this behaviour at all, and are needlessly inserting dependencies on the order of keys in their structs when they never ever wanted that behaviour. They're also accepting JSON that they would not consider to be acceptable or stable input for their program.
While I don't imagine this would cause any CVEs or anything, it's extremely easy for this to be abused on purpose or on accident by someone producing JSON for consumption: if what they produce is accepted by the program, then it is valid input regardless of whatever the author intended.
Also, the documentation explicitly states that this is not possible behaviour:

It's also possible that people could write code where authors assume something like:
#[derive(Serialize, Deserialize)]
struct CoOrd { x: f32, y: f32 }
let str = some_user_input();
let c: CoOrd = serde_json::from_str(&str).unwrap();
// this parsed, so we *know* for certain that `str` contains X and Y fields... right?
// .. do some stuff ..
// .. then pass this on to an application written in another language, say Typescript
send_to_typescript(str);
let input = fetch_input_from_rust_server();
// and we KNOW for certain that this is valid because it couldn't get to this point otherwise.. no need to validate..
input = input as { x: number, y: number };
// whoops you just crashed: x was undefined
input.x.toFixed(2);
obviously not validating here is lazy and bad, but we all know this happens all the time in the wild when input doesn't come from the user.
[0]: see mp3, html pre-html5, basically any major spec that has been liberal with what it accepts; there's a spec but in practice what matters is what was accepted. it doesn't matter that a spec says this is technically nonsense, if the users software accepts it then people are going to expect it to work.
Problem
When parsing into a
structwithserde_json, I expect it to accept json records with key -> value mappings. However, it appearsserde_jsonalso accepts arrays.This is a relatively large issue as it causes a couple things:
In my case, I'm working on a file format that lots of programming languages need to interop with. I've chosen a JSON representation and although there is a spec in practice, whatever the program accepts IS the spec to end-users.[0]
Since there's seemingly no way to disable this behaviour, this means that my spec has to accept this as input and needlessly define a "correct" ordering of keys, when I don't think one even really makes sense here.
Example Code
Playground link: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&code=%23%5Bderive%28serde%3A%3ADeserialize%2C+Debug%29%5D%0Astruct+Foo+%7B%0A++++bloop%3A+u32%2C%0A++++goop%3A+u32%2C%0A%7D%0A%0A%23%5Bderive%28serde%3A%3ADeserialize%2C+Debug%29%5D%0Astruct+User+%7B%0A++++username%3A+String%2C%0A++++age%3A+u32%2C%0A++++is_cool%3A+bool%2C%0A++++more_data%3A+String%2C%0A%7D%0A%0Afn+main%28%29+%7B%0A++++let+foo%3A+Foo+%3D+serde_json%3A%3Afrom_str%28r%23%22%5B182%2C+37%5D%22%23%29.unwrap%28%29%3B%0A++++println%21%28%22%7B%3A%3F%7D%22%2C+foo%29%3B%0A++++%0A++++let+user%3A+User+%3D+serde_json%3A%3Afrom_str%28r%23%22%5B%22bloopgoop%22%2C+138%2C+true%2C+%22googblorb%22%5D%22%23%29.unwrap%28%29%3B%0A++++println%21%28%22%7B%3A%3F%7D%22%2C+user%29%3B%0A%7D
As a footgun
I'd guess that the vast majority of
serde_jsonconsumers don't know about this behaviour at all, and are needlessly inserting dependencies on the order of keys in theirstructs when they never ever wanted that behaviour. They're also accepting JSON that they would not consider to be acceptable or stable input for their program.While I don't imagine this would cause any CVEs or anything, it's extremely easy for this to be abused on purpose or on accident by someone producing JSON for consumption: if what they produce is accepted by the program, then it is valid input regardless of whatever the author intended.
Also, the documentation explicitly states that this is not possible behaviour:

It's also possible that people could write code where authors assume something like:
obviously not validating here is lazy and bad, but we all know this happens all the time in the wild when input doesn't come from the user.
[0]: see mp3, html pre-html5, basically any major spec that has been liberal with what it accepts; there's a spec but in practice what matters is what was accepted. it doesn't matter that a spec says this is technically nonsense, if the users software accepts it then people are going to expect it to work.