[HN Gopher] Is Rust Used Safely by Software Developers?
       ___________________________________________________________________
        
       Is Rust Used Safely by Software Developers?
        
       Author : asimpletune
       Score  : 44 points
       Date   : 2021-07-16 19:22 UTC (3 hours ago)
        
 (HTM) web link (arxiv.org)
 (TXT) w3m dump (arxiv.org)
        
       | gjffkk wrote:
       | Safety
        
       | blippage wrote:
       | 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.
        
         | randomskk wrote:
         | 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.
        
         | correct_horse wrote:
         | 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.
        
         | conradludgate wrote:
         | 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
        
         | cakoose wrote:
         | 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.
        
         | qw3rty01 wrote:
         | 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.
        
         | SlySherZ wrote:
         | 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.
        
           | megous wrote:
           | 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.
        
             | cesarb wrote:
             | 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.
        
         | woodruffw wrote:
         | > 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.
        
         | tialaramex wrote:
         | 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.
        
       | epage wrote:
       | > We conclude that although the use of the keyword unsafe is
       | limited, the propagation of unsafeness offers a challenge to the
       | claim of Rust as a memory-safe language.
       | 
       | By this definition, is there any memory safe language?
       | 
       | They all boil down to unsafe dependencies. Even if you ignore
       | interpreter abstractions, any language with FFI is unsafe but
       | without any way to audit it.
        
       | onei wrote:
       | The title is a little misleading, and the results are at a glance
       | a little alarming. If you base your definition of safe on whether
       | you or your dependencies use unsafe anywhere, then obviously
       | you're going to find something of concern. However, if you
       | measure the safety of your code line by line, then the results
       | are much better. Similarly, there's a balance to be found when
       | assuming unsafe code is unsound.
       | 
       | The question is ultimately a reminder to check your dependencies
       | to make sure they're active and take reasonable steps for
       | validating unsafe code.
        
       | qzw wrote:
       | From the abstract:
       | 
       |  _> Our results indicate that software engineers use the keyword
       | unsafe in less than 30% of Rust libraries, but more than half
       | cannot be entirely statically checked by the Rust compiler
       | because of Unsafe Rust hidden somewhere in a library 's call
       | chain. We conclude that although the use of the keyword unsafe is
       | limited, the propagation of unsafeness offers a challenge to the
       | claim of Rust as a memory-safe language._
       | 
       | I don't think it's accurate to say that any use of _unsafe_
       | necessarily propagates to all code in the call chain. Often safe
       | abstractions are built around the unsafe blocks to effectively
       | limit the scope of unsafeness. It's not clear from the abstract
       | whether this analysis accounts for such scenarios.
        
       | nynx wrote:
       | This paper is surfaced pretty often and it's misleading. Rust is
       | all about making abstractions around unsafe code that cannot be
       | used unsafely.
        
       | nicolashahn wrote:
       | Disclaimer: Rust groupie.
       | 
       | Technically no (only for half of libraries) but effectively yes,
       | if you're comparing to C and C++. The fact that there might be an
       | `unsafe` somewhere in your dependency tree just means that
       | instead of being there being a 0% chance of a memory bug, there's
       | 0.0001%. Whereas a C/C++ codebase of moderate size is virtually
       | guaranteed to have many (orders of magnitude might be off but
       | point stands).
        
         | simion314 wrote:
         | Let's not forget that there are other languages that are memory
         | safe and might be good enough for many tasks, so limiting the
         | comparison only with C/C++ feels unfair to me.
        
           | vlovich123 wrote:
           | Every serious C/C++ shop I've seen is seriously considering
           | incorporating or switching to Rust. I don't think that's true
           | for many other languages unless they would otherwise be
           | switching to an unsafe language like C/C++ for performance
           | reasons, which is probably a minority.
        
             | snovv_crash wrote:
             | None of the C++ places I've worked are considering
             | switching. Rust lacks the libraries and ecosystem, and it's
             | hard enough to hire C++ developers as it is.
        
             | simion314 wrote:
             | Are they switching mid project and doing the "Rewrite It in
             | Rust" meme? Or only for new project?
             | 
             | If for new projects what type of project that is not some
             | kernel, browser or some low level library is Rust the best
             | tool for the job? For most type of projects I am thinking
             | Python,Java, C#, C++ still is better .
        
               | vlovich123 wrote:
               | TLDR: You've tried to exclude something like 90% of all
               | the systems programming problem domains & then said Rust
               | (a systems programming language) is the wrong tool. Yes,
               | but you're kind of missing the forest for the trees.
               | 
               | You've got MSFT, Facebook, & Google all investing heavily
               | into Rust as a replacement for C/C++. It's still early
               | days. Rust's sweet spot for being the best tool is
               | anything that's currently written in C/C++. If you're
               | writing Python, Java, or C# code, then Rust is probably
               | not the language for you (albeit Rust is getting deployed
               | in all these places where perf is needed because it
               | offers the performance of C/C++ with the safety of more
               | managed languages).
               | 
               | At no point will it be "replace everything in Rust" (at
               | least not in the immediate term). It'll be more like
               | "write this component with a thin API in Rust" or "with
               | codebases that have a C++ runtime, work out a way for the
               | peripheries to be written in Rust".
               | 
               | I'll give you an example. We used the venerable addr2line
               | (or even llvm-symbolizer) to symbolicate addresses for an
               | executable. We used it by spinning up a process for each
               | address to symbolicate (which is dumb, but whatever). By
               | writing a wrapper over Gimli-rs/addr2line-rs, we were
               | able to improve our symbolication time 1000x & reduce our
               | memory footprint 3x. The memory footprint improvement is
               | because Gimli is 0-copy. 100x of that speedup is because
               | Gimli's 0-copy is also just faster at parsing the DWARF
               | data. The next 10x is because we only parsed the DWARF
               | once & then used that precomputed information for all
               | addresses. The fact that that Rust tool was correctly
               | engineered as a library + frontend meant this was
               | relatively easy, cheap & painless to integrate into our
               | existing C++ codebase (using the cxx crate).
               | 
               | Like all things, Rust is a tool. Knowing how to find the
               | right tool for the job & deploying it well is 99% of the
               | job. Personally, I've found the Rust libraries I've come
               | across for systems programming to be higher quality than
               | the equivalent C/C++ libraries because the field is
               | green, the userbase is extremely passionate, enthusiastic
               | & early adopteree (i.e. more technically adept), & Rust
               | makes certain kinds of optimizations easier than they
               | were before (so naturally people want to play around with
               | them). Additionally, because these are all rewrites, 90%
               | of the time the improvement is just applying more modern
               | state of the art knowledge to existing APIs/algorithms
               | (e.g. doing 0-copy for symbolication is a "no-brainer"
               | but it's hard to do safely in C++).
        
               | Jetrel wrote:
               | Any codecs or anything like that seem like a screamingly
               | good target.
               | 
               | Right now they're one of our most egregious sources of
               | security vulnerabilities (mostly due to a lot of hands-on
               | bit-twiddling that results in a lot of possibilities for
               | overflow). It's gotten bad enough that a number of system
               | developers (browsers, OSes) have been shifting to a model
               | where codecs are strictly sandboxed, and just get a
               | single binary input blob, and a single output blob, and
               | don't get any other kind of access to the system they're
               | on - it's a scorched earth problem, but it really is that
               | bad.
               | 
               | The thing about trying to write one with Rust is, if you
               | actually 'went with the flow of the language' and didn't
               | just immediately pop into `unsafe`, you'd be able to
               | write a really safe codec.
               | 
               | -but-
               | 
               | The real payoff is that the performance would be
               | surprisingly good - usually a suggestion like the one I
               | just made would absolutely wreck performance, but one of
               | the big things about Rust is that because it lets you
               | describe what your intent is as a programmer in such
               | higher-level terms (despite being a machine-level
               | language), it gives the compiler a lot more information
               | about the scope of your intent, which opens the
               | floodgates to potential optimizations.
               | 
               | For a really easy example - Rust does all variables as
               | "immutable by default". When you're programming in C, and
               | something is compiling your code, there are tons of
               | opportunities where you'd go "oh, well we already
               | calculated this, why don't we just cache it?" But the
               | compiler can't, because it doesn't actually know what
               | you, the human, know - that that thing won't change.
               | Because that knowledge is available to the compiler
               | through Rust, the compiler actually DOES know, so tons of
               | things can get inlined, cached, and otherwise optimized
               | for you which you'd otherwise have to do by hand.
               | 
               | Usually when people do these sorts of things by hand,
               | they'll ace a couple key optimizations (usually things
               | that show up in profiling hotspots) and completely
               | overlook hundreds, even thousands of other potential
               | optimizations.
               | 
               | There are tons of other "high level intent" things that
               | give the compiler more info it can use to optimize, but
               | the "immutability by default" one is a really easy one to
               | explain.
        
               | jensvdh wrote:
               | Game Servers?
        
               | Jetrel wrote:
               | Also - Rust code can directly link to C++ code and vice-
               | versa. If you're an existing C++ shop with a big codebase
               | (say, a large game engine), you don't have to "rewrite it
               | in rust".
               | 
               | You can just start building new parts in rust. Like, if
               | you're building a new audio subsystem, you can do "just
               | that subsystem" in rust, and expose an interface for the
               | rest of the legacy engine code to use.
        
             | ipaddr wrote:
             | Every 'serious' C/C++ is seriously considering
             | incorporating or switching to Rust.
             | 
             | So all serious shops are going through this self discovery
             | process but none have decided yet they are weighing the
             | pros and cons daily. Unable to reach that conclusion but
             | moving closer to Rust everyday.
             | 
             | That sounds like someone is hopeful they bet on the right
             | horse.
        
         | Zababa wrote:
         | I think the problem here is not the absolute safety but the
         | illusion of safety. You approach problems differently when you
         | expect memory bugs to appear compared to thinking the chance is
         | 0%.
        
           | pcwalton wrote:
           | > You approach problems differently when you expect memory
           | bugs to appear compared to thinking the chance is 0%.
           | 
           | The chance is never 0%, and nobody should be thinking that
           | way. Memory safety bugs routinely arise in JITs (e.g.
           | JavaScript engine CVEs). In fact, memory safety issues
           | routinely arise in _CPUs_ (e.g. rowhammer).
           | 
           | In reality, terms like "memory safety" are rarely ever exact,
           | but they're still useful descriptions. Practically, people
           | should be thinking about frequencies, not in black and white.
        
             | cogman10 wrote:
             | Yup.
             | 
             | I've created a memory leak in java. Not a "I forgot to free
             | memory in a map" memory leak but a honest to goodness "This
             | JVM is allocating heap space and losing track of that
             | allocation". Something that required me to install a
             | special malloc in the VM to track down the leak. This was
             | using standard libraries (You can do it too! Go play with
             | the Deflater and Inflater API and you'll find it makes
             | calls off to Zlib. If not properly handled you end up
             | leaking heap allocations).
             | 
             | That was the first and only time I've ran into that issue,
             | but I ran into that issue.
             | 
             | The guardrails rust gives you are essentially the same that
             | the JVM gives you. That is, use only safe code and (barring
             | a compiler bug) you are safe. However, bad things COULD
             | (not will) happen if you use unsafe blocks. Just like bad
             | things can happen in JNI blocks or places where Java makes
             | use of C libraries for functionality.
             | 
             | And that is what makes rust fantastic. It's got memory
             | safety up there with JVM safety without a garbage
             | collector. It even has nifty keywords (unsafe) to keep
             | developers on their toes when they need it. Just like
             | Java's JNI or Pythons C extensions.
             | 
             | In fact, I'd argue that rust unsafe ends up being more safe
             | than either JNI or C extensions primarily because it is
             | something that can be audited very quickly across all rust
             | code bases. Have a memory issue? It's in the unsafe block.
        
           | yakubin wrote:
           | A study of the frequency of memory corruption in rust
           | programs would be useful here. If the real-world frequency is
           | let's say <0.1% of bugs uncovered, then I say this mindset is
           | a blessing. Working with C++ the feeling of paranoia you have
           | with 1/3[1] of bugs reported, that maybe again it's memory
           | corruption, is sucking the energy out of developers.
           | 
           | [1]: That's the frequency of this paranoia creeping in that I
           | observed, not of detected memory corruption, but that too I
           | think would be counted in an integer number of percents IME.
        
             | pornel wrote:
             | The real-world data shows that most Rust libraries fail due
             | to (memory-safe) panics or logic errors leading to resource
             | exhaustion, but plain old memory corruption is quite rare:
             | 
             | https://github.com/rust-fuzz/trophy-case
        
             | Zababa wrote:
             | That's a great point. Their study is a good starting point
             | but it's hard to get anything concrete out of it. I hope
             | they can continue with something like what you proposed.
        
           | adkadskhj wrote:
           | There is no illusion of safety. It's 100% safe, within the
           | code you right, if you don't use unsafe. If there is unsafe
           | in code you use (write or shared), it's a handful of lines
           | which you can nearly grep for it's so easy to locate.
           | 
           | Rust devs (like myself) aren't illusioned into believing that
           | my code and _everything below it_ is 100% safe.
           | 
           | However i can positively assure you my code is 100% safe.
           | Well, assuming no bug in Rust-lang/compiler. My _programs_
           | are not guaranteed to be 100% safe unless no unsafe is used,
           | but Rust lets you know for certain what is safe, and what is
           | unsafe.
           | 
           | The point isn't absolute safety in your program. The point is
           | absolute safety where you think there is absolute safety. To
           | strictly know what is unsafe. And to reduce the total lines
           | that are unsafe.
           | 
           |  _edit_ : I'm confused by the downvotes, what is incorrect or
           | controversial about this?
        
           | joshuamorton wrote:
           | I don't think this is as strong a claim here as you think.
           | 
           | The point of unsafe is to point you toward the areas you need
           | to audit. Unsafe code can't be proven memory safe by the
           | compiler [...but the rest can be]. It's far easier to audit
           | 10 or 100 LoC of unsafe rust than 10 or 100 thousand of C++.
           | 
           | And even in C++ there are subsets of the language that are
           | supposedly memory safe. But even when writing in those
           | subsets I'm not convinced that the abstractions I'm building
           | on are as well validated as rust.
        
       | woodruffw wrote:
       | Interesting paper. Here's one of their key conclusions:
       | 
       | > Our analysis shows that while publicly available Rust libraries
       | rarely use the unsafe keyword (even very popular libraries), most
       | of them are still not Safe Rust, because of unsafe use in
       | dependencies.
       | 
       | I think this might be a misapprehension of safety in Rust by the
       | authors: safety doesn't mean that absence of the `unsafe` keyword
       | in your dependency tree, it means _safety by construction_
       | through safe wrappers of fundamentally unsafe code.
       | 
       | The Rust standard library is the perfect example of this: it
       | exposes safe interfaces that are built on top of fundamentally
       | unsafe OS primitives and system calls. That doesn't make any code
       | that uses them "unsafe"; it parametrizes the notion of safety on
       | safe abstractions. That's all Rust has ever promised, and it's
       | _still_ significantly better than the status quo.
       | 
       | Other than that, the paper's other observations are excellent: we
       | _should_ be more aware of the presence of `unsafe` in our
       | dependency trees, and that information _should_ be more readily
       | surfaced by standard tooling. Tools like `cargo geiger`[1] and
       | `siderophile`[2] (FD: my company 's tool) bring us a little
       | closer to that.
       | 
       | [1]: https://github.com/rust-secure-code/cargo-geiger
       | 
       | [2]: https://github.com/trailofbits/siderophile
        
         | k__ wrote:
         | I never understood that logic.
         | 
         | You can write perfectly safe code in C where virtually
         | everything is unsafe in the sense of the unsafe keyword.
         | 
         | To my understanding, the idea of unsafe is that you now leave
         | the guard rails that Rust provides and have to think for
         | yourself.
        
           | setpatchaddress wrote:
           | Excellent -- so your large C codebase is free of security
           | bugs, something no software vendor has managed to accomplish.
           | Ritchie and Thompson themselves didn't manage that feat.
           | 
           | Tell us how you do it, please, so the world may learn.
        
             | throwaway_c2 wrote:
             | Oh, that's actually simple. Enforcing the "malloc once"
             | rule gets you like halfway there, and using custom hardened
             | code for common stuff like string manipulation finishes the
             | job. Things like that are why some C codebases are large in
             | the first place - you have to reinvent your own safe space
             | from scratch.
             | 
             | The funny thing is that it's actually harder to protect
             | from certain classes of bugs in Rust. For example, you
             | cannot uncouple from the global allocator as easy as you
             | can in C/C++, and if you do, you do it with "unsafe" code.
             | That doesn't make Rust a bad language, it's just that you
             | can see some of the inherent flaws only if you had written
             | safe C code previously and then you find out that some
             | basic techniques don't translate.
        
           | pornel wrote:
           | The difference is that in Rust you can build safe (foolproof)
           | abstractions on top unsafe code. OTOH, in C you can't write a
           | library that returns a pointer and prevents users from
           | misusing it (e.g. ensure they can't use it after your lib
           | frees the data, or improperly share it between threads). In
           | C, the risk of unsafety and misuse of APIs remains a problem
           | for every downstream user.
           | 
           | For example, Rust's String type internally allocates,
           | reallocates, and frees memory. It can have partially
           | uninitialized buffer, and lots of other "unsafe" things.
           | 
           | But there's _no way_ to cause UB when using safe Rust 's
           | String. You can't cause use-after-free. You can't cause a
           | data race. You can't overflow the buffer. You can't even
           | break its UTF-8 encoding. That's because String's public
           | interface only exposes safe methods. The unsafe String
           | internals were written and checked for correctness, and from
           | then on they're safe to use for everyone else.
        
           | topspin wrote:
           | "I never understood that logic."
           | 
           | Pragmatic techniques mystify some.
           | 
           | Some code is beyond the ability of a compiler to verify
           | because the necessary information is missing. Rust
           | acknowledges this reality and accommodates such code through
           | unsafe. The presumption inherent in this is that unsafe is
           | used only when deemed necessary. Yes, deemed; a decision made
           | by some flawed soul, as opposed to a compiler. This is a
           | pragmatic policy and it cannot be reconciled by the
           | unreasonable.
        
           | tialaramex wrote:
           | Unsafe doesn't remove all the guard rails, but it allows you
           | to do a handful (five) of tremendously _unsafe_ things, for
           | which you then take all responsibility as the compiler cannot
           | analyse them for safety.
           | 
           | * You can dereference raw pointers. Are they pointing at
           | anything at all? At something that type pointer might
           | reasonably point to? Not the compiler's job to check, if you
           | screwed up your program now has Undefined Behaviour like C or
           | C++
           | 
           | * You can access C-style unions. This one says it might be a
           | boolean, a pointer to something, or an integer. You decided
           | to guess it's... a boolean. If you're wrong you get Undefined
           | Behaviour.
           | 
           | * You can mutate (change) static variables. Global cheese
           | flavour is Cheddar? Let's change it to Gorgonzola, no wait
           | Parmesan. Was anybody else using that for anything? No idea,
           | too bad, you might introduce Undefined Behaviour if you
           | didn't think this through.
           | 
           | * You can call functions Rust labelled "unsafe". These
           | functions come with instructions about rules you must follow
           | to use them safely. If you violate any of those instructions,
           | all bets are off, but if you obey the instructions whoever
           | wrote the function (which may be the Rust standard library)
           | promises that was safe.
           | 
           | * You can implement special Traits labelled "unsafe" to
           | implement. These Traits, unlike most Rust traits, have to
           | promise they're correct. If you claim to implement Searcher,
           | a Trait for text processing, and you get it wrong, other
           | people's code may blow up. Whereas I have types like
           | Funhouse<> which claim to implement the trait Eq (promising
           | they understand how equality works) and then as a goof they
           | utterly refuse to do so correctly, that's a safe trait,
           | nothing blows up. My types don't work in a sensible way
           | (Funhouses are simultaneously equal to and not equal to every
           | other Funhouse including themselves, which is stupid), but
           | your program doesn't have Undefined Behaviour if you use
           | these types, they're just annoying.
           | 
           | But, lots of things that would cause Undefined Behaviour in
           | some popular languages are impossible _even in unsafe Rust_.
           | 
           | Suppose I have a function that takes an array of 1024 bytes.
           | I try to do { let z = array[1040]; } -- That won't compile,
           | this array isn't big enough. If I write unsafe { let z =
           | array[1040]; } it _still_ doesn 't compile, didn't I get it,
           | this array isn't big enough.
           | 
           | OK, let's have the function take a parameter k, and at
           | runtime we can set k to 1040 and try that way. If I write {
           | let z = array[k]; } when k is 1040 the program panics. The
           | array isn't big enough. If I write unsafe { let z = array[k];
           | } it _still_ panics and the compiler even warns me that
           | unsafe isn 't doing anything for me here and I should remove
           | it.
           | 
           | Similarly although Rust provides checked arithmetic (ie you
           | can explicitly say "Tell me the answer to x + 100, or, if
           | that would overflow, tell me it didn't work") its default
           | integer arithmetic will panic in debug builds if you overflow
           | and, if you missed that in debug, but it happens in
           | production, you get overflow, which may very well not be what
           | you wanted, but you apparently didn't even know it could
           | happen, so, good luck with that. Either way, no Undefined
           | Behaviour. 255_u8 + 255_u8 is a compile error, a runtime
           | panic or at worst it's 254, but it definitely is not zero,
           | forty-two, or unrelated program misbehaviour.
        
           | kibwen wrote:
           | The idea of the `unsafe` keyword in Rust isn't that a single
           | use of it anywhere in the dependency chain must make the
           | entire code base suspect. Instead, the point is that
           | encapsulation can be used to create primitives that rely on
           | `unsafe` internally but whose interface doesn't provide any
           | way to violate memory safety; downstream consumers of the
           | interface shouldn't need to worry themselves about upholding
           | memory safety.
           | 
           | Here's a contrived example, imagine a library with the
           | following public function:                   pub fn
           | index_or_zero_i_guess_lol_idk(array: Vec<u8>, index: usize)
           | -> u8 {             if index < array.len() {
           | // SAFETY: array index must be in bounds
           | unsafe { *array.get_unchecked(index) }             } else {
           | 0 // yolo             }         }
           | 
           | Does a downstream consumer of this library need to do
           | anything to ensure memory safety of their own code? No,
           | because the unsafe `get_unchecked` function has exactly one
           | caller-upheld safety invariant, that the array index is in
           | bounds (documented at https://doc.rust-
           | lang.org/std/primitive.slice.html#safety ), and because this
           | code makes sure to uphold the invariant, the buck stops here.
           | A downstream consumer need not use `unsafe` at all, and thus
           | not give up the guard rails. And even in a single codebase,
           | `unsafe` only "infects" the module that it's in, not the
           | whole codebase, since a module is the unit of encapsulation
           | in Rust. If you make your modules containing `unsafe` as
           | small as possible, then you reduce amount of code that needs
           | audited for safety.
           | 
           | And that's the whole point of `unsafe` in Rust: to reduce the
           | scope of code that can potentially be memory-unsafe. It
           | firmly places responsibility on users of `unsafe` to ensure
           | that they uphold the necessary safety invariants; there's
           | never any question as to which code is responsible for memory
           | unsafety, as it can always be traced back to a single use of
           | the `unsafe` keyword somewhere that has failed to uphold a
           | safety invariant. It doesn't outright eliminate bugs--humans
           | are fallible--but it makes it obvious when they might happen,
           | it makes it easier to have confidence that they won't happen,
           | and when they do happen it's easier to figure out why.
        
             | incrudible wrote:
             | A downstream consumer might not need to use the keyword
             | unsafe, but by calling such a function, they have de facto
             | lost memory safety. It is a nice-to-have to know that only
             | the unsafe blocks of code are able to violate memory
             | safety, but once memory is corrupted, errors can manifest
             | anywhere.
             | 
             | Rust in practice is likely not as safe an ecosystem as C#,
             | because Rust users are more likely to use unsafe blocks for
             | performance tricks. In my view, Rust should have a safe
             | keyword to mark functions that are transitively not unsafe,
             | much like the pure keyword in some languages marks
             | transitive functional purity.
        
               | eloff wrote:
               | > A downstream consumer might not need to use the keyword
               | unsafe, but by calling such a function, they have de
               | facto lost memory safety.
               | 
               | That's a silly definition of memory safety. You can't
               | write a non trivial rust program that doesn't use unsafe
               | in it's dependencies. No interior mutability, no
               | allocation, no containers.
               | 
               | The point is these uses of unsafe are checked by the
               | author to ensure they can't introduce unsound behavior
               | through the exposed safe API.
               | 
               | > Rust in practice is likely not as safe an ecosystem as
               | C#, because Rust users are more likely to use unsafe
               | blocks for performance tricks.
               | 
               | Sort of agree with you here. Unsafe code is rare in C#.
               | But C# has race conditions so some kinds of bugs are more
               | common. But C# isn't as suited for systems programming
               | type of problems or really high performance code.
        
               | lucian1900 wrote:
               | P/Invoke is not uncommon either, especially in Windows.
               | Rust and C# offer similar guarantees.
        
               | svat wrote:
               | Why would memory get corrupted in this example? To put it
               | differently: if the code didn't have the `if index <
               | array.len()` check, clearly things would be different.
               | How would you describe this distinction, other than
               | "memory safety"? (Asking because it's not clear whether
               | you're using an unusual definition of memory safety, or
               | just didn't notice the bounds check in the example.)
        
             | KallDrexx wrote:
             | This does assume that all `unsafe` usages are wrapped in
             | correctly `safe` guards though, doesn't it?
             | 
             | You can wrap `unsafe` functionality without checking and
             | thus have memory issues, or if the unsafe wrapper hasn't
             | correctly validated all possible entries that can cause
             | memory corruption. Then any consumer of this dependency has
             | a possible security issue with the right inputs to the
             | consumer, which can trigger passing an exploit down to the
             | unsafe portion.
        
             | void_mint wrote:
             | Your contrived example makes sense, I guess I'm just
             | confused about how one would have faith that code using
             | `unsafe` is actually safe? As in, to me (an avid non-Rust-
             | user), seeing unsafe would mean either A.) I have to audit
             | this codebase to make sure there wasn't any legitimate
             | unsafe behavior, or B.) I just have to totally skip this
             | dependency.
             | 
             | If the argument is "You can use unsafe but do things
             | safely", isn't that the same argument as C? "Just have
             | faith that they did it right"?
        
               | seoaeu wrote:
               | The difference is in the quantity of code you have to
               | audit. In a given Rust project perhaps 0.1% of the lines
               | of code may be unsafe, which while technically non-zero
               | is widly less effort to audit than the entire codebase in
               | the case of something like C
        
       ___________________________________________________________________
       (page generated 2021-07-16 23:02 UTC)