rust for linux

115
Rust for Linux Miguel Ojeda [email protected]

Upload: others

Post on 13-May-2022

4 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Rust for Linux

Rust for Linux

Miguel [email protected]

Page 2: Rust for Linux

Credits & Acknowledgments

Rust...for being a breath of fresh air

Kernel maintainers...for being open-minded

Everyone that has helped Rust for Linux(see credits in the RFC & patch series)

Page 3: Rust for Linux

History

30 years of Linux 30 years of ISO C

Page 4: Rust for Linux

Love story*

30 years of Linux

❤*

* Terms and Conditions Apply.

30 years of ISO C

Page 5: Rust for Linux

An easy task?

Page 6: Rust for Linux

An easy task?

“Do you see any language except C which is

suitable for development of operating systems?”

Page 7: Rust for Linux

An easy task?

“I like interacting with hardware from a software perspective.

And I have yet to see a language that comes even close to C.”— Linus Torvalds 2012

“Do you see any language except C which is

suitable for development of operating systems?”

Page 8: Rust for Linux

Why is C a good language for the kernel?

“When I read C, I know what the assembly language will look like.”

“If you think like a computer, writing C actually makes sense.”

“The people that designed C ... designed it at a time when compilers had to be simple.”

“You can use C to generate good code for hardware.” Fast

Low-level

Simple

Fits the domain

Page 9: Rust for Linux

But...

Page 10: Rust for Linux

But...

UB

Page 11: Rust for Linux

So, what does Rust offer?

Page 12: Rust for Linux

So, what does Rust offer?

UB🏖

Page 13: Rust for Linux

Safety

Safety in Rust

=No undefined behavior

Page 14: Rust for Linux

Safety

Safety in Rust

≠Safety in “safety-critical”

as in functional safety (DO-178B/C, ISO 26262, EN 50128…)

Page 15: Rust for Linux

Is avoiding UB that important?

Page 16: Rust for Linux

Is avoiding UB that important?

~70%of vulnerabilities in C/C++ projects come from UB

See more at https://www.memorysafety.org/docs/memory-safety/

Page 17: Rust for Linux

Sure, UB is an issue and safe Rust does not have it…

Page 18: Rust for Linux

Sure, UB is an issue and safe Rust does not have it…

...does Rust really help, though?

Page 19: Rust for Linux

Does Rust help?

Derived using data from https://adalogics.com/blog/fuzzing-100-open-source-projects-with-oss-fuzz

Page 20: Rust for Linux

What else does Rust offer?

Language

Page 21: Rust for Linux

What else does Rust offer?

LanguageStricter type system

Safe/unsafe split Sum types

Pattern matching

Generics

RAII

Lifetimes

Shared & exclusive references

Modules & visibility

Powerful hygienic and procedural macros

Page 22: Rust for Linux

What else does Rust offer?

Freestanding standard library

Page 23: Rust for Linux

What else does Rust offer?

Freestanding standard library

Vocabulary types like Result and Option

Iterators

FormattingPinning

Checked, saturating & wrapping integer arithmetic primitives

Page 24: Rust for Linux

Tooling

What else does Rust offer?

Page 25: Rust for Linux

Tooling

Great compiler error messages

What else does Rust offer?

Documentation generator

Formatter

Linter

Unit & integration tests

UBSAN-like interpreter

Static analyzer

Macro debugging

IDE tooling

C ↔ Rust bindings generators

Page 26: Rust for Linux

Tooling

Great compiler error messages

What else does Rust offer?

Documentation generator

Formatter

Linter

Unit & integration tests

plus the usual friends: gdb, lldb, perf, valgrind...UBSAN-like interpreter

Static analyzer

Macro debugging

IDE tooling

C ↔ Rust bindings generators

Page 27: Rust for Linux

What is the catch?

Page 28: Rust for Linux

What is the catch?

Cannot model everything ⇒ Unsafe code required

Page 29: Rust for Linux

What is the catch?

Cannot model everything ⇒ Unsafe code required

More information to provide ⇒ More complex language

Page 30: Rust for Linux

What is the catch?

Cannot model everything ⇒ Unsafe code required

More information to provide ⇒ More complex language

Extra runtime checks ⇒ Potentially expensive

Page 31: Rust for Linux

What is the catch?

Cannot model everything ⇒ Unsafe code required

More information to provide ⇒ More complex language

Extra runtime checks ⇒ Potentially expensive

