Can we trust libc?

        // ...
        let read_size = nix::unistd::read(fd, &mut *file_data_vec)?;
        unsafe {
            file_data_vec.set_len(read_size);
        }
        // ...

Can we assume that read_size is not greater than file_data_vec.len()?
Is it sound to use some invariants from libc?

This is hard to answer in general, but we can find an answer to your specific question.

nix::unistd::read takes a RawFd from and a &mut [u8] to read into. Luckily &mut [u8] carries around the length of the slice.

It then calls libc::read as read(fd, buf, buf.len()).
Let's see what the man page tells us about read:

ssize_t read(int fd, void *buf, size_t count);

read () attempts to read up to count bytes from file descriptor fd into the buffer starting at buf .
[...]
On success, the number of bytes read is returned (zero indicates end of file) [...]

Concluding from that: libc's read maps to the syscall read, which should never read more than the requested amount of bytes.
So read_size <= file_data_vec.len() should hold.

2 Likes

It should be, although in such a case I definitely wouldn't use set_len() unless it's in a really-really performance sensitive spot. You can just use resize() instead, which at least doesn't cause memory corruption if the length is wrong.

1 Like

Is the data pointed by &mut *file_data_vec initialized? If not you could have UB here, although I don't know how the compiler could exploit it. You could probably directly call libc::read since that doesn't involve creating a slice

Thanks for your answer. It solved the question1 perfectly.

If someone replaces the system libc with an incorrect implementation´╝îwhat will happen?

Is there a soundness problem in the sample code?

In what way is the implementation incorrect?

Yes it is sound to assume the invariants libc says it will uphold will actually be upheld; at some point you need to be able to trust your system otherwise you can't build anything.

The unistd.h header file defines the contract for read() and if your system provides a bad implementation (e.g. out-of-bounds write under some obscure circumstances) then the fault lies with your system libc, not the Rust code that calls it.

As long as the Rust libc::read() wrapper is implemented to call read from the system libc correctly based on the function signature and documentation (which it is) there is no soundness problem.

7 Likes

Any time one is doing FFI one is trusting the called code. The library being called is often written in an unsafe language, so can trigger UB, at which point all bets are off.

4 Likes

It makes sense.

So Rust's safety is not unconditional but relies on the system. We can trust the system.

I don't think of it like that.

Rather, Rust's safety guarantees are unconditional, assuming one is not actually using "unsafe" and modulo compiler bugs.

What is unsafe, in the Rust sense of the word, is the system you are building. That system likely includes libc and a bunch of other "foreign" libraries, the operating system, driver modules, the processors it all runs on, all the other surrounding hardware, with your Rust program sitting on top somewhere.

The problem being that Rust can only ensure safety in it's own domain. It has nothing to say about everything else around it.

The solution would be to grow the size of the Rust domain to make more of the entire system safe. In principle we could replace libc with a "librust" that provides all the same functionality. We could write the OS in Rust.

All of which sounds very far off at this time. Unless one is into Redox or building ones own embedded system.

9 Likes

If something like libc goes wrong and then Rust code crashes with segfault, does that break the safety guarantees?
I wonder how to describe the situation.

Imagine your computer's processor was dodgy and it read from a completely random address when you dereferenced a reference. Would you say it's Rust's fault that the program is broken, or the CPU's?

If there were safety issues because of a broken libc, people would create a new ticket on the issue tracker for whatever Rust application/library they first saw the issue in, the maintainers would realise it's a bug in libc and not their code, then they'd tell the person to create an issue upstream.

2 Likes

At the end of the day, everything boils down to calling machine code and you must trust the hardware to function correctly, because you cannot "write" hardware with Rust. Even if we agree on trusting the hardware, the interface to the hardware is inherently unsafe, i.e. even if we write the entire OS in Rust, unsafe code or more precisely assembly, is unavoidable, because we're dealing with architecture-specific code. All we can do is ensure we don't mess up the unsafe part and build a safe API on top of that.

This encapsulation is what makes Rust so awesome. We can write safe code and know, that if UB is triggered, it's not our fault. The root of the problem has to originate from unsafe code. Being able to significantly reduce the code surface to find the cause of UB is something, that programming languages like C/C++ can only dream of. This is currently the best we can do and Rust had to sacrifice quiet a bit of time during compilation for that.

Looking into the future, computers are still becoming faster and the compiler will be optimized as well, so eventually, those compilation times will be reduced to a point where they're negligible and perhaps, it'll enable more checks to be moved from run- to compile-time, making programs safer and faster than ever.

8 Likes

No.

In order to make any calls into libc one has to use FFI. Which necessarily means wrapping those calls in "unsafe". At which point all (many/most) of Rust's safety guarantees are switched off.

Philosophically I would say all kind of things come with safety guarantees. Those guarantees are only valid in some range of applicability of the product, as long as you follow the manufacturers instructions, and so on.

Your nice, safe, Volvo might come with ant-lock brakes, air bags, and all kind of features to increase your driving safety. But drive into a war zone full of mines and missiles and all bets for safety are off.

Rust is that Volvo, libc and and all else is the war zone!

(God I hate car analogies).

14 Likes

Quite so.

But why not? People are "writing" hardware in Python, Scala, and whatever else. I have idle daydreams that one day someone might create an HDL that leverages Rust.

Yep. That is why many of us are here I guess. Plus the fact that Rust and it's environment has many other awesome features.

I certainly love not having to worry about UB in my code !

Not so sure about "significantly reduce". Typically programs I am involved in are far smaller than the millions of lines of libraries and operating system they depend on. But yes, I love Rust taking care of all my malpractices. Whoever wrote all that other stuff will just have to fix it as best they can.

I agree

I was thinking more in terms of the physical component, that could result in defect hardware, because hardware lives in the physical and not the purely mathematical world and natural decay and chemical processes damage hardware. There's not much we can do about that.

Anyway, I found this Wikipedia page and think it fits right into the topic:

HDLs can describe hardware behavior, and you can theoretically prove that a certain hardware design has the desired behavior, assuming you can also describe that desired behavior in a way that the prover can understand. Theoretically you could prove the full stack, but you still need to actually manufacture it at some point. Beyond physical decay, radation etc. like @Phlopsi mentioned, there are multiple steps between HDL and physical device where things can go wrong: you have to compile the HDL into gates, create a physical layout, create a set of masks, and deposit/etch/etc. your way to victory.

It's true there are tools (such as LVS) that reduce the risk of making errors at each stage in this chain. But (1) these tools, at the moment, still need people to drive them in the right direction, and any point where human discretion is involved is a possible point of failure; and (2) the tools themselves are not part of the system being proven, so you have to invent tools to prove the tools, etc... (also, the EDA tool industry regularly spits out garbage that makes Cyberpunk 2077 look like a model of software development by comparison, but that is neither here nor there)

At the end of the day you have to trust something, whether it's libc, the OS, the processor, the guy who wrote the simulator the processor was tested in... But all that stuff is outside the scope of what a programming language can help with.

6 Likes