|
|
Subscribe / Log in / New account

The trouble with symbolic links

July 7, 2022

This article was contributed by Chris Riddoch

At the 2022 sambaXP conference, Jeremy Allison gave a talk titled "The UNIX Filesystem API is profoundly broken: What to do about it?". LWN regulars may recall hints of these talks in a recent comment thread. He started his talk with the problems that symbolic links ("symlinks") cause for application developers, then discussed how the solutions to the problems posed by symlinks led to substantial increases in the complexity of the APIs involved in working with pathnames.

Allison explained that hard links were the first "interesting addition" to the original Unix filesystem API; unlike symlinks, though, they are not dangerous, and are, in fact, easy to use. A hard link is simply the connection between a directory entry and the inode for the file (or directory) to which that entry refers. Unix systems allow multiple links to any file, but require that the inode and directory entries all reside on the same filesystem.

By contrast, symlinks contain another path as data, and the kernel transparently operates on the file at that path when system calls like open() or chown() are called on the symlink. This seemingly innocuous feature led to the addition of incredible amounts of complexity in the effort to fulfill the needs of programs that need to be aware of whether a pathname contains a symlink or not. Such programs include archival programs like tar, file synchronization and transfer programs such as rsync, network filesystem servers like Samba, and many more that suffer security problems as a result of not giving sufficient attention to symlinks in pathnames.

The variety of security problems resulting from symlinks can be seen in a search of CVE entries, which gave Allison 1,361 results when he ran it. These include vulnerabilities that facilitate information disclosure, privilege escalation, and arbitrary file manipulation including deletion, among other attacks. Without discussing any specific CVE in detail, he gave an example of the kind of security problem that can result from symlink-related vulnerabilities.

An application running as root may try to check that /data/mydir is a regular directory (not a symlink) before opening the file /data/mydir/passwd. In between the time the program does the directory check and the file open, an attacker could replace the mydir directory with a symlink to /etc, and now the file opened is, unexpectedly, /etc/passwd. This is a kind of race condition known as a time-of-check-to-time-of-use (TOCTOU) race.

Symlinks and complexity

Symlinks were created, Allison theorized, because hard links are restricted to linking within the same filesystem, so only symlinks (which lack that restriction) could be used if an administrator wanted to add new storage media without changing the paths to users' data. He quoted an advertisement for 4.2BSD, which touted, "This feature frees the user of the constraints of the strict hierarchy that a tree structure imposes. This flexibility is essential for good namespace management."

The addition of symlinks led to the lstat() system call, which provided the means to identify whether the last component in a pathname is a symlink. This was, unfortunately, insufficient for handling symlinks pointing to directories earlier in the path, he explained. An application could attempt to check each component of the path individually, but not atomically — another application could make a change to one of the components during this process, leading to security vulnerabilities.

An option to the open() system call, O_NOFOLLOW, exhibits the same problem as lstat(). O_NOFOLLOW instructs the system call to fail with ELOOP if the last component in the pathname is a symbolic link, but it only checks the last component. The realpath() C library function follows symlinks in a path and produces an absolute, canonical pathname that the application can then compare with the original. Allison described this as an appealing but incorrect solution to the problem. Another process could make a change in between the time realpath() is called and another function is used to manipulate the file in some fashion. In other words, the same TOCTOU race applies here.

Allison said that the openat() system call was designed as a solution to this problem; it introduces the idea of referring to files with respect to a directory that's indicated by an already-open file descriptor. The only reliable way to identify a file's path is to walk the hierarchy using multiple calls to openat(). Everything else would be vulnerable to race conditions.

But Allison also pointed out the flaw in this technique. "You cannot create a new directory with open(), you cannot remove a file, unlink a file, or delete a directory with an open() call." So, more functions following the pattern of openat() had to be created: mkdirat(), linkat(), unlinkat(), renameat(), and more. Some are still missing, like getxattrat() and setxattrat(). Some, like fchownat() and faccessat(), don't follow the pattern cleanly.

Allison didn't mince words: "So our original clean and beautiful POSIX filesystem API doesn’t look very clean and beautiful anymore...pathnames as a concept are now utterly broken in POSIX." One could reasonably attribute, in part at least, any perceived bitterness to Allison's struggles with the long road to a fix for CVE-2021-20316 in Samba.

Because of the talk's focus on the role of symlinks in complicating the Unix pathname API, Allison did not directly raise the point that race conditions involving pathnames can occur even without symlinks. It seems a major source of complexity is the lack of a mechanism for atomically batching together operations that involve walking directories and symlinks to eventually perform some operation on a file.

Workarounds

Allison then explained the use of the O_PATH flag to open(), which will return a file descriptor that is only useful for passing to the *at() system calls as the dirfd argument. Unfortunately for Samba, file descriptors opened with O_PATH cannot be used for reading or writing extended attributes. He found a workaround, demonstrated by a snippet of code that he described as "one of the most beautiful hacks I’ve ever seen, it’s so ugly it makes you want to vomit, but it’s amazing."

    int fd = openat(dirfd, "file", O_PATH|O_NOFOLLOW);
    sprintf(buf, "/proc/self/fd/%d", fd);
    getxattr(buf, ea_name, value, size);

The contents of /proc/self/fd are symlinks that represent every file descriptor the process has open. Allison explained the code: "If you print into a buffer '/proc/self/fd/' and then the number of the descriptor you just got back from O_PATH, you can then pass it to getxattr() or setxattr() as a path, and it can’t be symlink-raced." He wasn't sure whether to attribute this code to Android or Red Hat developers, but a similar use of /proc/self/fd/ can be found in the open() man page.

Allison reiterated the main point of his talk: "The concept of pathnames is unusable on POSIX, completely. For a non-trivial application, for a regular person writing code on POSIX, you will have symlink races in your code."

Examples of (since fixed) CVEs were then provided, including one in the Rust standard library, which was discussed extensively here. In the last few minutes of the talk, Allison noted several proposed solutions offered by LWN readers, including a special prctl() call and restrictions on when non-root symlinks are followed. He said that the MOUNT_NOSYMFOLLOW mount option, which simply forbids following symlinks within a filesystem, is his preferred solution: "It’s perfect. It does exactly what we need." Allison's talk concluded on that point.

While it certainly seems desirable to forbid symlinks in the name of cleaning up the POSIX API, they are a frequently used system-administration tool. Several popular "symlink managers" exist. Gnu Stow, for example, provides a way for administrators to install programs into a new directory hierarchy, such as /usr/local/stow/packagename-version/, and then create forests of symlinks from /usr/local/bin/example to /usr/local/stow/packagename-version/bin/example, using the minimum number of symlinks necessary. This makes it possible to "uninstall" a package simply by removing the symlinks with the help of stow -D.

