Live data from Hacker News

Secure Rust Guidelines

anssi-fr.github.io

31–40 of 61 posts

Re: Secure Rust Guidelines

#31
post #16

Earlier quoted context omitted.

Er, don't Haskell's head and (!!) functions default to throwing an exception? $ ghci Prelude> let a = [3, 4, 5] Prelude> a !! 0 3 Prelude> a !! 3 *** Exception: Prelude.(!!): index too large Prelude> head [] *** Exception: Prelude.head: empty list I don't think Haskell is really different from Rust in this respect. Both have a wrapper type for optional values with good syntax, but in both, there's still some syntax s…

> Hoogle doesn't seem to find me a function like Rust's .get() in the standard library In practice, you don't really need one - the safe alternative to "xs !! n" is pattern-matching on the result of "drop n xs", as that's [] if xs has ≤n elements: https://www.haskell.org/onlinereport/standard-prelude.html#$...

Sure, that seems syntactically more cumbersome than 'if let Some(foo) = xs.get(n)' or 'xs.get(n).map(|foo| ...)' in Rust, but yes, you can do it. As I said, because both the Rust and Haskell versions are more cumbersome than using the version that raises an exception/panics, both Rust and Haskell's standard libraries choose to give you a syntactically-easier alternative that isn't a total function.

All I'm saying is that Haskell doesn't seem to do anything different here - Rust has incorporated the lessons from Haskell's type system. (As someone who fell in love with Haskell a long time ago but never got to use it professionally, this is basically why I like Rust.) Is there something Haskell does that Rust does not do? I'm not trying to say Haskell is insufficient - I'm just refuting the claim that Rust is insufficient and should act more like Haskell.

Re: Secure Rust Guidelines

#32

Earlier quoted context omitted.

Same as any other failure; functions that may try to allocate memory and fail will return a Result instead of just a T.

Which would be pretty painful for everyone not working in a memory-constrained environment (at a guess, that's most developers). Much like std:println! panicking instead of returning an error, ergonomics are important too a lot of the time.

Which is why you offer two APIs: one that panics, & one which returns Result or Option

Re: Secure Rust Guidelines

#33

I personally strongly disagree with: Functions or instructions that can cause the code to panic at runtime must not be used. First of all, they kind of dodge this later by saying "Array indexing must be properly tested" (else it can panic) -- everyone thinks they write code which is "properly tested". Personally, I often write panicing code -- if the code gets in a state where I have no idea how to fix it, I panic. F…

Panicking is, in many ways, the best-case scenario for code that contains a bug. A bug that causes a panic is much easier to find through fuzzing, property testing, even static analysis or analyzing failures in production. It also prevents the bug from "infecting" other code by allowing the program to proceed in an invalid state. If you don't want a panic to take down the whole system, you can isolate the code in a t…

> If you don't want a panic to take down the whole system, you can isolate the code in a thread and use a supervision tree, or use `catch_unwind` to let the thread perform cleanup and then continue from a known state.

Playing devil's advocate: with unwinding panics (which are necessary for these two approaches), it's harder to make sure all the data structures the thread was using are left in a coherent state. It's not as bad as exception safety in C++, but it does have some similarities. Just take a look at the tricks the Rust standard library uses to keep everything sane even if the stack unwinds (structs implementing Drop normally called SomethingOnDrop, for instance CopyOnDrop), or std::sync::Mutex poisoning.

Re: Secure Rust Guidelines

#34

I'm having problems fulfilling this requirement in my libs: "Crates providing libraries should never use functions or instructions that can fail and cause the code to panic." The Rust standard library Vec, HashMap etc. can cause a panic in Rust, if the device (such as a mobile phone with a small memory) runs out of memory. C and C++ standard libraries (malloc, std::vector, std::map..) can handle those situations by r…

Yes that is one of the primary failures of Rust at the moment: to my knowledge it currently has no good way to safely manage allocation failures (it also has serious issues with stack overflows). This is an issue with all heap-allocating construct, not just collections but also Box or Rc. > I wish Rust had some easy way to recover from out-of-memory situations when using the standard library. I have been considering…

The problem with allocation failure is that it's non-local. Suppose thread A does a huge allocation of several hundred megabytes. The best case scenario is that this allocation fails cleanly; the worst case scenario is that this allocation succeeds but causes a small 1024-byte allocation in an unrelated thread B to fail (and it doesn't have to be multiple threads, this can happen even within a single thread). I don't know of any solution for this problem, other than statically reserving the memory which will be used for each part of the system.

