New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
CVE-2018-21269: checkpath root privilege escalation following non-terminal symlinks #201
Comments
|
I don't see a way to avoid this easily, because /var/run in your example can be a symlinkk, and if it is it is completely legit. |
|
There is no real way around this, as @williamh pointed out there are completely legitimate reasons to have symlinks in the path. The only way to prevent this would be to open each path component separately with |
|
Well systemd was able to do it by saying "your service won't work unless you use Considering that |
|
Exploiting this is not easy. Generally init scripts are poking paths that are owned by root and not writable by users (or just the daemon user). Also, since checkpath is usually used in init scripts (many/most of which run before sshd is running, all of which generally run before console logins are enabled) exploiting this would be rather tricky. If someone has a particular daemon that might be victim to this, they can practically eliminate the exploit vector with |
|
We are not systemd, telling people how their systems should be configured is not really an option. Also |
|
A non-symlink (I much prefer OpenRC's portable approach to systemd's) |
|
The problem is that you then can't write init scripts that work on both BSD and Linux without dirty "if linux" hacks. Currently, you can just write to /var/run and it will do the right thing on both Linux and BSD. |
|
Of course you can, you can do what the service script guide tells you to do, and use the runstatedir from the build system =) |
|
I do not have any plans for removing /var/run in Gentoo, and we don't know what other Linux distros are doing, so I'm not convinced checkpath should blow up in this situation. |
|
I'm not suggesting anything that would break compatibility on any system. If checkpath encounters a symlink, it would tell you (i.e. the packager) to use the real path instead. That can be done in the build system if upstream accepts it, or in the ebuild/rpm/deb/whatever. It would make a bunch of people update their 15 year old init scripts finally, but nothing would break. Same as the runscript -> openrc-run transition. |
|
it should really behave the same as tmpfiles. if tmpfiles is going the route of disallowing it, then so should checkpath. most users of checkpath should be converted to tmpfiles too which further drives home this point. if you really want to leave an escape hatch, you could add a command line option like chown's -L/-P options where -P is the default. then everyone can eat their cookies w/out making the default behavior insecure. |
|
I haven't tested yet, but It looks like, if the path we are going to adjust exists, I can use realpath() to flush out symbolic links and generate a warning. For example, |
|
technically that suffers from a TOCTOU race still |
|
@vapier yes, it does, but I'm at the point right now where I'm thinking about narrowing the window as much as possible since I don't know of a way to completely remove the race. |
The approach that Lennart suggested should work: start at |
|
my point about checkpath-vs-tmpfiles is that, since they practically serve the exact same purpose, they should behave the same. wrt opentmpfiles, that project doesn't really matter here. systemd's tmpfiles implementation is de-facto standard, so its behavior is what others (like opentmpfiles) should be implementing. doing a recursive openat walk from / via the path components would solve the TOCTOU issue, but again run into the problem of /var/run->/run. not sure that's been decided on. i'd hate to encode a whitelist of permissible symlinks. |
|
@vapier sure, I don't plan on encoding a list of acceptable symlinks, the idea is just to complain if the path contains symlinks. |
|
The three flags if (!new_implementation(path)) {
show_warning();
old_implementation(path);
}Then after however-long, we just delete the fallback. |
|
@orlitzky I don't see that there is going to be a separate implementation to keep since all we are doing is scanning the path and warning if there are any symbolic links. |
|
|
|
@chutz @orlitzky the more I think about this issue, I don't see that there is anything we can do about it because doing something about it would require that we mandate that all paths referred to by checkpath could never contain symlinks. |
|
My vote is to ban them with a deprecation period, so that people have to replace Otherwise, we have a bunch of init scripts with root privilege escalations that need to be fixed or worked around. |
|
@orlitzky You are just looking at one possible symlink. What if, for example, /var is a symlink (this is allowed by fhs). Also, there is no restriction requiring checkpath to only be used for certain paths, so maybe someone is doing something with checkpath under /opt/package. Either /opt or /opt/package might legitimately be a symlink. |
|
If /var is a symlink, then you would use the path that it points to instead of "/var". Same thing with /opt and /opt/package. If someone has devised some non-service-script use for checkpath, then they'll get a warning telling them to resolve the symlinks, and they can fix the problem by resolving the symlinks. The only situation where I see a problem is on a distro like Gentoo where /var is a directory "owned" by the package manager, and someone replaces it with a symlink. In that case, you may find calls to But if /var starts out as a symlink and is supposed to be a symlink, then you just use where it points to in all of your build scripts and packages. |
|
@orlitzky We do not accept a symlink as the terminal component of the path because we know we want it to be a file, directory or fifo. If we want to allow a symlink, we need another command line switch and syntax, for example: "checkpath -L /link/target:/foo/bar" could create a /foo/bar symlink that points to /link/target. This has nothing to do with how "dangerous" symlinnks are as the terminal component, we just don't support them. We could if we wanted to, but that hasn't been requested so far. |
|
What I mean is, somebody could decide they want |
|
I don't follow your previous comment. |
|
With the current OpenRC, |
|
I don't see that as dangerous, it is just not an allowed option. I think the tmpfiles spec allows you to create and manipulate symlinks, I havent looked. checkpath -L or something similar. |
This walks the directory path to the file we are going to create to make sure that when we create the file and change the ownership and permissions we are working on the same file. Also, symbolic links in the path need to be owned by root or the current user. On non-linux platforms, we no longer follow non-terminal symbolic links by default. If you need to do that, add the -s option on the checkpath command line. This is for #201.
|
@orlitzky please check the patch I just added. I will use _GNU_SOURCE and O_PATH reluctantly, since there doesn't appear to be another way to do this, but I'm not sure what I can do for the non-linux side other than not follow non-terminal symlinks by default. |
|
Won't this always refuse to follow symlinks on linux? #ifdef O_PATH
flags |= O_NOFOLLOW;
flags |= O_PATH;
#else
if (!symlinks)
flags |= O_NOFOLLOW;
flags |= O_RDONLY;
#endif I don't think it makes sense to (optionally) follow symlinks on non-linux systems if you'll always refuse to follow them on linux systems., especially when considering that linux is where they can be followed relatively safely. Maybe that's not what's intended though? |
|
I can use O_PATH|O_NOFOLLOW to open non-terminal symlinks without dereferencing (see the openat man page). This allows me to check who owns the symlink, and if root or the current user owns it,, it seems reasonable to follow it because chown will fail if the current user isn't root. |
OK so far...
But I don't think you ever actually follow it on Linux. You have a descriptor for the link itself in So ultimately, you never follow any symlinks. |
|
I'm not quite following; I don't think dirfd is required to be a descriptor of a directory. |
|
I don't think a descriptor for a symlink to a directory is valid as a dirfd for *at functions. |
Right, the next iteration just fails in the presence of root-owned symlinks. |
This walks the directory path to the file we are going to create to make sure that when we create the file and change the ownership and permissions we are working on the same file. Also, symbolic links in the path need to be owned by root or the current user. On non-linux platforms, we no longer follow non-terminal symbolic links by default. If you need to do that, add the -s option on the checkpath command line. This is for #201.
This walks the directory path to the file we are going to create to make sure that when we create the file and change the ownership and permissions we are working on the same file. Also, symbolic links in the path need to be owned by root or the current user. On non-linux platforms, we no longer follow non-terminal symbolic links by default. If you need to do that, add the -s option on the checkpath command line. This is for #201.
This walks the directory path to the file we are going to manipulate to make sure that when we create the file and change the ownership and permissions we are working on the same file. Also, all non-terminal symbolic links must be owned by root. This will keep a non-root user from making a symbolic link as described in the bug. If root creates the symbolic link, it is assumed to be trusted. On non-linux platforms, we no longer follow non-terminal symbolic links by default. If you need to do that, add the -s option on the checkpath command line, but keep in mind that this is not secure. This is for #201.
This walks the directory path to the file we are going to manipulate to make sure that when we create the file and change the ownership and permissions we are working on the same file. Also, all non-terminal symbolic links must be owned by root. This will keep a non-root user from making a symbolic link as described in the bug. If root creates the symbolic link, it is assumed to be trusted. On non-linux platforms, we no longer follow non-terminal symbolic links by default. If you need to do that, add the -s option on the checkpath command line, but keep in mind that this is not secure. This is for #201.
|
What purpose does the This is probably the closest you can get if you insist on following root-owned symlinks, but it does introduce a new TOCTOU issue between if (st.st_uid != 0)
eerrorx("%s: %s: synbolic link %s not owned by root", applet, path, str);and if (readlinkat(dirfd, str, linkpath, linksize) != st.st_size)
eerrorx("%s: symbolic link destination changed", applet);The latter is happy to follow a non-root symlink so long as it points to a path of the same length. |
|
@orlitzky The -s is used on systems that do not have the O_PATH if (readlinkat(dirfd, str, linkpath, linksize) != st.st_size) The reason for using readlinkat() instead of readlink() here was to avoid a toctou, based on the same idea as openat(), so how is there still a toctou? |
Oh, you're right. Nevermind.
You check that the descriptor |
|
Not useful outside of linux, but the man page for readlinkat mentions that if dirfd refers to a symlink, and was opened with O_PATH and O_NOFOLLOW, then you can do |
This walks the directory path to the file we are going to manipulate to make sure that when we create the file and change the ownership and permissions we are working on the same file. Also, all non-terminal symbolic links must be owned by root. This will keep a non-root user from making a symbolic link as described in the bug. If root creates the symbolic link, it is assumed to be trusted. On non-linux platforms, we no longer follow non-terminal symbolic links by default. If you need to do that, add the -s option on the checkpath command line, but keep in mind that this is not secure. This is for #201.
|
Unless there are systems that have O_PATH, but don't support that readlinkat extension, then that part of the change seems fine to me |
Nice catch, looks good. I can still prevent |
This walks the directory path to the file we are going to manipulate to make sure that when we create the file and change the ownership and permissions we are working on the same file. Also, all non-terminal symbolic links must be owned by root. This will keep a non-root user from making a symbolic link as described in the bug. If root creates the symbolic link, it is assumed to be trusted. On non-linux platforms, we no longer follow non-terminal symbolic links by default. If you need to do that, add the -s option on the checkpath command line, but keep in mind that this is not secure. This fixes #201.
|
Found a typo "synbolic" after the fact. |
|
One more thing! There's a call to |
Let's use a separate issue for this so we don't conflate it with the race condition fix in #195. With the following service script,
I can replace
/run/foo/bar(at my leisure) with a symlink to/etc...and the next time the service is restarted, checkpath will change ownership of
/etc/passwd:Even if the service script checks the return value of
checkpath, I have a moment to replace "bar" with a symlink. The systemd tmpfiles implementation has a similar issue systemd/systemd#7986The text was updated successfully, but these errors were encountered: