Safe code while interacting with I/O interfaces

I'm trying to understand the implications and consequences of the safety features provided in Rust. I see that when calling a macro such as println! or using other related I/O operations in Rust, the programmer should be able to assume safety rules are in place. However, we know that I/O operations may not be predictable as they require interaction with low level system calls and potentially unsafe code. So, I want to better understand if there is a "safety stack" in Rust where the promised safety is only at a certain level and if an attacker can mess with the underlying operations, all this safety stack may collapse.
I'd highly appreciate your comments and please excuse my little understanding of the language. Still learning!

I am not 100% sure, but I assume that println! just panics if underling system call returns an error. It's more like a debugging tool, because you rarely want to write directly to stdout. In contrast, writeln! is like println!, but takes a Write as first argument and returns a proper Result.

I think that Rust assumes that underling clib is trusted (which is linked dynamically by default iirc). So you'd better not to link Rust code with C library which launches missile instead of printing things.

However you can build completely static Rust binary, by including musl C library implementation (this blog post has a bit more details about this use case: http://betacs.pro/blog/2016/07/07/docker-and-rust/).

1 Like

Thanks for pointing out about writeln!. I add to that the use of other libraries such as BufWriter where you can configure a buffered stream.

I read through this blog link and as I understand the example, the proposal is to statically link to musl libc to prevent manipulation of system calls such as write. However, it is not clear to me how is statically linking to musl different than glibc? Is it preferred just because musl has a smaller archive file? After all if, with static linking, attacks are only possible on the linked binary. Also, static linking will not prevent attacks that are due to vulnerabilities in libc itself.

I don't know about this, would be interesting to hear :slight_smile:

Also, static linking will not prevent attacks that are due to vulnerabilities in libc itself.

Yes. The model for Rust is that if the code within unsafe blocks (and ffi calls are unsafe) is correct, than the safe code won't have memory unsafety, which covers some important vulnerabilities like buffer overruns. Even if you implement libc in Rust (it should be possible on the platforms that expose syscall interface), you still would be affected by the vulns in the syscalls.

So when we say 'safe' we mean memory safe, which is different from secure. Rust can't guarantee security, I would say nothing can guarantee that. Of course a large number of vulnerabilities result from memory unsafety issues, such as buffer overflow & use after free. That distinction aside, its true that Rust does not guarantee the memory safety of operations 'below' some point. That point is the 'unsafe superset' of Rust. There are a handful of operations which can only be performed in unsafe blocks & which Rust cannot guarantee the safety of. One of these is calling FFI functions for exactly the reason you point out - Rust can't possibly guarantee the safety of C code (or code in any language other than Rust).

1 Like

Sure; but memory safety plays a major role in mitigating powerful attack on a system. There is no absolute guarantee for security, I agree, however, all that the security research community is trying to achieve is reducing the attack surface by providing smaller safety guarantees, which Rust is now contributing to.

Thanks for the clarification.

1 Like