Re: Secure Rust Guidelines

#35
post #33

Earlier quoted context omitted.

Panicking is, in many ways, the best-case scenario for code that contains a bug. A bug that causes a panic is much easier to find through fuzzing, property testing, even static analysis or analyzing failures in production. It also prevents the bug from "infecting" other code by allowing the program to proceed in an invalid state. If you don't want a panic to take down the whole system, you can isolate the code in a t…

> If you don't want a panic to take down the whole system, you can isolate the code in a thread and use a supervision tree, or use `catch_unwind` to let the thread perform cleanup and then continue from a known state. Playing devil's advocate: with unwinding panics (which are necessary for these two approaches), it's harder to make sure all the data structures the thread was using are left in a coherent state. It's n…

Agreed. There are definitely good arguments for using abort-on-panic, and doing isolation and recovery at the process level rather than the thread level. This is what we do with all the Rust code in Firefox, for example.

Unwind safety is a real issue. I have some personal experience with fixing panic-safety issues in unsafe Rust code:

https://github.com/servo/rust-smallvec/pull/103

I wrote a bit more about it here:

https://users.rust-lang.org/t/c-pitfalls-hard-to-avoid-that-...

Re: Secure Rust Guidelines

#36

I personally strongly disagree with: Functions or instructions that can cause the code to panic at runtime must not be used. First of all, they kind of dodge this later by saying "Array indexing must be properly tested" (else it can panic) -- everyone thinks they write code which is "properly tested". Personally, I often write panicing code -- if the code gets in a state where I have no idea how to fix it, I panic. F…

No,if you are writing secure code then a panic is a denial of service. Their point is that you should never panic, unless you do reach an unrecoverable error.

Re: Secure Rust Guidelines

#37

I personally strongly disagree with: Functions or instructions that can cause the code to panic at runtime must not be used. First of all, they kind of dodge this later by saying "Array indexing must be properly tested" (else it can panic) -- everyone thinks they write code which is "properly tested". Personally, I often write panicing code -- if the code gets in a state where I have no idea how to fix it, I panic. F…

Panicking is, in many ways, the best-case scenario for code that contains a bug. A bug that causes a panic is much easier to find through fuzzing, property testing, even static analysis or analyzing failures in production. It also prevents the bug from "infecting" other code by allowing the program to proceed in an invalid state. If you don't want a panic to take down the whole system, you can isolate the code in a t…

> much easier to find through fuzzing

Note that you can fuzz with debug asserts enabled.

Crash-only software is a fantastic paper that teaches another school of thought: always panic gracefully and be quick to recover. This assumes that you have a quick recovery plan for panics, if you panic, which is then fine (but not always the case).

Re: Secure Rust Guidelines

#38
post #7

I personally strongly disagree with: Functions or instructions that can cause the code to panic at runtime must not be used. First of all, they kind of dodge this later by saying "Array indexing must be properly tested" (else it can panic) -- everyone thinks they write code which is "properly tested". Personally, I often write panicing code -- if the code gets in a state where I have no idea how to fix it, I panic. F…

> First of all, they kind of dodge this later by saying "Array indexing must be properly tested" (else it can panic) -- everyone thinks they write code which is "properly tested". You're skipping half the recommendation though: > Array indexing must be properly tested, or the get method should be used to return an Option . emphasis mine > Also, in rust if we don't want to panic we need to never use array indexing and…