The /etc/alternatives system created by Debian allows administrators to switch between substitutable packages in a similar manner without forcing the uninstallation or reinstallation of either package. In a similar vein, the Nix and Guix distributions make heavy use of symlinks — a Guix profile consists of a tree of symlinks to packages installed within /gnu/store/, making it easy to switch between grouped combinations of specific versions of packages.

Banning symlinks entirely would break these use cases, but restricting their creation to the root user would most likely suffice. Users may still have other legitimate needs for symlinks, though, and substantially restricting them would likely be an unpopular change.

SambaXP has made the talk's video and slides available.


Index entries for this article
KernelSymbolic links
GuestArticlesRiddoch, Chris


to post comments

The trouble with symbolic links

Posted Jul 7, 2022 14:54 UTC (Thu) by kees (subscriber, #27264) [Link] (4 responses)

The trouble with symbolic links

Posted Jul 7, 2022 17:02 UTC (Thu) by adobriyan (subscriber, #30858) [Link]

Should be nosymlinkfollow but naming is the hardest part.

The trouble with symbolic links

Posted Jul 8, 2022 1:10 UTC (Fri) by himi (subscriber, #340) [Link] (1 responses)

Damn . . . I scrolled down to the bottom of that changeset and was like "huh? where's the rest?" Is that literally all that's required to implement this properly?

The trouble with symbolic links

Posted Jul 8, 2022 14:54 UTC (Fri) by harisphnx (subscriber, #139363) [Link]

An example of a good design?

The trouble with symbolic links

Posted Jul 11, 2022 13:07 UTC (Mon) by scientes (guest, #83068) [Link]

This is what Sys 4 UNIX *didn't* do because they were too lazy to update all the binaries, and instead preferred to change the data structure through extension wihout thinking out the consequences. Nothing new.

The trouble with symbolic links

Posted Jul 7, 2022 15:32 UTC (Thu) by Sesse (subscriber, #53779) [Link] (13 responses)

I believe it's a bit of a stretch to say that “pathnames as a concept are now utterly broken in POSIX” just because userspace cannot verify that a path name is contained within some arbitrary part of the file system. Most applications will not have to care whether there's a directory symlink in the path, even if Samba needs to do its own userspace chroot.

The trouble with symbolic links

Posted Jul 7, 2022 16:07 UTC (Thu) by khim (subscriber, #9252) [Link] (11 responses)

> I believe it's a bit of a stretch to say that “pathnames as a concept are now utterly broken in POSIX” just because userspace cannot verify that a path name is contained within some arbitrary part of the file system.

It's like saying that car without wheels and an engine is not broken. I mean: you can still pull it with enough horses attached thus move people and cargo, right? So it's still usable, kinda.

Similarly here: the only difference between pathname and filename is the fact that pathnames have hierarchy.

Sure, apps which don't need pathnames but are just happy to use them as opaque file identifiers in a flat namespace are not broken with symlinks. But everything else is broken is a sense that Joe Average Developer would write incorrect code 10 times out of 10.

How you can call this “not utterly broken” is beyond me.

> Most applications will not have to care whether there's a directory symlink in the path, even if Samba needs to do its own userspace chroot.

On the contrary: practically any app which deals with file hierarchies (and just with individual files) is broken. Not all such programs lead to security vulnerability because sometimes you have situation “Joe can break Joe's program which s/he can crash directly anyway” which is considered acceptable. But they are broken, nonetheless.

The trouble with symbolic links

Posted Jul 7, 2022 16:09 UTC (Thu) by Sesse (subscriber, #53779) [Link] (3 responses)

I guess the disparity here is how many apps you think really needs to care. Most that I use certainly don't. File servers (including Apache) are a notable exception.

The trouble with symbolic links

Posted Jul 8, 2022 8:57 UTC (Fri) by gerdesj (subscriber, #5446) [Link] (2 responses)

"File servers (including Apache) are a notable exception."

That's quite a large notable exception!

The trouble with symbolic links

Posted Jul 9, 2022 0:47 UTC (Sat) by willy (subscriber, #9762) [Link]

If you're NetApp, sure. But if you consider desktop (or phone) usage of Linux, it's a small and uninteresting exception.

The trouble with symbolic links

Posted Jul 23, 2022 0:17 UTC (Sat) by DimeCadmium (subscriber, #157243) [Link]

They're also already large projects in and of themselves. Handling symlinks is still ultimately a very minor portion of the code.

The trouble with symbolic links

Posted Jul 7, 2022 17:01 UTC (Thu) by adobriyan (subscriber, #30858) [Link] (4 responses)

> But everything else is broken is a sense that Joe Average Developer would write incorrect code 10 times out of 10.

What are the requirements for, say, non-broken text editor wrt symlinks?

The trouble with symbolic links

Posted Jul 8, 2022 13:00 UTC (Fri) by ilammy (subscriber, #145312) [Link] (3 responses)

If you open a file in multiple tabs via multiple paths, edits in one of them should reflect in the other, immediately.

If a symlink in the open path has changed while a file is being edited, users might or might not want to be notified about that instead of overwriting some other file.

If there is a symlink loop, search by all files in the project should not hang or print out infinite results.

The trouble with symbolic links

Posted Jul 8, 2022 13:05 UTC (Fri) by Sesse (subscriber, #53779) [Link]

I find this example pretty contrived, but OK: stat() the file and check its inode number. (There's no new TOCTTOU, since editors typically don't keep the file descriptors open anyway.)

The trouble with symbolic links

Posted Jul 8, 2022 20:24 UTC (Fri) by nybble41 (subscriber, #55106) [Link]

The first two cases would also be an issue with hard links. You can't assume that a different path means a different file. And all three are possible with bind mounts, network filesystems, FUSE filesystems, or removable devices without involving symlinks in any way, so you need to handle those cases properly whether or not symlinks are available. For example the third case could be addressed by checking the device and inode numbers for each directory to prevent recursion, and also setting an absolute limit on the depth of the search.

The trouble with symbolic links

Posted Jul 8, 2022 22:26 UTC (Fri) by k8to (guest, #15413) [Link]

This is .. not standard text editor behavior.

The trouble with symbolic links

Posted Jul 7, 2022 17:54 UTC (Thu) by jthill (subscriber, #56558) [Link] (1 responses)

Is the concept of user input utterly broken because little bobby tables is such a hellraiser? I don't think so, I don't see how pointing to all the CVEs for failure to scrub user input is any different than pointing to all the CVEs for failure to scrub paths.

Besides, open and fstat the path's final directory, if getuid() owns it you don't even have to vet whatever real path you'd need to get you there, then either you don't follow final symlinks or you iteratively re-vet what readlinkat() for them gets you; and if when you later decide to rewrite your hosts file the path it's going to isn't a previously-vetted device and inode you'd would be well within reason to just refuse to continue.

Instead of blackholing symlinks a quick little library to implement the operations in terms of those safety checks seems reasonable; things like ssh and samba for which those aren't a enough are distinctly not Joe Average Coder projects. `std::filesystem` should probably operate by default only with getuid()-owned files anyway.

The trouble with symbolic links

Posted Jul 7, 2022 22:39 UTC (Thu) by khim (subscriber, #9252) [Link]

> Is the concept of user input utterly broken because little bobby tables is such a hellraiser?

The concept of injecting user input into a string is utterly broken, yes. That's why we have entirely different API which solves that issue once and for all. We don't have anything similar for pathnames.

> Besides, open and fstat the path's final directory

Have you actually read the article? Even finding the path's final directory is quite non-trivial.

> Instead of blackholing symlinks a quick little library to implement the operations in terms of those safety checks seems reasonable

Make one. Give it to Jeremy and we can make a bet about how many ways it's broken.

The trouble with symbolic links

Posted Jul 8, 2022 2:32 UTC (Fri) by k8to (guest, #15413) [Link]

Very much this.

99% of the time, you can just ignore them entirely as a developer and everything works.

It's true that once developers start trying to care about symbolic links explicitly they often make many errors. Usually it's enough to just use libc's realpath and consider the final answer. Trying to care about symbolic links in more detail is usually a path to madness.

For some problems, there are reasonable patterns, like writing code to "monitor" a directory and its contents, you usually don't want to follow links at all to avoid infinite loops etc, with limited user-provided exceptions.

For other problems, there may not be a good solution. I haven't worked on any problems like that in my long programming career, however.

The trouble with symbolic links

Posted Jul 7, 2022 15:48 UTC (Thu) by iabervon (subscriber, #722) [Link] (6 responses)

I think it would make sense to have the kernel not follow symlinks when doing path-based operations in general, and have user space libraries, when doing things that should possibly be affected by symlinks, call realpath(), validate the result as appropriate for having just been given the path from a possibly-malicious source, and use the result, which will probably be a path that works without the kernel following symlinks (or will fail safely).

I could imagine a C library change that would set the prctl and handle symlinks in user space with realpath() for the traditionally-named path-based functions, and include variants that you use instead if you're going to validate the path at all.

The trouble with symbolic links

Posted Jul 7, 2022 16:11 UTC (Thu) by khim (subscriber, #9252) [Link]

This would have worked great if we had a time machine and had the ability to go back to 1991 or 1992 to change Linux API.

I don't think we have this option today.

The trouble with symbolic links

Posted Jul 7, 2022 16:23 UTC (Thu) by nix (subscriber, #2304) [Link] (3 responses)

That requires all userspace programs not only to follow symlinks but to do so recursively (since any component of a path may be a symlink), and to do so in a race-free fashion. They also all need to spot loops.

How many of them do you imagine will get *that* right, given how many bugs and races there have been in, say, rm -rf, which you'd think is heavily tested? Oh look, a new set of security holes, probably worse than the last lot.

-- N., very heavy user of symlinks, thank god for Nix (the distro, no relation) making sure that not too many break when entire /usr trees are symlink farms

The trouble with symbolic links

Posted Jul 7, 2022 17:38 UTC (Thu) by pbonzini (subscriber, #60935) [Link] (1 responses)

At least in theory libc could be doing all that. However it would also be much slower and wasteful, because all the caching that the kernel does would be split across multiple userspace programs, and the cache would start cold for every new process.

The trouble with symbolic links

Posted Sep 3, 2022 11:56 UTC (Sat) by nix (subscriber, #2304) [Link]

In theory libc could implement its own cross-process cache using shared memory, like nscd. In practice, the mere thought is painful. This is definitely not userspace's job :)

The trouble with symbolic links

Posted Jul 7, 2022 19:33 UTC (Thu) by iabervon (subscriber, #722) [Link]

I imagine that either all userspace programs will get recursively following symlinks right, or they'll all get it wrong, since I think it would be done in libc and they'd all use the same implementation. For that matter, I think the implementation should probably be a system call that takes a path that may use symlinks and returns a path that doesn't use symlinks, in which case everyone would end up using the correct implementation that the kernel uses.

The only change I can see to outcomes of path-based operations from having separate resolve and operate syscalls would be that, if you rename a symlink over a regular file, a poorly-timed open() could get ELOOP instead of either getting the original file or the target of the symlink. Replacing one symlink with another would be atomic, and any other operation isn't atomic today (that is, you can't replace a directory with a symlink atomically, and you can't replace both a symlink and another file atomically; I guess you might get ELOOP when you could only have gotten ENOENT).

As far as changes to userspace program code, the only one would be that, if you've explicitly called realpath() yourself and validated the result in some way, you'd call realopen() on the string you validated rather than calling open() and getting another round of symlink resolution that might be different.

The trouble with symbolic links

Posted Jul 7, 2022 18:56 UTC (Thu) by bartoc (guest, #124262) [Link]

this is a bad idea. You want to be able to use symlinks to point an unsuspecting application somewhere else. The trouble comes when the unsuspecting app has a higher privilege level than the symlink creator.

The trouble with symbolic links

Posted Jul 7, 2022 17:40 UTC (Thu) by sbaugh (guest, #103291) [Link] (13 responses)

Plan 9 doesn't have symlinks. Their alternative is bind mounts, which can be created without privileges in Plan 9 since every process runs in its own private mount namespace, which seems like a much better approach. See e.g. https://9p.io/sys/doc/lexnames.html

The trouble with symbolic links

Posted Jul 7, 2022 20:09 UTC (Thu) by wahern (subscriber, #37304) [Link] (12 responses)

If we apply the same premises by which symlinks are judged insecure, user bind mounts are in many respects even *more* insecure. OTOH, such systems tend to disabuse you of those flawed premises, particularly the premise that a path name says anything of substance about authorization.

The trouble with symbolic links

Posted Jul 7, 2022 21:33 UTC (Thu) by neilbrown (subscriber, #359) [Link] (11 responses)

> If we apply the same premises by which symlinks are judged insecure, user bind mounts are in many respects even *more* insecure.

But it isn't just bind mounts, it is bind mounts in a private namespace. I don't think they are insecure.

The insecurity of symlinks is that if I create a symlink you have to follow it. When I create a bind mount in a private namespace you can't even see it let alone follow it

The trouble with symbolic links

Posted Jul 7, 2022 22:15 UTC (Thu) by willy (subscriber, #9762) [Link] (10 responses)

They're not insecure in the context of plan9. They're horrendously insecure in Unix with our setuid executables.

The trouble with symbolic links

Posted Jul 7, 2022 22:43 UTC (Thu) by khim (subscriber, #9252) [Link] (1 responses)

It all comes down to the original sin: the ability of less-privileged programs to dictate the worldview of more privileged programs.

This is fixable (Android, macOS, Windows, some Linux distributions are trying to fix that) but I wonder how much time would it take. Years? Decades?

The trouble with symbolic links

Posted Aug 5, 2022 16:11 UTC (Fri) by immibis (subscriber, #105511) [Link]

Oftentimes the privileged program is expected to follow the symbolic link - what then?

The trouble with symbolic links

Posted Jul 7, 2022 22:58 UTC (Thu) by Hello71 (subscriber, #103412) [Link] (6 responses)

i'm more inclined to agree with the proposition advanced by e.g. Rich Felker that actually suid executables are profoundly broken. privilege escalation should occur via communication with a system daemon using a well-defined protocol, not a partially-privileged process with ad-hoc environment inheritance rules. executing a suid program resets (at least) two prctl flags; without looking at the manual, which ones? actually, i'm not sure there's anything theoretically preventing adding mount namespaces to the long list of reset-on-suid-exe states.

The trouble with symbolic links

Posted Jul 8, 2022 5:13 UTC (Fri) by matthias (subscriber, #94967) [Link]

> actually, i'm not sure there's anything theoretically preventing adding mount namespaces to the long list of reset-on-suid-exe states.

Are you suggesting that if someone calls a suid binary inside a container, the binary should not use the mount namespace of the container but the root mount namespace instead?

The trouble with symbolic links

Posted Jul 8, 2022 6:35 UTC (Fri) by neilbrown (subscriber, #359) [Link]

> suid executables are profoundly broken

That's exactly the point I was going to make. bind mounts in private namespaces are perfectly safe. setuid programs as profoundly broken, whether you use namespaces or not.
If we, collectively, were serious about security, we would be mounting all filesystems with nosuid.

The trouble with symbolic links

Posted Jul 8, 2022 10:16 UTC (Fri) by tialaramex (subscriber, #21167) [Link] (2 responses)

"SUID is profoundly broken" I find a lot easier to get behind than "symbolic links are the problem".

One of the things I appreciate in Rust is that where there are special cases somebody takes the time to think about whether in fact there's some more general principle of which the special case was just the easy to observe part, the tip of the iceberg so to say, and to provide for the general principle, rather than just the special case.

Example: If you're a Rust beginner you learn that Option's None and Result's Error can be passed back up to your caller where appropriate with the '?' try operator and this seems like a special case, you can't go around treating an integer, or a file handle, or some other kind of thing this way. Or can you? In fact the general principle is the (nightly gated) Try trait, and Option and Result merely implement Try (they're allowed to do this even in stable Rust because Rust internally isn't subject to its own stability rules or it'd be recursively impossible to develop anything)

Another example: In languages with ad hoc polymorphism ("Function overloading") it's obvious that "LWN.net".contains("LWN") and "LWN.net".contains('.') are both reasonable things to write so you just overload the function to do either. Rust doesn't have ad hoc polymorphism, so it must decide what type that parameter is such that both "LWN" and '.' match or else provide two functions with different names. The resulting trait Pattern once again represents a more general principle - any pattern which could match parts of a string, and so in Rust you can also write "LWN.net".contains(char::is_alphabetic) because hey, char::is_alphabetic is a function which matches parts of a string so why not.

I think SUID just isn't one of those things, it's a profoundly special case, and so we should immediately be prejudiced against it as a design feature of the operating system. Rather than everything just falling out naturally, as we'd like for a general solution, SUID means everywhere we need to handle this differently. If you've just written a bunch of low level code there's a good chance that if I remind you that SUID exists you'll need to go back and add a bunch of conditional branches to handle that...

The trouble with symbolic links

Posted Jul 8, 2022 11:25 UTC (Fri) by farnz (subscriber, #17727) [Link] (1 responses)

Another reason to be biased against SUID is that it's a quick solution to the problem of users not having root access to the entire machine.

There are three different ways to run something as root on a Linux-like system (assuming that you've got an ordinary user account):

  1. Get someone to log in as root and run the command directly for you.
  2. Send a message over a suitable transport to something that runs the process for you - AF_UNIX sockets, SSH to localhost or similar, authenticating you and deciding whether you can do that.
  3. Get someone to make the command SUID and run it yourself.

The first is a non-starter if you're not trusted with root yourself - the coordination to keep someone with root access around is a pain.

The second involves more code. You have to have something running that can make the policy decisions based on a string sent to it, and then execute the process for you. There's also complexity around transferring "enough" environment state to the privileged process, and transferring results back.

The third is easy. Most of your SUID process's state is the same as it would have if it wasn't SUID, except for a few "minor" bits here and there (and this list of things is growing as we find security holes that cannot be fixed except by resetting state).

Ideally, everyone would bother to do the second option for things that need to be privileged - but that's a lot of work, and SUID is a "neat hack" that means you don't need to do it.

The trouble with symbolic links

Posted Jul 9, 2022 20:19 UTC (Sat) by NYKevin (subscriber, #129325) [Link]

The thing is, between systemd and polkit, option 2 is far easier now than it has ever been in the past. I think we are pretty much at the point where suid could just be outright deprecated and it wouldn't be that big of a deal. The anti-systemd crowd would not like that, of course, but what else is new?

The trouble with symbolic links

Posted Jul 8, 2022 12:32 UTC (Fri) by hallyn (subscriber, #22558) [Link]

> actually, i'm not sure there's anything theoretically preventing adding mount namespaces to the long list of reset-on-suid-exe states

User namespaces are designed such that this is supposed to not be necessary: you can only mount things if you create a new user namespace, and if you are fully unprivileged, you can only map ns-root to your own or a delegated uid, so you cannot trick host root with bind mounts and a setuid-root binary. The problem with user namespaces comes because user namespaces also want to grant ns-root privilege over the resources owned by the creator. And they do that pretty well, it's just that that expands the amount of kernel code which an unprivileged user can exercise, and exploit.

> privilege escalation should occur via communication with a system daemon using a well-defined protocol

Several people have tried to the plumbing needed for plan-9 factotum functionality into the kernel. My last attempt (expanding on Ashwin Ganti's original code) was 10 years ago - https://lore.kernel.org/all/20100427164139.GA7359@us.ibm.... , and there was a more recent independent patch posted at https://lore.kernel.org/all/20180210165845.18852-1-metux@... .

The trouble with symbolic links

Posted Jul 12, 2022 8:10 UTC (Tue) by koverstreet (✭ supporter ✭, #4296) [Link]

It's not just suid executables, it's any time you're using the filesystem across a permissions boundary - any time you have one program walking/reading/writing filesystem paths controlled by another user.

Which is pretty huge.

The trouble with symbolic links

Posted Jul 7, 2022 20:27 UTC (Thu) by developer122 (guest, #152928) [Link] (4 responses)

Could this have been avoided if creating a symlink required permissions in both the source and destination directories?

(I suppose this would require a filesystem to be checked and it's symlinks validated/invalidated during mounting)

The trouble with symbolic links

Posted Jul 7, 2022 22:55 UTC (Thu) by Hello71 (subscriber, #103412) [Link] (3 responses)

sure, but also the permissions on the destination and all of its parents can never be changed during the lifetime of the link, implying that for each file and directory, either a list of all links to it and its children must be stored, or whenever its permission is changed, the whole filesystem must be walked.

The trouble with symbolic links

Posted Jul 8, 2022 9:01 UTC (Fri) by taladar (subscriber, #68407) [Link]

Not necessarily. Technically it would be enough to keep a reference counter and only allow permission changes when it is 0. The issue with that would be that it could go out of sync with the actual number of links pointing to the directory in certain situations but that is the same with the list.

The trouble with symbolic links

Posted Jul 8, 2022 21:12 UTC (Fri) by developer122 (guest, #152928) [Link] (1 responses)

That doesn't actually sound like a problem.

If you're giving someone permission to make a simlink in a folder, then why care about any of it's parents? How is it different than regular wread/write access to the folder? (you'd be able to make/change files there anyway) Wouldn't altering/removing a parent break the simlink during lookup?

The trouble with symbolic links

Posted Jul 8, 2022 23:12 UTC (Fri) by developer122 (guest, #152928) [Link]

Thinking about it some more, I suppose you could change the ownership of the containing folder, create *another* symlink to it, and then optionally change the ownership back. Then the file is effectively double-mapped. There might be some way around this by checking the ownership at runtime or some other extra checks or reference counters, but it definitely needs to be considered carefully.

I still don't see what the issue is with altering parent directories of the folder in question though, or what special capability symlinks add to that scenario.

The trouble with symbolic links

Posted Jul 7, 2022 22:51 UTC (Thu) by zaitseff (subscriber, #851) [Link] (7 responses)

As Chris points out, the problem is not so much symlinks as Time Of Check to Time of Use (TOCTOU). Take, for example, a problem I've faced writing a simple C program:

  1. I want my program to read and store user data/configuration.
  2. The traditional place for that data (over the thirty years of the program so far) has been ~/.PROGRAM.
  3. Modern XDG systems use ~/.local/share for such purposes (actually, possibly using XDG_DATA_HOME from the environment).
  4. I'd like my program to continue to use ~/.PROGRAM if it already exists, otherwise use ${XDG_DATA_HOME}/PROGRAM if $XDG_DATA_HOME is set (creating the directory, and any parents as required, if it doesn't exist), otherwise use ~/.local/share/PROGRAM (creating the directory, and any parents as required, if it doesn't exist).

The naïve way would be to code up something like the following pseudo-C for determining which directory to use:

    const char *traditional_path = "/home/john/.PROGRAM";    /* Just for testing */
    const char *xdg_default_path = "/home/john/.local/share/PROGRAM";
    const char *xdg_data_home = getenv("XDG_DATA_HOME");
    char *xdg_path;

    if (stat(traditional_path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) {
        return traditional_path;
    } else if (xdg_data_home != NULL && *xdg_data_home != '\0' && *xdg_data_home == '/') {
        /* Use xdg_data_home + "/PROGRAM"; */
        xdg_path = strcat_malloc(xdg_data_home, "/PROGRAM");
        return xdg_path;
    } else {
        return xdg_default_path;
    }

And there you have it: a TOCTOU error (check for /home/john/.trader before creating the final target path using mkdir() later). No symlinks need be involved.

Now you can merrily post your solution to the above problem! :-)

The trouble with symbolic links

Posted Jul 7, 2022 23:04 UTC (Thu) by Paf (subscriber, #91811) [Link]

For a preferences file, I wouldn’t solve it. ;)

Now for something else which was security critical …………….. eek

The trouble with symbolic links

Posted Jul 7, 2022 23:06 UTC (Thu) by zaitseff (subscriber, #851) [Link] (4 responses)

And I should mention that, as an additional wrinkle/challenge, any solution must be portable to the latest versions of FreeBSD, OpenBSD, NetBSD, macOS, Solaris, ... — any POSIX environment.

The trouble with symbolic links

Posted Jul 7, 2022 23:57 UTC (Thu) by mpr22 (subscriber, #60784) [Link] (3 responses)

It's worth noting that openat(), mkdirat(), and fdopendir() were adopted in POSIX.1-2008.

The trouble with symbolic links

Posted Jul 8, 2022 0:46 UTC (Fri) by zaitseff (subscriber, #851) [Link] (2 responses)

Indeed. However, I can't quite see how to use these: something I didn't mention is that mkdir() is only run if writing a data file: if no data file is saved (a user-initiated operation), no directory is created. So I'm not sure how to avoid the TOCTOU problem in such a situation.

The trouble with symbolic links

Posted Jul 8, 2022 1:20 UTC (Fri) by mpr22 (subscriber, #60784) [Link] (1 responses)

  1. Any time you access a file without knowing which data directory to use, search for your preferred data directory with open() and fstat().
  2. If you find one of your desired directories, retain the file descriptor as well as the pathname, and use openat() with the file descriptor for all further activities relating to your preferred data directory.
  3. If you don't find any of the desired data directories, then don't retain the result at all; next time you try to access a file, start fresh from step 1.

Safely creating the directory if it doesn't exist is still kind of a pain in the neck, because you can't combine the operations of "make a directory" and "open a file descriptor pointing to the directory" into a single indivisible syscall.

The trouble with symbolic links

Posted Jul 8, 2022 4:16 UTC (Fri) by magfr (subscriber, #16052) [Link]

And neither the cd nor the mkdir shell builtin provides a flag to create a directory and chdir to it in one operation.
Probably for that exact reason but I have wished for it at times.

The trouble with symbolic links

Posted Jul 8, 2022 2:39 UTC (Fri) by k8to (guest, #15413) [Link]

I mean the usual solution for something along these lines is not to pass a computed path for later opening but rather open the file and pass that.

But if you want to have a problem have a stable dir it settles on and uses consistently, you've kind of designed a TOCTOU problem into the concept of your program or library. At least with classic system calls.

Of course *at calls allow one to do precisely what I implied. Open the dir, and keep it around for use. With pipe magic you can even eliminate TOCTOU across multiple processes.

The trouble with symbolic links

Posted Jul 8, 2022 2:21 UTC (Fri) by felixfix (subscriber, #242) [Link] (3 responses)

"Symlinks were created, Allison theorized, because hard links are restricted to linking within the same filesystem"

Hard links are also restricted to regular files, or at least to not being a directory. I checked just now, and almost all my symlinks are to directories.

The trouble with symbolic links

Posted Jul 8, 2022 8:36 UTC (Fri) by nim-nim (subscriber, #34454) [Link] (1 responses)

Yes a lot of those exist because people can’t agree on the correct file layout, app writers adhere to different theories, so the only way to make a bunch of unix apps behave more or less the same (becoming manageable) is to fool them via symlinks.

Windows uses magic registry entries for the same need, it works about as ugly.

The trouble with symbolic links

Posted Jul 8, 2022 10:28 UTC (Fri) by grawity (subscriber, #80596) [Link]

Even Windows (after the Vista migration to space-less directories) now ships with symlinks like ~\Application Data to ~\AppData\Roaming, because programs can't be bothered to check environment and just hardcode paths...

The trouble with symbolic links

Posted Jul 22, 2022 7:40 UTC (Fri) by callegar (guest, #16148) [Link]

Hard links are also way more "symmetric" and the inherent "asymmetry" in symbolic links has value and usage.

another level of indirection

Posted Jul 8, 2022 7:51 UTC (Fri) by rgb (subscriber, #57129) [Link]

You could probably make the same argument about almost any kernel feature: virtual memory, name spaces, virtual filesystem, snapshots, ...
“All problems in computer science can be solved by another level of indirection." And each level of indirection creates another level of pain.
The alternative would be to still run on the bare metal writing assembly I guess.

The trouble with symbolic links

Posted Jul 9, 2022 0:27 UTC (Sat) by pj (subscriber, #4506) [Link]

Gnu Stow is the least of the users... consider NixOS, Nix and Guix.

The trouble with symbolic links

Posted Jul 9, 2022 3:31 UTC (Sat) by Subsentient (guest, #142918) [Link] (1 responses)

I *love* symlinks as they are, and I use them constantly, but the author makes some good points, especially with the race condition. This should all be fixable with new system calls/libc functions. Please don't mess with symlinks in general.

I personally don't *want* most userspace programs to be able to tell if something is a link. I've much more often had issues with a program that saw a symlink and refused to dereference it, than I ever have with a program that used them implicitly.

Note to other userspace developers: Please use stat() instead of lstat() whenever possible. You don't know if there's a good reason it's a symlink, for example, a .config folder could be pointing into an sshfs network drive so my settings are synced between machines. I put symlinks in weird places if I think they're useful, and the only times it hurts me are when some application is using lstat() and/or looking for symlinks some other way.

The trouble with symbolic links

Posted Jul 9, 2022 11:55 UTC (Sat) by mathstuf (subscriber, #69389) [Link]

> Please use stat() instead of lstat() whenever possible.

One place you can't is when asking "does this path exist?" because `stat` will return "no" for broken (or looped) symlinks while `lstat` will say "yes".

The trouble with symbolic links

Posted Jul 11, 2022 20:00 UTC (Mon) by ma4ris5 (guest, #151140) [Link] (2 responses)

Here is one conceptual way to _open up a file for read_ in a safe way

It is possible to validate a path with file descriptors,
using openat() family of functions, to open folders starting from root into the actual file,
and then _from the directory_, that contains the file, back to root folder with ".." traversal.
The inode numbers should match this validation.

"_from the directory_" can be checked this way:
Somehow discover the folder that contains the non-soft linked file.
Both directory and file must be fd-opened, and directory must contain that non-soft link file.

"_from the directory_" physical path can be discovered in a way "pwd -P" does it:
Fork, forked program must set working directory as the directory fd.
Then forked program can read /proc/self/cwd and print it for the caller process.

There are some other details that must be taken care too (signal handling, unrelated file descriptors),
to make fork work robustly, but that is out of scope for now.

For opening file for write can be done with the above elements too: grab a validated folder,
open non-soft linked file there.

The trouble with symbolic links

Posted Jul 12, 2022 3:26 UTC (Tue) by neilbrown (subscriber, #359) [Link] (1 responses)

> Here is one conceptual way to _open up a file for read_ in a safe way

Or you could use realpath() to expand all the symlinks, validate the path in whatever way you care about, and then use openat2() with RESOLVE_NO_SYMLINKS.
If any symlinks have been inserted into the path, the open will fail.

(requires Linux 5.6 or later, glibc doesn't have a wrapper)

The trouble with symbolic links

Posted Jul 15, 2022 6:10 UTC (Fri) by ma4ris5 (guest, #151140) [Link]

Thanks for the tip. Sounds sane: First remove symbolic links, then expect that there are no symbolic links.

With all uncertainty, the approach that I mentioned, is to first hold file descriptor to something
that is nearest of the goal operation.
File descriptor is stable (Kernel guarantees that), so holding it prevents many kinds of race conditions.
Then validate the opened file descriptor based on trusted sources for security reasons.

After gaining trust for a folder file descriptor, use it in safe ways (for example create a file with symbolic links turned off).

So from Samba developer, /proc/self approach works for solving the real path for validation:

fd = openat(-1, "/bin", O_DIRECTORY); /* fd = 3 */
resolved_path = realpath("/proc/self/fd/3", NULL); /* resolved_path="/usr/bin" */

"/usr/bin" is safe, so fd 3 can be used for following operations.

The trouble with symbolic links

Posted Jul 12, 2022 8:33 UTC (Tue) by koverstreet (✭ supporter ✭, #4296) [Link] (1 responses)

Perhaps we should add a mount option to disable following symlinks that are
- not owned by the current user
- not owned by the root user
- that point to a different uid/gid as the symlink

Are there any attacks that would miss?

The trouble with symbolic links

Posted Jul 12, 2022 12:30 UTC (Tue) by jengelh (guest, #33263) [Link]

A feature like that has existed for a long time in grsec as a build-time option. Found a mirror at e.g. https://github.com/hannob/symlinkown .

The trouble with symbolic links

Posted Jul 12, 2022 20:50 UTC (Tue) by civodul (guest, #58311) [Link] (1 responses)

I view the *at function family as the right thing; it was wrong for 'open' et al. to take an implicit "root" parameter (the Hurd file system interface is very much like that: every file system operation takes a capability to the root directory on which it operates, starting with 'file_name_lookup', which is like 'openat').

In some cases, such as the Stow/Guix/Nix examples, symlinks could be replaced by a unionfs. Maybe a way forward?

The trouble with symbolic links

Posted Sep 8, 2022 12:32 UTC (Thu) by nix (subscriber, #2304) [Link]

A unionfs of thousands of filesystems is going to be hilariously slow. You're replacing an O(smallnum) lookup when a symlink is resolved with an O(n) where n == number of installed packages for *every file lookup*, as it works through all the layers.

Or did you mean to suggest some other, cleverer trick that I hadn't thought of? (Quite likely, because the naive approach is obviously a disaster.)

The trouble with symbolic links

Posted Jul 15, 2022 4:51 UTC (Fri) by flussence (guest, #85566) [Link]

There's a whole lot of ways to avoid symlinks already in widespread use: ld.so.conf, xdg and similar ad-hoc patterns of spreading config all over the filesystem (/usr/share, /etc, /lib), $PATH… this looks like an area that, for lack of any one well-thought-out answer, gets reinvented equally poorly by nearly everything. Symlinks are indeed a nightmare but taking them away would probably result in more of these.

The trouble with symbolic links

Posted Jul 15, 2022 6:54 UTC (Fri) by sven_wagner (guest, #114232) [Link] (3 responses)

For security, symlinks pose no "direct" problem, they are simple "look there instead" pointers that comes from a user thus is user provided data, should be input-validated when used and also resides in user space only.

The problem starts when higher privileged accounts use user data to do tasks with higher privileges. I.e. when an admin is archiving (or restoring) user homes, syncing them from one Server to another (i.e. rsync).
Or when a build-process (in one user account) uses another users data tree to act on it.
Then TOCTOU becomes interesting.

A few possible solutions:

In nearly all cases (i believe) either the root job/cronjob should have been run under lesser privileges or should have been splitted into two phases like:
- archive/restore only the user home folders (/home/*) but NOT their contents as at that directory level root privileges are needed to create the folders with their owners/permissions and
- use separated archives per home folder and archive/restore them from within user space (sudo -u $user tar -xf $archive)

Then the root task would not need symlink checking of user input as the restore task runs with user permissions only.

The build process for example should not be able to write it's own config/scripts, so that a toctou attack could only alter the data actually used for the build which could be checked after a recursive copy into the build-process user space.

Another way to prevent TOCTOU when reading (as root) user provided data (that resides in symlinks) would be to use immutable flag on the directories in the tree during the root task. The user himself cannot set the flag, so the admin already knows when this flag is set somewhere (and needs to stay) or otherwise can set it blindly and just remove it afterwards. Symlinks cannot be set immutable, but setting the directory immutable removes the users ability to remove or alter the symlinks or directories there, so after setting immutable flags to directories in user tree, the admin can once recheck, that all directories have the flag and then has no TOCTOU problems with directory/symlink changes until he removes the flags again after finishing the task. Maybe the user could still use fuse to overmount directories in is space, but the thread is about symlinks. (To prevent collusion with user scripts or cronjobs, flock could help)

Using chroot could also be used to solve the "overwriting /etc/passwd" risk.

Yet another way:
SElinux provides a way to allow a script to have root privileges (as in setting the owner of arbritary files) while by design it needs a "whitelist" which group of files /dirs (sockets .. etc) it is allowed to modify (and how exactly), before it can do so. A process that can write to home_t or samba_share_t (which i assume often needs chown privileges) would not be able to write to etc_t. Instead of getting tricked by the user with changing symlinks to write to /etc/passwd it would create an audit record of this attack vector for beeing used (and prevented).
On the other hand, the cronjob that runs as root would also not need to read shadow_t and (as it is not whitelisted) could also not leak data on beeing tricked by TOCTOU attacks.

Symlinks are not a problem here, administration with only(!) multi-decades old security mechanisms, actually is. There is a reason for SELinux to exist and there are hundreds (or more) of reasons to use it. =)
Other than by preventing symbolic links from beeing used to trick some other process by entirely removing the whole symlink mechanism, selinux prevents all not whitelisted accesses on a syscall level AND also reports the already prevented attack.

The trouble with symbolic links

Posted Jul 15, 2022 10:57 UTC (Fri) by ma4ris5 (guest, #151140) [Link] (1 responses)

I remember when I started studying at "Helsinki University of Technology (HUT)" in 1992.
It is named now as "Aalto University", this is different from "University of Helsinki", where Linus studied.

There were VT computer terminals with UART serial ports, attached to Unix desktops for shared
use with private home folders on NFS. I remember learning Gopher, and also Mosaic browser:
Maybe it was an early version of Mosaic - according to Wikipedia first release was at 1993.

IT administrators told, that Unix vendors had fixed the TOCTTOU soft link race conditions:
Some students in universities were using soft links in /tmp/ folders to gain root access before the fixes.
Unix administrators in HUT forbid to try such attacks on the computers: attempts would be noticed and there were severe consequences.

Fortunately symbolic link races were history: students were safe, and we learned to prevent the race conditions in the future.

The trouble with symbolic links

Posted Jul 15, 2022 22:19 UTC (Fri) by ma4ris5 (guest, #151140) [Link]

So maybe the mistake was already made in 1992:
TOCTTOU races were (partially) fixed, for the remaining ones,
there was logging and software level booby traps.

If somebody would try to search for the remaining race conditions,
alerts would be raised for one of the first attempts,
and user would be thrown out and blocked.

IT departments were in control again.

The world would look quite different in Linux side, if universities would have demanded
for Unixes to remove soft link TOCTTOU races at file system level before 1992.

Something like this might happen in next 10 years:

Security scanners
- open(), chmod() chcon() stat() etc. TOCTTOU hazard libc functions will be marked as insecure at source code level.
- All applications that use those functions, will need to be altered (just like Samba multi year project to fix one CVE),
to avoid unsafe calls, otherwise those projects will become obsolete.
- All commands, that have in source code such calls (like "setfacl -R" command which was mentioned in the Samba video )
are marked as vulnerable because of the possibility of existing TOCTTOU races.
- Security aware application container deployments must use latest OS, because older OS versions don't get the (massive multi year) fixes.
- Container (non-root-path) mounts that use data disks that follow soft links by default will be marked as insecure.

Other attack mitigations
- Of course, SELinux might come into containers too as mandatory, when OS is present at file system level.
- Single binary containers take more ground (Examples: Rust, Go).
There is only host kernel, read only binary, configuration and root disk, data partition with no
soft link support, no vulnerable OS binaries at file system (only minimal set like "/work/", "/dev/", "/proc" and "/sys")).
No suid binaries. Container runs as non-root user.

Converting all existing code bases into safety should be as easy as possible (the 10 year project).
Creating new safe applications should be the easy (default) way. Creating vulnerable code is fine to be a bit harder to do.

The trouble with symbolic links

Posted Jul 16, 2022 8:08 UTC (Sat) by sven_wagner (guest, #114232) [Link]

Yet a few more ways to secure working on user space data:

Before starting the job, move the complete user space folder into another one where the user cannot reach it, check for still open file handles of the user, then do your job without fear of toctou by evil users and at the end move the folder back in place where the user can work on it.

Another opportunity would be to temporarily remove login of the user, sigstop all of his processes and disable his cron/at jobs, let the privileged task be done and afterwards sigcont the processes again.

Similar for shares, disable the user, check no currect connection is alive, do the job and enable the user again. Or just completely shutdown samba while the higher privileged job runs.

Those who wants the user to be able to work while the higher privileged job is ongoing, would just add more insecurities that reside within the programs that work on the files but don't expect the file to be changed while reading or maybe even mmapping it. At least whatever the higher privileged task does with the data, cannot be assumed to be consistent.

If you let the user change anything inside his userspace while higher privileged tasks are running, the user might just add data to the end of a file currently read by root and punch the data at the beginning so that he does not exceed his quota. The process reading the file (into / partition?) could end uptrying to read like 16TiB before the user has to try using collapse instead of punch to see if the root cronjob continues to read even more of the file.
(Fortunately the user cannot directly see the offset of the root processes filehandle, so he must rather guess where the other process currently is, to do this type of attack)
Is this attack vector now caused by PUNCH or COLLAPSE? Do we have to remove them just for beeing able to run root commands on userspace data while the user is able to work on it as is suggested here with symlinks?

The trouble with symbolic links

Posted Jul 18, 2022 10:17 UTC (Mon) by ras (subscriber, #33059) [Link] (4 responses)

The problem with symlinks could be summed up in one word: races.

Parallelism is the hardest thing to get right in the programming world. POSIX offers no help whatsoever. It doesn't have transactions, and it doesn't have mandatory locks. Even the simplest of things like doing an atomic set of writes to a single file so hard we have the SQLite designer tell us to use it like fwrite(). Using an entire SQL library just to do reliable writes should be an absurd recommendation, but as things stand: he's right.

Because it's near impossible to get right, there are many bugs, some have security implications, and thus POSIX's total non-support of transactions has the cause of CVE after CVE for literally decades. It's not just symlinks. We seen streams of CVE's over the downright trivial operation of creating a temporary file, and surprise surprise hard links have their share of CVE's too. Renames, copies, moves, deletes are all the same. If you can't be 100% sure of what the file system looks like when you execute them (and POSIX ensures you can never be sure if there is more than one thread), then the end result is likely to be a gamble.

Or to put it another way, if POSIX did support transactions, most of the symlink CVE's he mentions, and all the need for all these xxxxat() operations would just vanish. Ergo, symlink's aren't the problem, and getting rid of them won't fix it.

The trouble with symbolic links

Posted Jul 19, 2022 13:35 UTC (Tue) by mathstuf (subscriber, #69389) [Link] (3 responses)

Have there been systems with transactional filesystem APIs?

The trouble with symbolic links

Posted Jul 19, 2022 14:59 UTC (Tue) by ras (subscriber, #33059) [Link] (1 responses)

A quick google reveals most desktops are running one: https://en.wikipedia.org/wiki/Transactional_NTFS

It's not clear if that still true, but it blows me away anyway.

The trouble with symbolic links

Posted Jul 19, 2022 16:40 UTC (Tue) by mathstuf (subscriber, #69389) [Link]

Given that Microsoft has declared it deprecated and may have it disappear in the future…it seems like a failed experiment on at least the deployment level.

The trouble with symbolic links

Posted Jul 19, 2022 16:24 UTC (Tue) by kleptog (subscriber, #1183) [Link]

PostgreSQL has support for transactional large objects. To call it a filesystem is a bit of a stretch since objects only have numeric identifiers not names. However, they have permissions, and support open/close/seek/read/write and are all within a transaction. You could certainly build a complete transactional filesystem on top of it.

But this also shows the difficulty, given the amount of work database systems have to do to make transactions work while giving everyone a consistent view and making it perform. Then again, the problem space of filesystems is much simpler.

Even if we supported only transactional metadata changes (rename, move, create, unlink) would be a step up. Certainly being able the work on a fixed snapshot of the filesystem would be good enough for quite a lot of purposes (I think btrfs has this).

The trouble with symbolic links

Posted Jul 21, 2022 17:50 UTC (Thu) by kdbotts (guest, #159822) [Link]

I understand there are races involving symlinks, and I sympathize with the concern. But I hope everybody bears in mind:

Symlinks have immense utility. They are essential and extremely valuable features of Linux and Unix, and have been for 40 years. Not just every distribution, but every deployment and pretty much every host intimately depend on symlinks. Breaking them is simply not an option. I find it hard to imagine a worse violation of "WE DO NOT BREAK USER SPACE!"

The trouble with symbolic links

Posted Jul 22, 2022 10:44 UTC (Fri) by jwilk (subscriber, #63328) [Link]

The O_PATH|O_NOFOLLOW + /proc/self/fd/N trick can be also used to bind-mount over symlinks: https://lore.kernel.org/all/20191230052036.8765-1-cyphar@...

The linked mail from 2019 proposed removal of this feature, but AFAICS it still works, so I guess it's not going to be removed after all?

The trouble with symbolic links

Posted Jul 22, 2022 13:53 UTC (Fri) by jdisnard (subscriber, #90248) [Link]

Where do bind mounts fit into the picture here?

The trouble with symbolic links

Posted Jul 23, 2022 0:41 UTC (Sat) by DimeCadmium (subscriber, #157243) [Link]

> For a non-trivial application, for a regular person writing code on POSIX, you will have symlink races in your code.

I think this overstates the amount of applications for which it actually matters. All you actually need to prevent symlink attacks is... other/untrusted users can't write to the directories you deal with or their parents. Which is the case for most applications: the directory they're operating in is owned by the user they're running as, and all of its parent directories are owned by the same user (or root).

The most common case that brings symlink attacks into possibility, I think, is an application which runs as root and does operations in (for example) /tmp or another world-writeable directory. Other than that it's really only multi-user servers that matter. (For example, Apache was mentioned in the comments: Apache only has to care about symlinks because it's not running as the same user who owns the website-files, and could therefore read files that the user can't.)


Copyright © 2022, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds