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:
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.
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.
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.
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.
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.
I think there is no language level feature in Rust to prevent TTCTTOU. TTCTTOU can happen in both c++ and rust.
for example:
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.