Open andrewrk opened 5 months ago
+1
Using this function is almost always a bug, but it is not a common knowledge (someone needs to write "realpath considered harmful" blog post). At the same time, this function lies on the path of the least resistance for a lot of problems, and routinely sneaks into all kinds of places where it is completely inappropriate.
I like that the function is preserved in the source code as compileError with an explnanatory comment. I think the comment could be better though: I'd remove the bit about "naivety", as knowing that realpath is not good is expert knowledge: in my career, I've seen this clearly articulated only twice: once in an off-hand remark by alexcrichton, and the second time in the Zig community. And I would include a bit of positive guidance here, which can be lifted straight out of the PR description. So, something along the lines of
This function is not portable, relies on a legacy permissions model, cannot handle arbitrarily nested file system paths, and is therefore typically a bug to call.
The correct behavior is usually to take note of the file paths passed in by the user. These strings must be retained whether absolute or relative, and used, rather than trying to resolve the path based on the file descriptor.
To improve software quality, Zig Standard Library omits this function and provides this educational note instead.
If we remove this function, I'd love to see https://github.com/ziglang/zig/issues/5190 fixed, as it is one of the cases where realpath
is needed as a work-around for an unrelated bug.
Using this function is almost always a bug, but it is not a common knowledge (someone needs to write "realpath considered harmful" blog post).
I could certainly write a blog post but I'd need some sources lol. On another note, I don't really understand what makes realpath not portable and a problem. Is it mainly a Windows issue?
on the portability, my guess is openbsd because windows can do it and https://github.com/ziglang/zig/issues/6718 exists; so it wouldn't surprise me if there was also difficulty in inspecting arbitrary fd's
I have no real opinion on removing the API from std.fs
, but want to note that the std.os
implementation (std.os.getFdPath
in particular) is used for testing absolute paths in std/fs/test.zig
, see https://github.com/ziglang/zig/pull/16847 for context.
There's also std.fs.selfExePath
which relies on some realpath-like functionality for certain platforms (MacOS/OpenBSD/Haiku call std.os.realpathZ
, Windows calls std.fs.Dir.realpathW
).
How to prevent path traversal attack without realpath
?
@GalaxySnail std.fs.path.resolve
and check for ../
at the beginning of the result
What would be an alternative to realpath
when talking to foreign code like C libraries or other executables without changing cwd
?
Assuming i only have a std.fs.Dir
and a relative path available, and i want to call into a foreign function that only takes char const * path
, what is the Zig way to do that then?
The only way i know of right now would be using ´setAsCwd()` and then pass the relative path, but there might be some secret knowledge here.
I guess if we deprecate that function, we should provide the users with a set of better alternatives instead of just claiming why it's a bug and that we do not support it.
I interpreted the messaging as that the use cases that still need it should use a 3rd party module
Assuming i only have a
std.fs.Dir
and a relative path available, and i want to call into a foreign function that only takeschar const * path
, what is the Zig way to do that then?
My understanding of how a "proper" program is supposed to handle this situation (please correct me if I'm mistaken) is that because you have a std.fs.Dir
handle, you presumably know where it came from. Either it's the cwd, or you opened it from a path (either relative to the cwd, or absolute). If it's the latter, the idea is that you keep the original path string in memory for as long as you need to do these kinds of operations.
If the path you want to pass to the C library function is absolute, just pass that path verbatim.
If the path you want to pass is relative, if your dir handle is the cwd you pass that path verbatim, otherwise, you pass the result of try std.fs.path.join(allocator, &.{ original_dir_path, rel_path })
where original_dir_path
is the path string the directory was opened from. If you don't need to be cautious of symlinks you could also use fs.path.resolve
.
This should work for well-behaving external libraries. If the external library is poorly designed and only accepts absolute paths or paths relative to some hard-coded but unknown directory, you have to implement the required path transformation logic yourself in a way that makes the most sense for the OSes you're targeting.
My understanding of how a "proper" program is supposed to handle this situation (please correct me if I'm mistaken) is that because you have a std.fs.Dir handle, you presumably know where it came from.
That's exactly the point. If i'm writing a library, i don't. I just receive a dirhandle+path, but no information if it's cwd-relative, root or anything, so the only two options are the ones i stated, at least to my knowledge. I cannot work on a "string" level of paths, because i don't know the root
This function is not portable, relies on a legacy permissions model, cannot handle arbitrarily nested file system paths, and is therefore typically a bug to call. Yet, many programmers reach for this function out of naivety. To prevent it from cropping up in various codebases, and causing an overall degradation of software quality, omit it from the Zig Standard Library.
Currently used by the zig compiler in several places:
All of these instances have to do with debug info, except for the last line which I kept for the humor value. They are indeed all bugs. The problem is that the debug info in an executable needs to refer to the file paths of source locations, and if it uses relative paths, then a debugger looking at a moved executable will not be able to find the source files.
However, it is still incorrect to attempt a transformation of a relative path to an absolute path. Instead, the compiler needs to take note of the file paths passed in by the user. These strings must be retained whether absolute or relative, and used, rather than trying to resolve the path based on the file descriptor.
This pushes the problem back into the build system which constructs the CLI for the compiler. But that's precisely how to solve this problem - push it as far back as possible, until there is exactly one realpath() transformation done at the very beginning of the software, ideally by the user themselves.
I expect this to be a controversal proposal.