Date: Thu, 5 Sep 2024 16:37:27 -0600 From: Alan Somers <asomers@freebsd.org> To: ske-89@pkmab.se Cc: freebsd-hackers@freebsd.org Subject: Re: The Case for Rust (in any system) Message-ID: <CAOtMX2gdC-9ZsTq1G23XHJnmg7fLAo=uixNxspv6o6OtHjEtQQ@mail.gmail.com> In-Reply-To: <202409052313.aa18097@berenice.pkmab.se> References: <CAOtMX2iCNX5OkdeghnbmcMrO0UYWwm4zfxFSZGznOznu%2Bmh5rA@mail.gmail.com> <202409052313.aa18097@berenice.pkmab.se>
next in thread | previous in thread | raw e-mail | index | archive | help
On Thu, Sep 5, 2024 at 3:13=E2=80=AFPM <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. 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 an= d 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 allocate= d like `Vec::with_capacity(262144)`. In the latter case, it would be partial= ly initialized during the WRITE BUFFER command. But given the semantics of SC= SI'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 anot= her 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 simi= lar 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-initiali= zing a structure is considered unsafe, because an all-zero pattern isn't valid f= or all structures. To initialize a structure in Rust, you either need to prov= ide the value of every member or else use an initializer function. The simples= t intializer is often STRUCT_NAME::default(), which can be automatically deri= ved and is often equivalent to bzero. But all of those methods know the size o= f 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-chec= ked 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 would= n'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)`. 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 anoth= er. This check happens at compile-time, with 0 runtime cost. For cases whether= the compiler cannot determine whether the access is safe, various runtime optio= ns are available, like Mutex. In this case, the function's author actually performed a cast to remove "const" from the variable. Rust makes such cast= s 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 w= hy 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?CAOtMX2gdC-9ZsTq1G23XHJnmg7fLAo=uixNxspv6o6OtHjEtQQ>