Skip site navigation (1)Skip section navigation (2)
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>