Designing a Paths Julep

Recently there was an LWN news item pointing to an article on the Go blog about Traversal-resistant file APIs.

4 Likes

That’s a nice API. I’d also be nicer in Julia since we could use a do block instead of defer.

My two cents here: if Paths API has some kind of abstract interface, then maybe it can be used consistently across packages like Tar.jl, ZipArchives.jl, ZipFile.jl (maybe with some constraints)?

4 Likes

Thanks for the link! Incidentally this touches on one of the tensions in the current design: my desire to break with select conventions in the name of consistency/safety.

For instance, currently p"/some/absolute/path" * p"../../../../../../../../etc/passwd" will raise an error once we try to get the parent directory of p"/", however the “standard” behavior is for the parent of / to be / :confused:.

I particularly enjoyed reading this section:

If the attacker controls part of the local filesystem, they may be able to use symbolic links to cause a program to access the wrong file:

// Attacker links /home/user/.config to /home/otheruser/.config:
err := os.WriteFile("/home/user/.config/foo", config, 0o666)

If the program defends against symlink traversal by first verifying that the intended file does not contain any symlinks, it may still be vulnerable to time-of-check/time-of-use (TOCTOU) races, where the attacker creates a symlink after the program’s check:

Since this is exactly the issue that is systematically eliminated by requiring the use of a FD-as-Path type, as I am now experimenting with (and based on encouraging results, advocating for).

I don’t think it would be too hard to extend the proposal slightly and have rooted-ness be an extra flag that the FD-as-Path type can have, which is then automatically propagated to all path joining operations.

From reading that article, with this proposal will give us a better story with this class of vulnerabilities than Go :slight_smile:

RAII would be nice here, but in the current experiment I’m just using a finaliser. I’m not sure that do is really what we want, since it’s good if the same handle is passed around more instead of being dropped and re-acquired.

2 Likes

Sure, why not :smiley: How does julia-basic-paths/abstractpaths.jl at main - tec/julia-basic-paths - Code by TEC look to you?

It’s slightly imprecise, but I’m thinking that maybe PathHandle is the name to go with here. I think having “handle” in the name is important, and while FSHandle etc. are most accurate, by putting Path in the name we get an explicit connection to the Path type and an indication of the relationship between the two. Appearing as a completion with Path<tab> is a nice bonus. I think together this more than makes up for the loss in accuracy.

So, with this, there are three kinds of system-native paths:

  • Path as a “pure” path for the current system (an alias of PosixPath or WindowsPath)
  • PathHandle for a file descriptor/filesystem handle produced from a Path
  • DirEntry is a parent directory PathHandle combined with a relative Path and (optional) metadata.

All of these types can be converted between (e.g. PathHandle to a Path, DirEntry to a PathHandle, etc.) using the platform filesystem APIs. We can do this automatically where convenient, and require a particular type when we want to prod the user into behaving more safely (e.g. requiring them to acquire a handle, to encourage them to re-use it).

6 Likes