1 of 35

Taint analysis

&

Serde's serialize_struct "quirk"

two lightning talks by Disconnect3d

@ Rust Cracow #1

[20.10.2023]

@disconnect3d_pl

2 of 35

Taint analysis 101

"Taint analysis is a process (...) to identify the flow of user input through �a system to understand the security implications of the system design"

3 of 35

Taint analysis 101

4 of 35

Why you may want taint tracking?

  • Make sure that certain inputs are always sanitized before using
    • E.g. escaping input before rendering a HTML, JS, etc.
  • Make sure that sensitive data (PII - personally identifiable information) does not leak to logs or is saved to disk
  • Understand how the data flows through your program
    • What APIs consume the data etc.

5 of 35

Rust taint analysis projects

  • https://github.com/facebookexperimental/MIRAI
    • Abstract interpreter for Rust compiler's MIR (Medium Intermediate Representation)
  • https://github.com/LiHRaM/taint
    • Master thesis from 2021: "Using Rusts Ownership System for Precise Static Analysis"
  • https://github.com/wmkhoo/taintgrind
    • Valgrind plugin: taint at binary level

6 of 35

Example of taint analysis in Rust�(from LiHRaM/taint)

7 of 35

// Program takes unsanitised input in one branch of an if statement. Since we can't at compile �// time say which branch will be taken, we must assume that b may be tainted and throw an error.

#![feature(register_tool)]

#![register_tool(taint)]

fn main() {

//This input is not an issue, as we allow input to be used to decide control flow

let a = input();

let b;

if a < 5 {

b = input(); // This input is an issue, as b may be used in the output function.

} else {

b = 5;

}

output(b); //~ ERROR function `output` received tainted input [T0001]

}

#[taint::source]

fn input() -> i32 {

4

}

#[taint::sink]

fn output(_: i32) {

()

}

8 of 35

// Program takes unsanitised input in one branch of an if statement. Since we can't at compile �// time say which branch will be taken, we must assume that b may be tainted and throw an error.

#![feature(register_tool)]

#![register_tool(taint)]

fn main() {

//This input is not an issue, as we allow input to be used to decide control flow

let a = input();

let b;

if a < 5 {

b = input(); // This input is an issue, as b may be used in the output function.

} else {

b = 5;

}

output(b); //~ ERROR function `output` received tainted input [T0001]

}

#[taint::source]

fn input() -> i32 {

4

}

#[taint::sink]

fn output(_: i32) {

()

}

9 of 35

// Program takes unsanitised input in one branch of an if statement. Since we can't at compile �// time say which branch will be taken, we must assume that b may be tainted and throw an error.

#![feature(register_tool)]

#![register_tool(taint)]

fn main() {

//This input is not an issue, as we allow input to be used to decide control flow

let a = input();

let b;

if a < 5 {

b = input(); // This input is an issue, as b may be used in the output function.

} else {

b = 5;

}

output(b); //~ ERROR function `output` received tainted input [T0001]

}

#[taint::source]

fn input() -> i32 {

4

}

#[taint::sink]

fn output(_: i32) {

()

}

10 of 35

// Program takes unsanitised input in one branch of an if statement. Since we can't at compile �// time say which branch will be taken, we must assume that b may be tainted and throw an error.

#![feature(register_tool)]

#![register_tool(taint)]

fn main() {

//This input is not an issue, as we allow input to be used to decide control flow

let a = input();

let b;

if a < 5 {

b = input(); // This input is an issue, as b may be used in the output function.

} else {

b = 5;

}

output(b); //~ ERROR function `output` received tainted input [T0001]

}

#[taint::source]

fn input() -> i32 {

4

}

#[taint::sink]

fn output(_: i32) {

()

}

11 of 35

// Program takes unsanitised input in one branch of an if statement. Since we can't at compile �// time say which branch will be taken, we must assume that b may be tainted and throw an error.

#![feature(register_tool)]

#![register_tool(taint)]

fn main() {

//This input is not an issue, as we allow input to be used to decide control flow

let a = input();

let b;

if a < 5 {

b = input(); // This input is an issue, as b may be used in the output function.

} else {

b = 5;

}

output(b); //~ ERROR function `output` received tainted input [T0001]

}

#[taint::source]

fn input() -> i32 {

4

}

