2 test program to demonstrate race between AIO and setresuid()
4 tridge@samba.org August 2008
6 The race condition is in setresuid(), which in glibc tries to be
7 smart about threads and change the euid of threads when the euid of
8 the main program changes. The problem is that this makes setresuid()
9 non-atomic, which means that if an IO completes during the complex
10 series of system calls that setresuid() becomes, then the thread
11 completing the IO may get -1/EPERM back from the rt_sigqueueinfo()
12 call that it uses to notify its parent of the completing IO. In that
13 case two things happen:
15 1) the signal is never delivered, so the caller never is told that
18 2) if the caller polls for completion using aio_error() then it
19 will see a -1/EPERM result, rather than the real result of the IO
21 The simplest fix in existing code that mixes uid changing with AIO
22 (such as Samba) is to not use setresuid() and use setreuid()
23 instead, which in glibc doesn't try to play any games with the euid
24 of threads. That does mean that you will need to manually gain root
25 privileges before calling aio_read() or aio_write() to ensure that
26 the thread has permission to send signals to the main thread
29 #define _XOPEN_SOURCE 500
48 /* The signal we'll use to signify aio done. */
50 #define RT_SIGNAL_AIO (SIGRTMIN+3)
53 static volatile bool signal_received;
55 static void signal_handler(int sig)
57 signal_received = true;
62 static void become_root(void)
72 printf("become root failed\n");
77 static void unbecome_root(void)
84 setresuid(UID, UID, -1);
86 if (geteuid() != UID) {
87 printf("become root failed\n");
93 static ssize_t pread_aio(int fd, void *buf, size_t count, off_t offset)
99 memset(&acb, 0, sizeof(acb));
103 acb.aio_nbytes = count;
104 acb.aio_offset = offset;
105 acb.aio_sigevent.sigev_notify = SIGEV_SIGNAL;
106 acb.aio_sigevent.sigev_signo = RT_SIGNAL_AIO;
107 acb.aio_sigevent.sigev_value.sival_int = 1;
109 signal(RT_SIGNAL_AIO, signal_handler);
113 if (aio_read(&acb) != 0) {
119 while (!signal_received) {
121 if (time(NULL) - t > 5) {
122 printf("Timed out waiting for IO (AIO race)\n");
127 ret = aio_error(&acb);
129 printf("aio operation failed - %s\n", strerror(ret));
133 return aio_return(&acb);
138 static void sig_alarm(int sig)
140 printf("%6d\r", count);
143 signal(SIGALRM, sig_alarm);
147 int main(int argc, const char *argv[])
157 fd = open(fname, O_RDWR|O_CREAT|O_TRUNC, 0600);
159 memset(buf, 1, sizeof(buf));
160 write(fd, buf, sizeof(buf));
163 signal(SIGALRM, sig_alarm);
167 if (pread_aio(fd, buf, sizeof(buf), 0) != sizeof(buf)) {