Date: Fri, 6 Sep 2024 07:02:57 +0300 From: Konstantin Belousov <kostikbel@gmail.com> To: Alan Somers <asomers@freebsd.org> Cc: ske-89@pkmab.se, freebsd-hackers@freebsd.org Subject: Re: The Case for Rust (in any system) Message-ID: <Ztp-8RH8l-3UJzrV@kib.kiev.ua> In-Reply-To: <CAOtMX2gdC-9ZsTq1G23XHJnmg7fLAo=uixNxspv6o6OtHjEtQQ@mail.gmail.com> References: <CAOtMX2iCNX5OkdeghnbmcMrO0UYWwm4zfxFSZGznOznu%2Bmh5rA@mail.gmail.com> <202409052313.aa18097@berenice.pkmab.se> <CAOtMX2gdC-9ZsTq1G23XHJnmg7fLAo=uixNxspv6o6OtHjEtQQ@mail.gmail.com>
next in thread | previous in thread | raw e-mail | index | archive | help
On Thu, Sep 05, 2024 at 04:37:27PM -0600, Alan Somers wrote: > On Thu, Sep 5, 2024 at 3:13 PM <ske-89@pkmab.se> wrote: > > > > Alan Somers <asomers@freebsd.org> wrote: > > > In fact, of all the C bug fixes that I've been involved with (as > > > either author or reviewer) since May, about three quarters could've > > > been avoided just by using a better language. > > ... > > > To summarize, here's the list of this week's security advisories, and > > > also some other recent C bug fixes of my own involvement: > > > > After checking several of these examples, I'm wondering what the code > > would have looked like in some "better language", where those bugs would > > have been avoided? > > > > E.g for the "use after free" or "unitialized memory" examples. > > > > To me, several of those bugs seem fairly complex, and not just a > > question of having bounds checking for arrays or a borrow checker > > for pointers, or something simple like that. > > > > But maybe the bugs could have been detected and prevented if the > > code would have been forced to be expressed in a completely > > different manner by some other language? Or what is your vision > > of how that would be accomplished? > > > > You seem to be saying that certain examples would be solved by > > a better language, and certain ones would not, so I suppose you > > do have some vision of how that would work. > > > > I'm just curious to learn more, since it is not obvious to me, > > and thus all the more interresting. > > > > /Kristoffer Eriksson > > Excellent question. Here's why a selected sample of those bugs > would've been prevented had the programs been written in Rust. > > 2909ddd17cb4d750852dc04128e584f93f8c5058 > Rust uses RAII wherever possible. Variables are automatically deallocated when > they leave scope. Circular references are almost impossible to create due to > the lifetime borrow checker. So bugs like this really just can't happen in > idiomatic Rust code. This can be expressed in different way. Rust makes it too hard to operate on structures that are richer than acyclic directed graphs. I am very interested in proposals of a reasonable Rust bindings for our VFS interfaces. > > CVE-2024-45063 > Written in idiomatic Rust, the lun->write_buffer would've had a type like > Option<Box<Vec<u8>>>. The only way to free that would be to remove the > contents of the Option, leaving None in its place. So a subsequent > use-after-free would be impossible. The bug would still be present, but > instead of a use-after-free the READ BUFFER command would have to create and > zero-initialize a new buffer. The bug would be immediately obvious to the user > since READ BUFFER would return the wrong data (all zeroes). > > CVE-2024-8178 > Rust abhors uninitialized data. LLVM doesn't even guarantee that a program > will run correctly when accessing it. So written in idiomatic Rust, > lun->write_buffer would either be zero initialized, or it would be allocated > like `Vec::with_capacity(262144)`. In the latter case, it would be partially > initialized during the WRITE BUFFER command. But given the semantics of SCSI's > READ BUFFER and WRITE BUFFER commands, I think zero-initialization is more > appropriate. > > CVE-2024-6119 > In Rust, initializing a union via one member and then accessing it via another > is actually considered to be the same thing as reading uninitialized memory, > and LLVM abhors it. The idiomatic solution is to use a enum (which is similar > to a Java enum) instead of a union. The enum is basically a tagged union, so > the programs knows at runtime which member is initialized. That makes bugs > like CVE-2024-6119 impossible in idiomatic Rust code. > > CVE-2024-41928 > This bug involved zero-initialing a structure, but with the wrong size. > Idiomatic Rust code never uses anything like bzero. In fact, zero-initializing > a structure is considered unsafe, because an all-zero pattern isn't valid for > all structures. To initialize a structure in Rust, you either need to provide > the value of every member or else use an initializer function. The simplest > intializer is often STRUCT_NAME::default(), which can be automatically derived > and is often equivalent to bzero. But all of those methods know the size of > the structure, so bugs like this aren't possible in idiomatic Rust code. > > CVE-2024-45287 > In debug builds of Rust, integer math operations are by default bounds-checked > at runtime. That catches many bugs like this. For release builds, integer > math operations are wrapping by default, but the programmer can also select > bounds-checking. In this particular case, however, a Rust programmer wouldn't > have attempted to multiply those two integers together. Instead, `value` > would've been a Vec of some type, and it would be initialized like > `Vec::with_capacity(nvp->nvp_nitems)`. Note that Rust would only help there if code actually execute a case which makes the operation overflow. So until somebody notes it, the bug is there, in the form of panic in debug build, or 'whatever' in the release. > > 1f5bf91a85e93afa17bc9c03fe7fade0852da046 > Rust's borrow checker will ensure that a single variable cannot be modified > from two locations at the same time, or modified in one and read from another. > This check happens at compile-time, with 0 runtime cost. For cases whether the > compiler cannot determine whether the access is safe, various runtime options > are available, like Mutex. In this case, the function's author actually > performed a cast to remove "const" from the variable. Rust makes such casts > harder, and it's better type system makes them far less necessary. > > 35f4984343229545881a324a00cdbb3980d675ce and > eced2e2f1e56b54753702da52a88fccbe73b3dcb > In idiomatic Rust, a falliable function returns a `Result` type, and the > compiler is smart enough to know when a programmer ignores the Result. It will > generate a warning, and most projects are configured to treat that type of > warning as an error. So bugs like this don't usually happen. They can, > however. A programmer can deliberately ignore the error, as in eced2e2f1e5 , > or he can "unwrap" it. That means "panic on error", which is not terribly > different from the bug fixed by 35f49843432. So it's possible but far from > certain that a Rust implementation would've prevented these bugs. That's why > in my summary I said "about three quarters" could've been avoided. >
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?Ztp-8RH8l-3UJzrV>