There’s a number of functions that panic in the std if misused, for example copy_from_slice

Re: Secure Rust Guidelines

#39

I'm having problems fulfilling this requirement in my libs: "Crates providing libraries should never use functions or instructions that can fail and cause the code to panic." The Rust standard library Vec, HashMap etc. can cause a panic in Rust, if the device (such as a mobile phone with a small memory) runs out of memory. C and C++ standard libraries (malloc, std::vector, std::map..) can handle those situations by r…

Yes that is one of the primary failures of Rust at the moment: to my knowledge it currently has no good way to safely manage allocation failures (it also has serious issues with stack overflows). This is an issue with all heap-allocating construct, not just collections but also Box or Rc. > I wish Rust had some easy way to recover from out-of-memory situations when using the standard library. I have been considering…

Honestly all C programs that fail to allocate panic as well. What else can you do?

Re: Secure Rust Guidelines

#40

I personally strongly disagree with: Functions or instructions that can cause the code to panic at runtime must not be used. First of all, they kind of dodge this later by saying "Array indexing must be properly tested" (else it can panic) -- everyone thinks they write code which is "properly tested". Personally, I often write panicing code -- if the code gets in a state where I have no idea how to fix it, I panic. F…

(This is neither direct disagreement nor agreement, but a sort of elaboration of my own.)

The practical semantics of a panic become increasingly ill-defined as the number of threads in the program and the depth of their stacks increase. I emphasize "practical" because mathematically, no matter how many threads you have or how deep you get, the semantics are perfectly well defined; it's going to do whatever it's going to do. But those mathematical semantics get more and more complicated as the program grows. If you call X() and it panics, its behavior depends on its stack above. If that X() is called in a "server" thread and it ends up terminating it (or restarting it, or anything else), it may impact any number of other threads in weird ways, even if you "handle" the panic in some manner, e.g., even if you "clean up the locks" on the way up the panic handler that still doesn't mean the program's in a "clean state".

When a programmer goes to write the aforementioned X(), it's impossible in general for that programmer to know what their "panic environment" is going to be. (In specific, it may be an unexposed/private method that does have a good idea what environment it will be called in, but in general a function does not know.) Working via panic handlers shares the responsibility between the caller and the callee in a difficult-to-describe manner. At small scales this is completely neglectable, but as you scale up, mismatches between the programmer of X() and the user of X() very gradually start accumulating, and interacting with the other mismatches, and at some point you get to the point where some junior programmer doesn't fully understand the complicated maze and just starts bashing on code until it superficially seems to work, and then you're in real trouble.

By contrast, the contract with error-type returns like Result or Either or whatever has a clean contract between the caller and the callee; the callee is responsible for reporting failure, and the caller is responsible for dealing with it. This kind of contract does not compose up into a complicated "panic environment", because it does not cross the stack frames like a panic/exception can. The frames don't mix with each other.

In theory it may be possible to completely exclude exceptions; in practice right now we don't really know how to do it 100% in practical languages, so regardless of what I wrote and regardless of what you may like, you will have some sort of "panic environment". However, I think you are in general better off to try to avoid complicating it as much as possible, because it gets exponentially complicated. I do mean exponentially as in 2^x and not x^2 as it is commonly abused. But the x is fairly small, which makes it easy to fail to notice. Also if you know your program is going to stay reasonably small it means it's a valid choice to just not care and use it anyhow. But if you have any reason to think your program may scale, I'd suggest trying very hard to stick to error-type handling as much as possible, as you only pay that exponential term as you add things into your panic environment.

(Also, I know that "using errors" doesn't fix the problems I mentioned, as I myself have had plenty of days where my "error based" program terminated a critical internal server and made the program "go all weird". I'm just saying it makes it better to not add the complications of a complicated panic-handling environment in addition to the already-unavoidable other tasks you have to do.)

Post reply on HN