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