An extra language to learn ⇒ Logistics & maintenance burden

Page 32: Rust for Linux

Why is C a good language for the kernel?

“When I read C, I know what the assembly language will look like.”

“If you think like a computer, writing C actually makes sense.”

“The people that designed C ... designed it at a time when compilers had to be simple.”

“You can use C to generate good code for hardware.” Fast

Low-level

Simple

Fits the domain

Page 33: Rust for Linux

Why is C a good language for the kernel?Rust

Sometimes

Yes

Not really

...

“When I read C, I know what the assembly language will look like.”

“If you think like a computer, writing C actually makes sense.”

“The people that designed C ... designed it at a time when compilers had to be simple.”

“You can use C to generate good code for hardware.” Fast

Low-level

Simple

Fits the domain

Page 34: Rust for Linux

An easy task?

“I like interacting with hardware from a software perspective.

And I have yet to see a language that comes even close to C.”— Linus Torvalds 2012

“Do you see any language except C which is

suitable for development of operating systems?”

Page 35: Rust for Linux

An easy task?

“I like interacting with hardware from a software perspective.

And I have yet to see a language that comes even close to C.”— Linus Torvalds 2012

“Do you see any language except C which is

suitable for development of operating systems?”

maybe?

Page 36: Rust for Linux

Some examples where Rust helps

Page 37: Rust for Linux
Page 38: Rust for Linux
Page 39: Rust for Linux
Page 40: Rust for Linux
Page 41: Rust for Linux

Building an abstraction

Page 42: Rust for Linux
Page 43: Rust for Linux
Page 44: Rust for Linux
Page 45: Rust for Linux
Page 46: Rust for Linux

Rust support in the kernel

Page 47: Rust for Linux

rust/library/

builtinscrate

macroscrate

alloccrate

kernelcrate

alloccrate

corecrate

exports helpers

include/

Module

bindgen

bindingscrate

Rust tree Linux tree

Page 48: Rust for Linux

Driver point of view

Page 49: Rust for Linux

drivers/

my_foodriver

include/

bindgen

bindingscrate

kernelcrate

foo subsystem

bar subsystem

foo/

Forbidden!

Safe

Abstractions

Unsafe

Linux tree

...

Page 50: Rust for Linux

Supported architectures

arm (armv6 only)

arm64

powerpc (ppc64le only)

riscv (riscv64 only)

x86 (x86_64 only)

See Documentation/rust/arch-support.rst

Page 51: Rust for Linux

Supported architectures

arm (armv6 only)

arm64

powerpc (ppc64le only)

riscv (riscv64 only)

x86 (x86_64 only)

See Documentation/rust/arch-support.rst

...so far!32-bit and other restrictions should be easy to remove

Kernel LLVM builds work for mips and s390

GCC codegen paths should open up more

Page 52: Rust for Linux

Rust codegen paths for the kernel

rustc_codegen_llvm Rust GCCrustc_codegen_gcc

Main oneAlready passes

most rustc testsExpected in 1-2 years

(rough estimate)

Page 53: Rust for Linux

Documentation

Page 54: Rust for Linux
Page 55: Rust for Linux
Page 56: Rust for Linux
Page 57: Rust for Linux
Page 58: Rust for Linux

Documentation code

Page 59: Rust for Linux

/// Wraps the kernel's `struct task_struct`.////// # Invariants////// The pointer `Task::ptr` is non-null and valid. Its reference count is also non-zero.////// # Examples////// The following is an example of getting the PID of the current thread with/// zero additional cost when compared to the C version:////// ```/// # use kernel::prelude::*;/// use kernel::task::Task;////// # fn test() {/// Task::current().pid();/// # }/// ```pub struct Task { pub(crate) ptr: *mut bindings::task_struct,}

Page 60: Rust for Linux

Rust code has access to conditional compilation based on the kernel config

Conditional compilation

#[cfg(CONFIG_X)] // `CONFIG_X` is enabled (`y` or `m`)#[cfg(CONFIG_X="y")] // `CONFIG_X` is enabled as a built-in (`y`)#[cfg(CONFIG_X="m")] // `CONFIG_X` is enabled as a module (`m`)#[cfg(not(CONFIG_X))] // `CONFIG_X` is disabled

Page 61: Rust for Linux

Coding guidelines

No direct access to C bindings Rust 2018 edition & idioms

No undocumented public APIs No unneeded panics

