Hacker News new | past | comments | ask | show | jobs | submit login

I don't understand this article.

I think there is no language level feature in Rust to prevent TTCTTOU. TTCTTOU can happen in both c++ and rust.

for example:

    let f = File::open("username.txt");

    /*what if the file is deleted here?*/

    let mut f = match f {

        Ok(file) => /*what if the file is deleted here?*/ file,

        Err(e) => return Err(e),

    };

    /*what if the file is deleted here*/

I mean even if you write it like: let f = File::open("username.txt")?;

it looks atomic, it is not.

the c++ example, on the other hand, can do a filtering too, using:

http://www.cplusplus.com/reference/fstream/ifstream/is_open/

I mean Rust forces you to do error handling, which is nice. But that's not enough for preventing TTCTTOU. I don't think the first example is a strong argument.




File::open("username.txt")? looks fine to me, given the correct mental model of file systems—it returns an error if opening the file fails. Reading is then a different operation that can yield its own set of errors. Therefore each read is fallible; hence reader.lines().filter_map(Result::ok), which says “read it line by line, and just ignore any errors”. If the file is deleted before you’re done reading it, further reads may fail (depends on the file system’s behaviour), and be properly handled as specified.

If you want atomic open-and-read-the-whole-file, you can reach for something like std::fs::read_to_string (https://doc.rust-lang.org/stable/std/fs/fn.read_to_string.ht...).


> /what if the file is deleted here?/

Nothing. In all major operating systems I'm aware of, opening a file creates a handle to the file contents. This handle is distinct from the handle implied by the existence of the path in the filesystem. Some filesystems allow you to create another handle to the same file by a different name (called a hardlink). Removing a file only deletes the handle implied by the path; if another handle to it exists (via a hardlink or the file currently being open in a process) the data itself is unaltered.

In general, TOCTTOU attacks are caused by introspection being done on paths instead of file handles. Once you have handles, and you are querying exclusively using handles, your TOCTTOU issues largely go away. There is still a related issue that filesystem atomicity guarantees (or rather, a general lack thereof) is still a major headache, but it's not the same issue as TOCTTOU attacks.


On POSIX filesystems, deleting a file removes the name from the directory list, but the actual file is still available to open file descriptors.

The point is, if I go by names, there is no guarantee of identity. File-descrpitors ensure that.

Same could be done with c++, but the libraries are not designed that way.

Still, I don't find it terribly convincing.


OP says you can't really avoid it in Rust either, but argues that because there's not a separate check it's more robust. I agree w/ you though that it's not a strong argument, and that you can do the same thing in C++; it's a confusing/arguably incorrect point IMO.

More broadly, this is kind of a bad example. Handling files is surprisingly difficult and that's really where the dragons lie here. IME the dragons exist for both Rust and C++, because they're platform problems rather than stdlib/language problems.

NB: the correct thing to do here for POSIX platforms is to call fstat, which is true regardless of systems language.


I found the filesystems complaint about C++ bizarre too, because that is a system-dependent behaviour. How an open file behaves when it is deleted has very little to do with the programming language. And it does not matter if that language is Python, Rust or C++. These ecosystems interact with the underlying OS and report back what the OS layer tells them.

I absolutely understand some of the niceness about Rust preventing programmers from shooting themselves in the foot(it is still possible nonetheless), but sometimes it feels people just want to complain about C++ for the sake of complaining.


I am not sure what TTCTTOU is, but what you are saying needs transaction like semantics in filesystems. So its more of a property of an FS instead of a language.

All the common FS (in linux .. ext* xfs) do not have that these.


“time to check to time to use”

As you intuited, a problem that arises in environments that lack transactions/locks/etc


> All the common FS (in linux .. ext* xfs) do not have that these

You know that files are nameless on Unix, right?


> do not have that these

I was referring to the transaction semantics


> let f = File::open("username.txt");

> /what if the file is deleted here?/

you don't need transaction semantics here.

On Unix file is basically inode with counter. If it doesn't have record (aka name) in directory but non-zero counter, it would exist.

So on open(2) counter is bumped up and is equal to 2 (or higher if there are hardlinks), one for the directory record and one for program running it.

If you remove record from directory (aka `rm filename`), counter would decrease by 1, but it is still non-zero, program still has it open, could read its content, etc. Only after close(2) counter would decrease to 0 and file would be gone.

Just tested it on Ubuntu 20.4 with simple C program


What do you imagine is a more thorough handling of the problem than forced error checking? Its not a problem that can be prevented. It's caused by an external process.


That is not a particularly good example of a TOCTTOU (2-3 Ts, not 4), but the rust standard library does fall a bit short for filesystem operations in that regard. E.g. it doesn't expose the *at syscall family some sort of directory handle. Doubly so if you want to perform the atomic write dance securely inside a specific direcory without being subject symlink substitution.


In equivalent C++ on Windows, f is a file handle without delete share permissions so the act of successful opening the file locks out delete operations until the file handle is closed, preventing TTCTTOU issues. Am assuming Rust would be backed by the same is mechanism.


On Windows Rust calls out to `CreateFileW`[0], and by default uses the `FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE` share permission[1].

[0] https://github.com/rust-lang/rust/blob/master/library/std/sr...

[1] https://doc.rust-lang.org/std/os/windows/fs/trait.OpenOption...


That's a design difference from C++ then, however even if the file is deleted the delete on Windows doesn't persist for existing open handles until the file is closed.


> what if the file is deleted here?

You know that files are nameless on Unix, right?




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: