News for hackers. Stuff that matters.
Re:Is Rust Used Safely by Software Developers? by blippage on Friday July 16, 2021 @04:24PM (#27860821)
I'm not a Rust programmer, so be gentle with me.

What worries me is that when it comes to safety, surely the chain is as strong as the weakest link? You declare one thing unsafe, and then suddenly all bets are off. And maybe you don't even know that what you're doing is unsafe.

But here's an example I pulled from github, which blinks a Raspberry Pi Pico:

    // Set GPIO25 to be an output (output enable is set)
    p.SIO.gpio_oe_set.write(|w| unsafe {
        w.bits(1 << 25);
        w
    });
Right, but what if you're fiddling with registers that might require atomic operations? Lines such as this seem to be saying "ignore all that, write to the register anyway."

Fearless concurrency, you say: are you sure? How do you know?

Suppose you have an ISR (interrupt service routine) that wants to toggle a GPIO pin. From whence does it borrow the privilege to set the pin?

Surely, at the end of the day, microcontrollers can be characterised as large state machines with global state. That's what they ARE. It's their inherent nature, which you cannot abstract away without it leaking somewhere (at least to do anything useful, anyway).

At least with C/C++ you KNOW you've got a problem, and have to reason about the problem carefully. With Rust, you THINK you've solved the problem, but have you?

"Change My View", as they say on Reddit.

*Update 1* OK, I'm going to press my point on this. I'm looking at the Embedded Rust Book (please remember, I'm not a Rust programmer) at https://docs.rust-embedded.org/book/concurrency/index.html

I'm considering the issue of interrupts here.

" Instead, our interrupt handlers might be called at any time and must know how to access whatever shared memory we are using. At the lowest level, this means we must have statically allocated mutable memory, which both the interrupt handler and the main code can refer to."

"In Rust, such static mut variables are always unsafe to read or write, because without taking special care, you might trigger a race condition, where your access to the variable is interrupted halfway through by an interrupt which also accesses that variable."

RIGHT, but that means that Rust gives me no better assurances than C/C++. Here's their code:

    static mut COUNTER: u32 = 0;
    
    #[entry]
    fn main() -> ! {
        set_timer_1hz();
        let mut last_state = false;
        loop {
            let state = read_signal_level();
            if state && !last_state {
                // DANGER - Not actually safe! Could cause data races.
                unsafe { COUNTER += 1 };
            }
            last_state = state;
        }
    }
    
    #[interrupt]
    fn timer() {
        unsafe { COUNTER = 0; }
    }
Then is says "Can you spot the race condition? The increment on COUNTER is not guaranteed to be atomic"

It then solves the problem by showing how to introduce a critical section.

So at the end of the day it's just C with different syntax, and a lot more hoopla. You've made everything mutable and unsafe. Exactly the kind of thing you were trying to avoid in the first place.

Re:Is Rust Used Safely by Software Developers? by woodruffw on Friday July 16, 2021 @04:28PM (#27860862)
> At least with C/C++ you KNOW you've got a problem, and have to reason about the problem carefully. With Rust, you THINK you've solved the problem, but have you?

Having written C and C++ for about a decade and Rust for about 2 years now, I don't think this is accurate: people don't reason any more carefully about C and C++ than other languages, and neither language (even in their safer subsets) encourages methodical safety thinking.

This is in direct contrast to Rust's absolute requirement that you annotate anything that isn't already safe by construction with an `unsafe` keyword. You might be wrong about the safety of whatever you're constructing, but that alone is a significantly more methodical and careful approach to encapsulating the "weak" parts of programs.

Re:Is Rust Used Safely by Software Developers? by SlySherZ on Friday July 16, 2021 @04:52PM (#27861073)
Alright, I'll try.

Last time you used an higher level interpreted language (like JS), did you have to worry whether or not the machine instructions it was generating were the right ones? Generating instructions at runtime sounds pretty unsafe, no?

You didn't, right? You can just mostly assume the language works and get on with your life. That's how unsafe feels to me when I'm programming in Rust, I never have to worry about it and pretty much never need to use it.

Sometimes you'll run into a problem where you need to use unsafe, but that only happens very very rarely, for example when creating an interface to a C library, but most people will never need that. When that happens, sure, you'll have to be extra careful and make sure everything works. But after you're done with that small piece, you're back into safe Rust.

So, how does it work in practice, if you really need to use unsafe? Suppose you have a C enum with values MyEnum {A, B, C} (but it can be any int in C!), and you want to use it in Rust. You can make an unsafe wrapper that tries to parse the enum, and it can either return None - if it was an invalid integer, or return Some(A), Some(B), Some(C).

But once you get the wrapper right (which is not hard), Rust will guarantee that values of type MyEnum have values A, B or C and nothing else, so you never need to worry about the exceptional case again.

You worry about it once at the boundary and that's it.

Re:Is Rust Used Safely by Software Developers? by megous on Friday July 16, 2021 @05:17PM (#27861336)
Issues with ISRs are different. They'll sorta appear when rust is used inside the Linux kernel, too.

For example, can rust make sure some kind of function is not called at any level when called from certain kind of context (say atomic context).

Can rust help detect/prevent you from accessing some multi-register SFRs or memory locations from main context without making you disable interrupts first in the main context?

And this constraint is only relevant if the SFR or said memory location is ever accessed from some ISR, and the access is not atomic, otherwise disabling interrupts is not necessary.

Do you have to wrap all these accesses and keep track of all this manually just like in C or does rust have some concept of "colored" functions, so that you're forced to wrap accesses to certain memory locations only if these accesses are shared between differently colored functions.

I'm not a rust person either. Just wondering how rust would help me with the most complicated aspects of concurrency in low level programming. Allocating/deallocating memory and keeping track of it is easy compared to this.

Re:Is Rust Used Safely by Software Developers? by cesarb on Friday July 16, 2021 @06:27PM (#27861926)
What you want sounds similar to Rust's Mutex design pattern. In most languages, there's no link between a mutex and the resources it's protecting, but in the Rust standard library, a Mutex<T> contains the resources (the T), so that the only way to get access to these resources is to lock the mutex, and the borrow checker will not allow accessing them after the mutex is unlocked again.

In your case, you could implement something like an InterruptDisabler<T>, and use it to wrap an object which has the methods to access these registers. When you wanted to access these registers, you would do something like "my_interrupt_disabler.disable_interrupts()", which would disable interrupts and return an InterruptDisablerGuard<T>, which then would you to access the T. Once that InterruptDisablerGuard<T> gets out of scope, the compiler automatically calls the drop() method from the Drop trait on it, and that method could enable interrupts again. You cannot access the inner object without going through the guard, and you cannot get the guard without disabling interrupts.

And that's only one possible implementation. Another one would be to have something like an "interrupts disabled" token which is returned by a "disable interrupts" function, which is consumed (that is, destroyed) by an "enable interrupts" function, and which is !Send and !Sync (so it cannot be passed to another thread). The methods which access these registers would require you to give them a reference to the token, so they could only be called if you somehow obtained the token, which could only be done by disabling interrupts in the current thread. And for when you're within the interrupt handler itself, the low-level code which calls it could manufacture one of these tokens and pass a reference to it to the interrupt handler, so it would be able to access the registers without calling the "disable interrupts" function.

Of course, both of these ideas still require you to decide which registers are safe to call with interrupts enabled, and which registers are not, when creating the objects which represent the register blocks. Rust can help the developer, but it's not a panacea.

Re:Is Rust Used Safely by Software Developers? by megous on Saturday July 17, 2021 @05:54PM (#27869139)
It's not about which registers are safe, but about whether the access patterns compiler generates for register access are safe. Say `andl some_sfr,#0xfe` is safe for all SFRs, but `mov r1,some_sfr ; andl r1,#0xfe ; mov some_sfr,r1` is not (not atomic, interrupt can happen in the middle).

Say `some_sfr = 1` is safe but `some_sfr = (some_sfr & 0xf0) | 0x3` is not.

Token thing seems interesting, but generally, no code calls the interrupt handler, HW does that by itself (outside of the control of the language), so there would be no way to pass the token to the handler, it would have to be manufactured in the handler out of thin air (without disabling interrupts, because those are disabled automatically by HW). And I'd have to wrap all the non-atomic language constructs in some functions that would require the token, to have some enforced safety.

I guess the fearless concurrency thing is only for cases where the language controls the creation of threads.

Re:Is Rust Used Safely by Software Developers? by cakoose on Friday July 16, 2021 @06:49PM (#27862057)
I think the difference is the all-or-nothing mentality. Nothing is 100% trustworthy.

Even if you write "unsafe"-free Rust that only relies on the Rust standard library, you need to trust:

- The Rust compiler and everything it uses to be correct (not just memory safe), e.g. LLVM.

- The Rust standard library, and everything it calls in to, e.g. system libraries, the kernel, the CPU.

That's a ton of code. By calling into an additional library that uses the "unsafe" keyword, you're just adding that library to your set of things you need to trust.

Naturally, you probably have higher trust in the Rust compiler and OS kernel then you do in some Rust library on GitHub, but nothing is 100%.

And the GitHub Rust library probably only uses a few small blocks of "unsafe" code. That makes it easier to audit and increase your confidence than if it were written in C.

Plus, the Rust safety guarantees don't cover everything. You're still trusting that the library doesn't have a bug that accidentally writes to the wrong file, or sends the wrong value over the network.

C++ defines a memory model, allowing programmers to "rely" on certain guarantees from the compiler and runtime library. But you're still trusting the C++ compiler, the runtime library, the OS kernel, and the CPU to work the way they're supposed to. That doesn't mean the C++ memory model is useless.

Re:Is Rust Used Safely by Software Developers? by tialaramex on Friday July 16, 2021 @06:39PM (#27861999)
Looking at your "blink an LED" example this appears to end up in some code from svd2rust and in that code what's going on here is the write takes a closure which modifies the bits to be written (starting from the hardware default).

The unsafe thing being done here is calling w.bits() which is a method on w (the bits we're going to write to this register in the hardware) that allows the caller to set it to any value of their choosing, even values that nobody said are OK for this hardware.

There are two reasons why you might do this. Maybe your hardware was too lazy to provide safe ways to set every value that could be OK. This seems entirely plausible for the Raspberry Pi. Or, maybe you are crazy and you're writing values the hardware maker said never to use. Either way, Rust doesn't know anything about how the Raspberry Pi GPIO unit works and what bit values are OK.

This unsafe code does not write bits to registers. This isn't bypassing some imaginary safeguard that prevents touching stuff simultaneous to an ISR. It's critical to understand that.

The surrounding safe code performs the register write, as an atomic operation (AIUI a single hardware instruction on the Raspberry Pi) using the result of the closure.

> I'm not a Rust programmer, so be gentle with me.

> So at the end of the day it's just C with different syntax, and a lot more hoopla.

So what seems to have happened here is that you're completely ignorant of the topic and you've mistaken that for expertise. That's a bad habit to have developed, and it's important to break it.

Re:Is Rust Used Safely by Software Developers? by randomskk on Friday July 16, 2021 @06:42PM (#27862016)
That Raspberry Pi Pico code snippet is slightly unusual in that the `unsafe` there is because the library isn't sure that 1<<25 is a safe value to write to this register - for example, if the register was a DMA memory address pointer, it's not safe to allow directly writing arbitrary pointers to it since it would allow memory access outside of Rust's memory model. It's not to do with the potential atomicity requirement.

The exclusivity/atomicity in that case is ensured because the method is called on "p.SIO", an object that can't be accessed from both ISRs and the main code at the same time in safe Rust (because it doesn't "implement Sync"). If both an interrupt and the main thread need to access that peripheral, you need to provide some way of sharing it - either using `unsafe`, or in safe code by using a synchronisation primitive such as a critical-section based Mutex that provides that guarantee.

The book chapter you've linked to starts out by demonstrating what is essentially how you'd write this in C and thus requires unsafe, but it builds towards a safe solution - when using either the Atomic* variables (in the Atomic Access section) or mutexes (in the Mutex section), `unsafe` isn't required any more; the user's code is only safe Rust which provides synchronised access to the shared state between the main thread and the ISR.

In other words, the benefit over C is that it _is_ now possible to use only safe Rust to access memory and peripherals from both interrupts and the main thread, and that safe Rust is itself ensuring you can't cause race conditions. The unsafe option is there as a building block for those safe abstractions.

> So at the end of the day it's just C with different syntax, and a lot more hoopla. You've made everything mutable and unsafe.

Perhaps that chapter isn't getting the right message across then. The goal is to completely avoid applicaton code having to make things mutable and unsafe by providing the right abstractions that allow safe Rust to get the same work done while ensuring there are no races.

Re:Is Rust Used Safely by Software Developers? by conradludgate on Friday July 16, 2021 @05:12PM (#27861279)
I think most rust programmers are aware enough to know that it's not actually 100% memory safe. But it's memory safe-r. I don't actually know anyone who's done even a little bit of rust actually believe it's 100% safe

The stdlib is full of unsafe, I'd much rather they handle the unsafe parts which have been audited and battle tested and I use their abstractions.

It's almost impossible to avoid unsafe somewhere in the chain. But that's not really unexpected. What's nice is that once you have a safe wrapper, it's impossible to misuse and cause memory errors

Re:Is Rust Used Safely by Software Developers? by qw3rty01 on Friday July 16, 2021 @06:53PM (#27862084)
In Rust, the only way to trigger undefined behavior is the presence of unsafe code. So everything outside of manipulating COUNTER in your code block cannot trigger undefined behavior. In C, there aren't any of those guarantees, so any line in the code could potentially trigger undefined behavior. Since we know that only the block manipulating COUNTER can cause undefined behavior to occur, ensuring that the block manipulating COUNTER can not cause undefined behavior to occur is the only place where heavy scrutiny is needed...as opposed to C, where every function call and line of code would need to be scrutinized.

Sure you could throw unsafe everywhere and not check anything, but that misses a major reason to use rust--safe abstractions over unsafe behavior. Once a safe abstraction exists for unsafe behavior, all the heavy lifting of guaranteeing code will not result in undefined behavior is offloaded to the compiler, instead of the programmer having to keep track of it all.

Re:Is Rust Used Safely by Software Developers? by correct_horse on Friday July 16, 2021 @06:06PM (#27861772)
I like rust, but don't use it for embedded. Here's a blog I read on how to make GPIO access safe https://www.ecorax.net/macro-bunker-2/.

In that article, they have three goals: verify that the pin is correctly configured, use atomic reads and writes, make sure only one thread (including interrupts) can write to a pin at a time.

Spoiler alert, the way they avoid interrupts screwing everything up is with `cortex_m::interrupt::free(|_| {/*read/write pin*/})`, which executes the passed closure in an interrupt-free context. The author's solution isn't atomic, but the borrow checker and the fact that they target single threaded MCU means no one else can be writing to the port at the same time.

Re:Is Rust Used Safely by Software Developers? by uwaces2 on Friday July 16, 2021 @07:32PM (#27862351)
> What worries me is that when it comes to safety, surely the chain is as strong as the weakest link? You declare one thing unsafe, and then suddenly, all bets are off. And maybe you don't even know that what you're doing is unsafe.

Yes, you are correct. If some code or module contains a memory safety or data race bug, then the code which uses it could realize that bug in the form of a crash or race. This paper is not analyzing code that contains a memory safety or data race bug, but instead, code that the Rust compiler cannot prove doesn't have such a bug. That is a big difference.

Let's take your example. The questions you bring up are good ones. Fiddling with registers may indeed require atomic in the presence of multiple threads using that register. Or, if you can guarantee only one thread touches those registers, it may not. In both use cases, I'd like the compiler to help me verify that those conditions are met. A Rust approach to this problem (verified empirically by the paper) would be to create a module, function, or trait that enforces the invariants we need. Such an abstraction might contain a bug that causes the codebase to be unsafe. But when debugging that, we shouldn't have to look over the whole codebase for register writes but rather in just one place.

> Suppose you have an ISR (interrupt service routine) that wants to toggle a GPIO pin. From whence does it borrow the privilege to set the pin?

Assuming we are protecting the pin with atomics, the ISR could call into our library to acquire the lock. The ISR doesn't necessarily need to borrow the privilege from another thread. Our module could provide a safe interface for unsafe access to a global lock.

I would also ask how in C/C++ an ISR performing such an operation would work without data races. I assume it would need to perform a very similar set of operations to the compiled Rust binary. The only difference is that it is harder to find that gpio pin manipulation in the C code. That isn't an accident Rust's explicit goal is for its abstractions to be zero-cost.

> Surely, at the end of the day, microcontrollers can be characterized as large state machines with global state. That's what they ARE. It's their inherent nature, which you cannot abstract away without it leaking somewhere (at least to do anything useful, anyway).

First, this can be said about any computer, no matter how small or large. But I think that you are getting at something important here. Rust is a much more natural fit in user space on an operating system that abstracts away the hardware details than in a micro-controller. And certainly, on a microcontroller, you have to interact often with IO of various kinds, which are global state that Rust considers unsafe. But I think, to the extent, it is possible having the unsafe/safe stratification of code enables you to separate the code which doesn't interact with the outside world and the code which does. I think that is abstractly similar to the user/kernel space divide and Haskell's IO/computation divide. I believe that split helps with code organization and concentrates the attention when such a bug occurs.

> At least with C/C++ you KNOW you've got a problem, and have to reason about the problem carefully.

First, you should know that Rust's safety guarantees extend only to memory safety and data races. Second, I don't think that it is true, in C/C++, that if you have a memory safety or data race bug, you necessarily know that you have one. If you do have one, it could be in any part of the code. Reasoning about a large codebase is much more difficult than a single module with all potentially unsafe code.

> With Rust, you THINK you've solved the problem, but have you?

You may well not have. Here is an example of Rust's core libraries containing a CVE [1]. And here is another popular crate that had a CVE relating to unsafe code [2].

The paper noted the unsafe code was slightly more prevalent amongst the most popular repos. At first, that may seem like a bad thing, but I take this as a good sign because it means the unsafe code is more concentrated in few crates. And as a result of that, more attention can be placed on making sure those crates are correct. If they are safe, then the Rust compiler can ensure that client code is safe. Compare that to C, where if you decide to use someone else's code, you and you alone are responsible for ensuring you don't break the invariants they expected of you. The borrow checking Rust does to verify the safety property is a form of automatic verification that is good news for everyone. We know empirically that 70% of all security vulnerabilities are related to memory safety [3][4]. And that is not due to a lack of talented programmers (those stats come from Google and Microsoft). I would want the compiler to ensure most of my code doesn't have these issues. Rust enables the compiler to do that, whereas C/C++ stops a compiler from doing that at the same level. And so we must fall back on tools like ASAN, which, while good, don't happen early enough in the code-debug loop and have proven (see previous evidence) not to solve the problem well enough.

There is another part to this story that I don't expect most or even a significant fraction of Rust programmers to be proficient in but which we all can benefit from, formal verification. There is a substantial academic (and to a lesser extent industry) driven push to create formal verification tools [5]. I've seen a bug report on the standard library that a graduate student found while verifying a module that used unsafe code. That bug report was a direct benefit to all the users of that module. In an equivalent C world, the code users would need whole program formal verification actually to ensure they got a similar benefit. With Rust, the compiler is doing that heavy lifting for us.

The paper notes that unsafe rules are still provisional (meaning only that they are subject to change, not incomplete), but there is a goal to formalize those rules. Standardizing those rules enables those who write unsafe code to prove (not just test) that their module/function/trait properly encapsulates the unsafe code in such a way that it cannot cause memory safety or data race bugs. If you can do that, you can KNOW that you don't have a problem anywhere in the code. Now, of course, the standard disclaimers around formal verification apply: most people won't do this. It requires specialized skill and knowledge, and so on. But there are limited cases where it would confer a real benefit, the Rust standard library, being the most widely used, is perhaps the prime example.

I'd also like to note that Rust raises the bar in many other ways you may not be aware of. Integer manipulation is a good example. Rust will throw a checked runtime error in debug builds when there is a numerical overflow or underflow. If that is not the behavior, you expected you could use a compiler intrinsic made available in the standard library to explicitly allow and specify the exact behavior you expected in the event of an overflow [6]. That's important for the correctness of integer manipulation and memory safety if a computed index contains an unexpected integer overflow. There are other examples of Rust raising the bar above C, including hygienic macros, strings, and algebraic data types [7][8]. And it does that without compromising on performance and sometimes enabling more performant patterns, which would be possible but not advisable to attempt in C [9].

[1] https://cve.mitre.org/cgi-bin/cvename.cgi?name=%20CVE-2018-1... [2] https://shnatsel.medium.com/auditing-popular-rust-crates-how... [3] https://www.zdnet.com/article/chrome-70-of-all-security-bugs... [4] https://msrc-blog.microsoft.com/2019/07/16/a-proactive-appro... [5] https://alastairreid.github.io/rust-verification-tools/ [6] https://doc.rust-lang.org/std/primitive.i64.html [7] http://dtrace.org/blogs/bmc/2018/09/18/falling-in-love-with-... [8] http://dtrace.org/blogs/bmc/2020/10/11/rust-after-the-honeym... [9] http://dtrace.org/blogs/bmc/2018/09/28/the-relative-performa...