No implicit unsafe block No infallible allocations

Docs follows Rust standard library style ...

// SAFETY proofs for all unsafe blocks

Clippy linting enabled

Automatic formatting enforced

Page 62: Rust for Linux

Coding guidelines

No direct access to C bindings Rust 2018 edition & idioms

No undocumented public APIs No unneeded panics

No implicit unsafe block No infallible allocations

Docs follows Rust standard library style ...

// SAFETY proofs for all unsafe blocks

Clippy linting enabled

Automatic formatting enforcedAiming to be as strict as possible

Page 63: Rust for Linux

Abstractions code

Page 64: Rust for Linux

/// Wraps the kernel's `struct file`.////// # Invariants////// The pointer `File::ptr` is non-null and valid./// Its reference count is also non-zero.pub struct File { pub(crate) ptr: *mut bindings::file,}

Page 65: Rust for Linux

impl File { /// Constructs a new [`struct file`] wrapper from a file descriptor. /// /// The file descriptor belongs to the current process. pub fn from_fd(fd: u32) -> Result<Self> { // SAFETY: FFI call, there are no requirements on `fd`. let ptr = unsafe { bindings::fget(fd) }; if ptr.is_null() { return Err(Error::EBADF); }

// INVARIANTS: We checked that `ptr` is non-null, so it is valid. // `fget` increments the ref count before returning. Ok(Self { ptr }) }

// ...}

Page 66: Rust for Linux

Driver code

Page 67: Rust for Linux

static int pl061_resume(struct device *dev){ int offset;

struct pl061 *pl061 = dev_get_drvdata(dev);

for (offset = 0; offset < PL061_GPIO_NR; offset++) { if (pl061->csave_regs.gpio_dir & (BIT(offset))) pl061_direction_output(&pl061->gc, offset, pl061->csave_regs.gpio_data & (BIT(offset))); else pl061_direction_input(&pl061->gc, offset);

}

writeb(pl061->csave_regs.gpio_is, pl061->base + GPIOIS); writeb(pl061->csave_regs.gpio_ibe, pl061->base + GPIOIBE); writeb(pl061->csave_regs.gpio_iev, pl061->base + GPIOIEV); writeb(pl061->csave_regs.gpio_ie, pl061->base + GPIOIE);

return 0;}

fn resume(data: &Ref<DeviceData>) -> Result {

let inner = data.lock(); let pl061 = data.resources().ok_or(Error::ENXIO)?;

for offset in 0..PL061_GPIO_NR { if inner.csave_regs.gpio_dir & bit(offset) != 0 { let v = inner.csave_regs.gpio_data & bit(offset) != 0; let _ = <Self as gpio::Chip>::direction_output( data, offset.into(), v); } else { let _ = <Self as gpio::Chip>::direction_input( data, offset.into()); } }

pl061.base.writeb(inner.csave_regs.gpio_is, GPIOIS); pl061.base.writeb(inner.csave_regs.gpio_ibe, GPIOIBE); pl061.base.writeb(inner.csave_regs.gpio_iev, GPIOIEV); pl061.base.writeb(inner.csave_regs.gpio_ie, GPIOIE);

Ok(())}

— https://lwn.net/Articles/863459/

Page 68: Rust for Linux

Testing code

Page 69: Rust for Linux

fn trim_whitespace(mut data: &[u8]) -> &[u8] { // ...}

#[cfg(test)]mod tests { use super::*;

#[test] fn test_trim_whitespace() { assert_eq!(trim_whitespace(b"foo "), b"foo"); assert_eq!(trim_whitespace(b" foo"), b"foo"); assert_eq!(trim_whitespace(b" foo "), b"foo"); }}

Page 70: Rust for Linux

/// Getting the current task and storing it in some struct. The reference count is automatically/// incremented when creating `State` and decremented when it is dropped:////// ```/// # use kernel::prelude::*;/// use kernel::task::Task;////// struct State {/// creator: Task,/// index: u32,/// }////// impl State {/// fn new() -> Self {/// Self {/// creator: Task::current().clone(),/// index: 0,/// }/// }/// }/// ```

Page 71: Rust for Linux

How to proceed?

Page 72: Rust for Linux

https://github.com/Rust-for-Linux/linux

Page 73: Rust for Linux

Rust for Linux

Miguel [email protected]

Page 74: Rust for Linux

Backup slides

Page 75: Rust for Linux

C Charter

— N2086 C2x Charter - Original Principles

— N2086 C2x Charter - Additional Principles for C11

Page 76: Rust for Linux
Page 77: Rust for Linux

Undefined Behavior

— N2596 C2x Working Draft

Page 78: Rust for Linux

Example of UB

int f(int a, int b) { return a / b;}

Page 79: Rust for Linux

Example of UB

int f(int a, int b) { return a / b;}

UB ∀x f(x, 0);

Page 80: Rust for Linux

Example of UB

Any other inputs that trigger UB?

int f(int a, int b) { return a / b;}

Page 81: Rust for Linux

Example of UB

UB f(INT_MIN, -1);

Any other inputs that trigger UB?

int f(int a, int b) { return a / b;}

Page 82: Rust for Linux

Instances of UB

Page 83: Rust for Linux

Instances of UB

Page 84: Rust for Linux

Instances of UB

Page 85: Rust for Linux

Instances of UB

Page 86: Rust for Linux

Instances of UB

Page 87: Rust for Linux

Instances of UB

Page 88: Rust for Linux

Instances of UB

Page 89: Rust for Linux

Instances of UB

Page 90: Rust for Linux

Instances of UB

Page 91: Rust for Linux

Avoiding UB

int f(int a, int b) { if (b == 0) abort();

if (a == INT_MIN && b == -1) abort();

return a / b;}

Page 92: Rust for Linux

Avoiding UB

f is a safe function

int f(int a, int b) { if (b == 0) abort();

if (a == INT_MIN && b == -1) abort();

return a / b;}

Page 93: Rust for Linux

Safe function

f is a safe function

int f(int a, int b) [[safe]] { if (b == 0) abort();

if (a == INT_MIN && b == -1) abort();

return a / b;}

Not C

Page 94: Rust for Linux

Safe function

f is a safe function

int f(int a, int b) [[safe]] { if (b == 0) abort();

if (a == INT_MIN && b == -1) abort();

return a / b;}

(yet? N2659)Not C

Page 95: Rust for Linux

Safety examples

abort()s in C

areRust-safe

Page 96: Rust for Linux

Safety examples

abort()s in C

areRust-safe

Even if your company goes bankrupt.

Page 97: Rust for Linux

Safety examples

abort()s in C

areRust-safe

Even if your company goes bankrupt.

Even if somebody is injured.

Page 98: Rust for Linux

Safety examples

Rust panics

areRust-safe

Page 99: Rust for Linux

Safety examples

Kernel panics

areRust-safe

Page 100: Rust for Linux

Safety examples

Uses after free, null derefs, double frees,

OOB accesses, uninitialized memory reads,

invalid inhabitants, data races...

are notRust-safe

Page 101: Rust for Linux

Safety examples

Uses after free, null derefs, double frees,

OOB accesses, uninitialized memory reads,

invalid inhabitants, data races...

are notRust-safe

Even if your system still works.

Page 102: Rust for Linux

Safety examples

Race conditions

areRust-safe

Page 103: Rust for Linux

Safety examples

Memory leaks

areRust-safe

Page 104: Rust for Linux

Safety examples

Deadlocks

areRust-safe

Page 105: Rust for Linux

Safety examples

Integer overflows

areRust-safe

Page 106: Rust for Linux

Is avoiding UB that important?

— https://msrc-blog.microsoft.com/2019/07/18/we-need-a-safer-systems-programming-language/

Page 107: Rust for Linux

Is avoiding UB that important?

— https://langui.sh/2019/07/23/apple-memory-safety/

Page 108: Rust for Linux

Is avoiding UB that important?

— https://www.chromium.org/Home/chromium-security/memory-safety

Page 109: Rust for Linux

Is avoiding UB that important?

— https://security.googleblog.com/2019/05/queue-hardening-enhancements.html

Page 110: Rust for Linux

Is avoiding UB that important?

Page 111: Rust for Linux

Does Rust help?

Page 112: Rust for Linux

I took a look at this spreadsheet published a couple weeks ago...

Does Rust help?

Page 113: Rust for Linux

I took a look at this spreadsheet published a couple weeks ago...

— https://adalogics.com/blog/fuzzing-100-open-source-projects-with-oss-fuzz

Does Rust help?

Page 114: Rust for Linux

I filled the language column and plotted...

Does Rust help?

Page 115: Rust for Linux

I filled the language column and plotted...

Does Rust help?