nicer output
[tridge/junkcode.git] / aio_uid.c
1 /*
2   test program to demonstrate race between AIO and setresuid()
3
4   tridge@samba.org August 2008
5
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:
14
15     1) the signal is never delivered, so the caller never is told that
16     the IO has completed
17
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
20
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
27  */
28
29 #define _XOPEN_SOURCE 500
30 #define _GNU_SOURCE 
31
32 #include <stdio.h>
33 #include <unistd.h>
34 #include <stdlib.h>
35 #include <errno.h>
36 #include <sys/stat.h>
37 #include <fcntl.h>
38 #include <string.h>
39 #include <getopt.h>
40 #include <signal.h>
41 #include <utime.h>
42 #include <stdbool.h>
43 #include <sys/time.h>
44 #include <sys/wait.h>
45 #include <time.h>
46 #include <aio.h>
47
48 /* The signal we'll use to signify aio done. */
49 #ifndef RT_SIGNAL_AIO
50 #define RT_SIGNAL_AIO (SIGRTMIN+3)
51 #endif
52
53 static volatile bool signal_received;
54
55 static void signal_handler(int sig)
56 {
57         signal_received = true;
58 }
59
60 #define UID 1000
61
62 static void become_root(void)
63 {
64 #if USE_SETEUID
65         seteuid(0);
66 #elif USE_SETREUID
67         setreuid(-1, 0);
68 #else
69         setresuid(0, 0, -1);
70 #endif
71         if (geteuid() != 0) {
72                 printf("become root failed\n");
73                 exit(1);
74         }
75 }
76
77 static void unbecome_root(void)
78 {
79 #if USE_SETEUID
80         seteuid(UID);
81 #elif USE_SETREUID
82         setreuid(-1, UID);
83 #else
84         setresuid(UID, UID, -1);
85 #endif
86         if (geteuid() != UID) {
87                 printf("become root failed\n");
88                 exit(1);
89         }
90 }
91
92 /* pread using aio */
93 static ssize_t pread_aio(int fd, void *buf, size_t count, off_t offset)
94 {
95         struct aiocb acb;
96         int ret;
97         time_t t;
98
99         memset(&acb, 0, sizeof(acb));
100
101         acb.aio_fildes = fd;
102         acb.aio_buf = buf;
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;
108
109         signal(RT_SIGNAL_AIO, signal_handler);
110         signal_received = 0;
111
112         become_root();
113         if (aio_read(&acb) != 0) {
114                 return -1;
115         }
116         unbecome_root();
117
118         t = time(NULL);
119         while (!signal_received) {
120                 usleep(1000);
121                 if (time(NULL) - t > 5) {
122                         printf("Timed out waiting for IO (AIO race)\n");
123                         exit(1);
124                 }
125         }
126
127         ret = aio_error(&acb);
128         if (ret != 0) {
129                 printf("aio operation failed - %s\n", strerror(ret));
130                 return -1;
131         }
132
133         return aio_return(&acb);        
134 }
135
136 static int count;
137
138 static void sig_alarm(int sig)
139 {
140         printf("%6d\r", count);
141         count=0;
142         fflush(stdout);
143         signal(SIGALRM, sig_alarm);
144         alarm(1);
145 }
146
147 int main(int argc, const char *argv[])
148 {
149         int fd;
150         const char *fname;
151         char buf[8192];
152
153         fname = argv[1];
154
155         unbecome_root();
156
157         fd = open(fname, O_RDWR|O_CREAT|O_TRUNC, 0600);
158
159         memset(buf, 1, sizeof(buf));
160         write(fd, buf, sizeof(buf));
161         fsync(fd);
162
163         signal(SIGALRM, sig_alarm);
164         alarm(1);
165
166         while (1) {
167                 if (pread_aio(fd, buf, sizeof(buf), 0) != sizeof(buf)) {
168                         break;
169                 }
170                 count++;
171         }
172
173         return 0;
174 }