#[taint::sink]

fn output(_: i32) {

()

}

12 of 35

// Program takes unsanitised input in one branch of an if statement. Since we can't at compile �// time say which branch will be taken, we must assume that b may be tainted and throw an error.

#![feature(register_tool)]

#![register_tool(taint)]

fn main() {

//This input is not an issue, as we allow input to be used to decide control flow

let a = input();

let b;

if a < 5 {

b = input(); // This input is an issue, as b may be used in the output function.

} else {

b = 5;

}

output(b); //~ ERROR function `output` received tainted input [T0001]

}

#[taint::source]

fn input() -> i32 {

4

}

#[taint::sink]

fn output(_: i32) {

()

}

13 of 35

serde serialize_struct�quirk

a lightning talk by Disconnect3d

@ Rust Cracow #1

[20.10.2023]

14 of 35

15 of 35

16 of 35

17 of 35

18 of 35

???

19 of 35

20 of 35

21 of 35

22 of 35

23 of 35

🤷 unused _len

24 of 35

25 of 35

How to live?

Use Semgrep or Dylint (TBD)

26 of 35

27 of 35

rules:

- id: incorrect-serialize-struct

message: "Serializing a structure with the incorrect number of fields."

languages: [rust]

severity: ERROR

patterns:

- pattern-either:

- pattern: |

let $X = $S.serialize_struct($NAME, $T)?;

...

$X.end()

- pattern-not: |

let $X = $S.serialize_struct($NAME, 1)?;

...

$X.serialize_field(...);

...

$X.end()

- pattern-not: |

let $X = $S.serialize_struct($NAME, 2)?;

...

$X.serialize_field(...);

...

$X.serialize_field(...);

...

$X.end()

28 of 35

rules:

- id: incorrect-serialize-struct

message: "Serializing a structure with the incorrect number of fields."

languages: [rust]

severity: ERROR

patterns:

- pattern-either:

- pattern: |

let $X = $S.serialize_struct($NAME, $T)?;

...

$X.end()

- pattern-not: |

let $X = $S.serialize_struct($NAME, 1)?;

...

$X.serialize_field(...);

...

$X.end()

- pattern-not: |

let $X = $S.serialize_struct($NAME, 2)?;

...

$X.serialize_field(...);

...

$X.serialize_field(...);

...

$X.end()

29 of 35

rules:

- id: incorrect-serialize-struct

message: "Serializing a structure with the incorrect number of fields."

languages: [rust]

severity: ERROR

patterns:

- pattern-either:

- pattern: |

let $X = $S.serialize_struct($NAME, $T)?;

...

$X.end()

- pattern-not: |

let $X = $S.serialize_struct($NAME, 1)?;

...

$X.serialize_field(...);

...

$X.end()

- pattern-not: |

let $X = $S.serialize_struct($NAME, 2)?;

...

$X.serialize_field(...);

...

$X.serialize_field(...);

...

$X.end()

30 of 35

rules:

- id: incorrect-serialize-struct

message: "Serializing a structure with the incorrect number of fields."

languages: [rust]

severity: ERROR

patterns:

- pattern-either:

- pattern: |

let $X = $S.serialize_struct($NAME, $T)?;

...

$X.end()

- pattern-not: |

let $X = $S.serialize_struct($NAME, 1)?;

...

$X.serialize_field(...);

...

$X.end()

- pattern-not: |

let $X = $S.serialize_struct($NAME, 2)?;

...

$X.serialize_field(...);

...

$X.serialize_field(...);

...

$X.end()

31 of 35

rules:

- id: incorrect-serialize-struct

message: "Serializing a structure with the incorrect number of fields."

languages: [rust]

severity: ERROR

patterns:

- pattern-either:

- pattern: |

let $X = $S.serialize_struct($NAME, $T)?;

...

$X.end()

- pattern-not: |

let $X = $S.serialize_struct($NAME, 1)?;

...

$X.serialize_field(...);

...

$X.end()

- pattern-not: |

let $X = $S.serialize_struct($NAME, 2)?;

...

$X.serialize_field(...);

...

$X.serialize_field(...);

...

$X.end()

32 of 35

Who use Clippy?

33 of 35

Who heard about Dylint?

34 of 35

Dylint

35 of 35

Dylint