]> git.armaanb.net Git - opendoas.git/blob - timestamp.c
Change binary permissions to 4755. Closes #26
[opendoas.git] / timestamp.c
1 /*
2  * 1) Timestamp files and directories
3  *
4  * Timestamp files MUST NOT be accessible to users other than root,
5  * this includes the name, metadata and the content of timestamp files
6  * and directories.
7  *
8  * Symlinks can be used to create, manipulate or delete wrong files
9  * and directories. The Implementation MUST reject any symlinks for
10  * timestamp files or directories.
11  *
12  * To avoid race conditions the implementation MUST use the same
13  * file descriptor for permission checks and do read or write
14  * write operations after the permission checks.
15  *
16  * The timestamp files MUST be opened with openat(2) using the
17  * timestamp directory file descriptor. Permissions of the directory
18  * MUST be checked before opening the timestamp file descriptor.
19  *
20  * 2) Clock sources for timestamps
21  *
22  * Timestamp files MUST NOT rely on only one clock source, using the
23  * wall clock would allow to reset the clock to an earlier point in
24  * time to reuse a timestamp.
25  *
26  * The timestamp MUST consist of multiple clocks and MUST reject the
27  * timestamp if there is a change to any clock because there is no way
28  * to differentiate between malicious and legitimate clock changes.
29  *
30  * 3) Timestamp lifetime
31  *
32  * The implementation MUST NOT use the user controlled stdin, stdout
33  * and stderr file descriptors to determine the controlling terminal.
34  * On linux the /proc/$pid/stat file MUST be used to get the terminal
35  * number.
36  *
37  * There is no reliable way to determine the lifetime of a tty/pty.
38  * The start time of the session leader MUST be used as part of the
39  * timestamp to determine if the tty is still the same.
40  * If the start time of the session leader changed the timestamp MUST
41  * be rejected.
42  *
43  */
44
45 #include <sys/ioctl.h>
46 #include <sys/stat.h>
47 #include <sys/vfs.h>
48
49 #if !defined(timespecisset) || \
50     !defined(timespeccmp) || \
51     !defined(timespecadd)
52 #       include "sys-time.h"
53 #endif
54
55 #include <ctype.h>
56 #include <dirent.h>
57 #include <err.h>
58 #include <errno.h>
59 #include <fcntl.h>
60 #include <limits.h>
61 #include <stdio.h>
62 #include <stdlib.h>
63 #include <string.h>
64 #include <time.h>
65 #include <unistd.h>
66
67 #include "includes.h"
68
69 #ifndef TIMESTAMP_DIR
70 #       define TIMESTAMP_DIR "/run/doas"
71 #endif
72
73 #if defined(TIMESTAMP_TMPFS) && defined(__linux__)
74 #       ifndef TMPFS_MAGIC
75 #               define TMPFS_MAGIC 0x01021994
76 #       endif
77 #endif
78
79 #ifdef __linux__
80 /* Use tty_nr from /proc/self/stat instead of using
81  * ttyname(3), stdin, stdout and stderr are user
82  * controllable and would allow to reuse timestamps
83  * from another writable terminal.
84  * See https://www.sudo.ws/alerts/tty_tickets.html
85  */
86 static int
87 proc_info(pid_t pid, int *ttynr, unsigned long long *starttime)
88 {
89         char path[128];
90         char buf[1024];
91         char *p, *saveptr, *ep;
92         const char *errstr;
93         int fd, n;
94
95         p = buf;
96
97         n = snprintf(path, sizeof path, "/proc/%d/stat", pid);
98         if (n < 0 || n >= (int)sizeof path)
99                 return -1;
100
101         if ((fd = open(path, O_RDONLY)) == -1)
102                 return -1;
103
104         while ((n = read(fd, p, buf + sizeof buf - p)) != 0) {
105                 if (n == -1) {
106                         if (errno == EAGAIN || errno == EINTR)
107                                 continue;
108                         close(fd);
109                         return -1;
110                 }
111                 p += n;
112                 if (p >= buf + sizeof buf)
113                         break;
114         }
115         close(fd);
116
117         /* error if it contains NULL bytes */
118         if (n != 0 || memchr(buf, '\0', p - buf))
119                 return -1;
120
121         /* Get the 7th field, 5 fields after the last ')',
122          * (2th field) because the 5th field 'comm' can include
123          * spaces and closing paranthesis too.
124          * See https://www.sudo.ws/alerts/linux_tty.html
125          */
126         if ((p = strrchr(buf, ')')) == NULL)
127                 return -1;
128
129         n = 2;
130         for ((p = strtok_r(p, " ", &saveptr)); p;
131             (p = strtok_r(NULL, " ", &saveptr))) {
132                 switch (n++) {
133                 case 7:
134                         *ttynr = strtonum(p, INT_MIN, INT_MAX, &errstr);
135                         if (errstr)
136                                 return -1;
137                         break;
138                 case 22:
139                         errno = 0;
140                         *starttime = strtoull(p, &ep, 10);
141                         if (p == ep ||
142                            (errno == ERANGE && *starttime == ULLONG_MAX))
143                                 return -1;
144                         break;
145                 }
146                 if (n == 23)
147                         break;
148         }
149
150         return 0;
151 }
152 #else
153 #error "proc_info not implemented"
154 #endif
155
156 static int
157 timestamp_path(char *buf, size_t len)
158 {
159         pid_t ppid, sid;
160         unsigned long long starttime;
161         int n, ttynr;
162
163         ppid = getppid();
164         if ((sid = getsid(0)) == -1)
165                 return -1;
166         if (proc_info(ppid, &ttynr, &starttime) == -1)
167                 return -1;
168         n = snprintf(buf, len, TIMESTAMP_DIR "/%d-%d-%d-%llu-%d",
169             ppid, sid, ttynr, starttime, getuid());
170         if (n < 0 || n >= (int)len)
171                 return -1;
172         return 0;
173 }
174
175 int
176 timestamp_set(int fd, int secs)
177 {
178         struct timespec ts[2], timeout = { .tv_sec = secs, .tv_nsec = 0 };
179
180         if (clock_gettime(CLOCK_BOOTTIME, &ts[0]) == -1 ||
181             clock_gettime(CLOCK_REALTIME, &ts[1]) == -1)
182                 return -1;
183
184         timespecadd(&ts[0], &timeout, &ts[0]);
185         timespecadd(&ts[1], &timeout, &ts[1]);
186         return futimens(fd, ts);
187 }
188
189 /*
190  * Returns 1 if the timestamp is valid, 0 if its invalid
191  */
192 static int
193 timestamp_check(int fd, int secs)
194 {
195         struct timespec ts[2], timeout = { .tv_sec = secs, .tv_nsec = 0 };
196         struct stat st;
197
198         if (fstat(fd, &st) == -1)
199                 return 0;
200
201         if (!timespecisset(&st.st_atim) || !timespecisset(&st.st_mtim))
202                 return 0;
203
204         if (clock_gettime(CLOCK_BOOTTIME, &ts[0]) == -1 ||
205             clock_gettime(CLOCK_REALTIME, &ts[1]) == -1)
206                 return 0;
207
208         /* check if timestamp is too old */
209         if (timespeccmp(&st.st_atim, &ts[0], <) ||
210             timespeccmp(&st.st_mtim, &ts[1], <))
211                 return 0;
212
213         /* check if timestamp is too far in the future */
214         timespecadd(&ts[0], &timeout, &ts[0]);
215         timespecadd(&ts[1], &timeout, &ts[1]);
216         if (timespeccmp(&st.st_atim, &ts[0], >) ||
217             timespeccmp(&st.st_mtim, &ts[1], >))
218                 return 0;
219
220         return 1;
221 }
222
223 int
224 timestamp_open(int *valid, int secs)
225 {
226         struct timespec ts[2] = {0};
227         struct stat st;
228         int fd;
229         char path[256];
230         int serrno = 0;
231
232         *valid = 0;
233
234         if (stat(TIMESTAMP_DIR, &st) == -1) {
235                 if (errno != ENOENT)
236                         return -1;
237                 if (mkdir(TIMESTAMP_DIR, 0700) == -1)
238                         return -1;
239         } else if (st.st_uid != 0 || st.st_mode != (S_IFDIR | 0700)) {
240                 return -1;
241         }
242
243         if (timestamp_path(path, sizeof path) == -1)
244                 return -1;
245
246         if (stat(path, &st) != -1 && (st.st_uid != 0 || st.st_gid != getgid()|| st.st_mode != (S_IFREG | 0000)))
247                 return -1;
248
249         fd = open(path, O_RDONLY|O_NOFOLLOW);
250         if (fd == -1) {
251                 char tmp[256];
252                 int n;
253
254                 n = snprintf(tmp, sizeof tmp, TIMESTAMP_DIR "/.tmp-%d", getpid());
255                 if (n < 0 || n >= (int)sizeof tmp)
256                         return -1;
257
258                 fd = open(tmp, O_RDONLY|O_CREAT|O_EXCL|O_NOFOLLOW, 0000);
259                 if (fd == -1)
260                         return -1;
261                 if (futimens(fd, ts) == -1 || rename(tmp, path) == -1) {
262                         serrno = errno;
263                         close(fd);
264                         unlink(tmp);
265                         errno = serrno;
266                         return -1;
267                 }
268         } else {
269                 *valid = timestamp_check(fd, secs);
270         }
271         return fd;
272 }
273
274 int
275 timestamp_clear()
276 {
277         char path[256];
278
279         if (timestamp_path(path, sizeof path) == -1)
280                 return -1;
281         if (unlink(path) == -1 && errno != ENOENT)
282                 return -1;
283         return 0;
284 }