Remix.run Logo
leoh 11 hours ago

Kind of stupid it didn’t have something like this to begin with tbh. It really is an incredible oversight when one steps back. I am fully ready to be downvoted to hell for this, but rust ftw.

IshKebab 11 hours ago | parent [-]

Rust doesn't have anything like this either. I think you misunderstood what it is.

raggi 10 hours ago | parent | next [-]

It doesn’t but the problem space is more constrained as you are at least in control of heap vs stack storage. Register clearing is not natively available though. To put it more simply: yes but you can write this in rust- you can’t write it in go today.

purplesyringa 9 hours ago | parent | next [-]

You can try to write it in Rust, doesn't mean you'll succeed. Rust targets the abstract machine, i.e. the wonderful land of optimizing compilers, which can copy your data anywhere they want and optimize out any attempts to scramble the bytes. What we'd need for this in Rust would be an integration with LLVM, and likely a number of modifications to LLVM passes, so that temporarily moved data can be tracked and erased. The only reason Go can even begin to do this is they have their own compiler suite.

leoh 5 hours ago | parent [-]

It's not clear to me how true your comment is. I think that if things were as unpredictable as you are saying, there would be insane memory leaks all over the place in Rust (let alone C++) that would be the fault of compilers as opposed to programs, which does not align with my understanding of the world.

purplesyringa 4 hours ago | parent [-]

"Memory leaks" would be a mischaracterisation. "Memory leak" typically refers to not freeing heap-allocated data, while I'm talking about data being copied to temporary locations, most commonly on the stack or in registers.

In a nutshell, if you have a function like

    fn secret_func() -> LargeType {
        /* do some secret calculations */
        LargeType::init_with_safe_Data()
    }
...then even if you sanitize heap allocations and whatnot, there is still a possibility that those "secret calculations" will use the space allocated for the return value as a temporary location, and then you'll have secret data leaked in that type's padding.

More realistically, I'm assuming you're aware that optimizing compilers often simplify `memset(p, 0, size); free(p);` to `free(p);`. A compiler frontend can use things like `memset_s` to force rewrites, but this will only affect the locals created by the frontend. It's entirely possible that the LLVM backend notices that the IR wants to erase some variable, and then decides to just copy the data to another location on the stack and work with that, say to utilize instruction-level parallelism.

I'm partially talking out of my ass here, I don't actually know if LLVM utilizes this. I'm sure it does for small types, but maybe not with aggregates? Either way, this is something that can break very easily as optimizing compilers improve, similarly to how cryptography library authors have found that their "constant-time" hacks are now optimized to conditional jumps.

Of course, this ignores the overall issue that Rust does not have a runtime. If you enter the secret mode, the stack frames of all nested invoked functions needs to be erased, but no information about the size of that stack is accessible. For all you know, memcpy might save some dangerous data to stack (say, spill the vector registers or something), but since it's implemented in libc and linked dynamically, there is simply no information available on the size of the stack allocation.

This is a long yap, but personally, I've found that trying to harden general-purpose languages simply doesn't work well enough. Hopefully everyone realizes by now that a borrow checker is a must if you want to prevent memory unsoundness issues in a low-level language; similarly, I believe an entirely novel concept is needed for cryptographical applications. I don't buy that you can just bolt it onto an existing language.

leoh 5 hours ago | parent | prev [-]

This is basically my point, in addition to the fact that the time at which data is freed from the heap is far more predictable.

leoh 5 hours ago | parent | prev [-]

Okay, fair point, sort of. Rust does not have a built-in feature to zero data. Rust does automatically drop references to data on the heap. Zeroing data is fairly trivial, whereas in go, the issue is non-trivial (afaiu).

  use std::ptr;
  
  struct SecretData {
      data: Vec<u8>,
  }
  
  impl Drop for SecretData {
      fn drop(&mut self) {
          // Zero out the data
          unsafe {
              ptr::write_bytes(self.data.as_mut_ptr(), 0, self.data.len());
          }
      }
  }
steveklabnik 4 hours ago | parent [-]

Zeroing memory is trickier than that, if you want to do it in Rust you should use https://crates.io/crates/zeroize