]> git.armaanb.net Git - opendoas.git/blob - timestamp.c
briefly mention /etc/examples/ in the FILES section of all the manual pages that...
[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|O_NOFOLLOW)) == -1) {
102                 warn("failed to open: %s", path);
103                 return -1;
104         }
105
106         while ((n = read(fd, p, buf + (sizeof buf - 1) - p)) != 0) {
107                 if (n == -1) {
108                         if (errno == EAGAIN || errno == EINTR)
109                                 continue;
110                         warn("read: %s", path);
111                         close(fd);
112                         return -1;
113                 }
114                 p += n;
115                 if (p >= buf + (sizeof buf - 1))
116                         break;
117         }
118         close(fd);
119
120         /* error if it contains NULL bytes */
121         if (n != 0 || memchr(buf, '\0', p - buf - 1) != NULL) {
122                 warn("NUL in: %s", path);
123                 return -1;
124         }
125
126         *p = '\0';
127
128         /* Get the 7th field, 5 fields after the last ')',
129          * (2th field) because the 5th field 'comm' can include
130          * spaces and closing paranthesis too.
131          * See https://www.sudo.ws/alerts/linux_tty.html
132          */
133         if ((p = strrchr(buf, ')')) == NULL)
134                 return -1;
135
136         n = 2;
137         for ((p = strtok_r(p, " ", &saveptr)); p;
138             (p = strtok_r(NULL, " ", &saveptr))) {
139                 switch (n++) {
140                 case 7:
141                         *ttynr = strtonum(p, INT_MIN, INT_MAX, &errstr);
142                         if (errstr)
143                                 return -1;
144                         break;
145                 case 22:
146                         errno = 0;
147                         *starttime = strtoull(p, &ep, 10);
148                         if (p == ep ||
149                            (errno == ERANGE && *starttime == ULLONG_MAX))
150                                 return -1;
151                         return 0;
152                 }
153         }
154
155         return -1;
156 }
157 #else
158 #error "proc_info not implemented"
159 #endif
160
161 static int
162 timestamp_path(char *buf, size_t len)
163 {
164         pid_t ppid, sid;
165         unsigned long long starttime;
166         int n, ttynr;
167
168         ppid = getppid();
169         if ((sid = getsid(0)) == -1)
170                 return -1;
171         if (proc_info(ppid, &ttynr, &starttime) == -1)
172                 return -1;
173         n = snprintf(buf, len, TIMESTAMP_DIR "/%d-%d-%d-%llu-%d",
174             ppid, sid, ttynr, starttime, getuid());
175         if (n < 0 || n >= (int)len)
176                 return -1;
177         return 0;
178 }
179
180 int
181 timestamp_set(int fd, int secs)
182 {
183         struct timespec ts[2], timeout = { .tv_sec = secs, .tv_nsec = 0 };
184
185         if (clock_gettime(CLOCK_BOOTTIME, &ts[0]) == -1 ||
186             clock_gettime(CLOCK_REALTIME, &ts[1]) == -1)
187                 return -1;
188
189         timespecadd(&ts[0], &timeout, &ts[0]);
190         timespecadd(&ts[1], &timeout, &ts[1]);
191         return futimens(fd, ts);
192 }
193
194 /*
195  * Returns 1 if the timestamp is valid, 0 if its invalid
196  */
197 static int
198 timestamp_check(int fd, int secs)
199 {
200         struct timespec ts[2], timeout = { .tv_sec = secs, .tv_nsec = 0 };
201         struct stat st;
202
203         if (fstat(fd, &st) == -1)
204                 err(1, "fstat");
205         if (st.st_uid != 0 || st.st_gid != getgid() || st.st_mode != (S_IFREG | 0000))
206                 errx(1, "timestamp uid, gid or mode wrong");
207
208         /* this timestamp was created but never set, invalid but no error */
209         if (!timespecisset(&st.st_atim) || !timespecisset(&st.st_mtim))
210                 return 0;
211
212         if (clock_gettime(CLOCK_BOOTTIME, &ts[0]) == -1 ||
213             clock_gettime(CLOCK_REALTIME, &ts[1]) == -1) {
214                 warn("clock_gettime");
215                 return 0;
216         }
217
218         /* check if timestamp is too old */
219         if (timespeccmp(&st.st_atim, &ts[0], <) ||
220             timespeccmp(&st.st_mtim, &ts[1], <))
221                 return 0;
222
223         /* check if timestamp is too far in the future */
224         timespecadd(&ts[0], &timeout, &ts[0]);
225         timespecadd(&ts[1], &timeout, &ts[1]);
226         if (timespeccmp(&st.st_atim, &ts[0], >) ||
227             timespeccmp(&st.st_mtim, &ts[1], >)) {
228                 warnx("timestamp too far in the future");
229                 return 0;
230         }
231
232         return 1;
233 }
234
235 int
236 timestamp_open(int *valid, int secs)
237 {
238         struct timespec ts[2] = {0};
239         struct stat st;
240         int fd;
241         char path[256];
242         int serrno = 0;
243
244         *valid = 0;
245
246         if (stat(TIMESTAMP_DIR, &st) == -1) {
247                 if (errno != ENOENT)
248                         return -1;
249                 if (mkdir(TIMESTAMP_DIR, 0700) == -1)
250                         return -1;
251         } else if (st.st_uid != 0 || st.st_mode != (S_IFDIR | 0700)) {
252                 return -1;
253         }
254
255         if (timestamp_path(path, sizeof path) == -1)
256                 return -1;
257
258         fd = open(path, O_RDONLY|O_NOFOLLOW);
259         if (fd == -1) {
260                 char tmp[256];
261                 int n;
262
263                 if (errno != ENOENT)
264                         err(1, "open: %s", path);
265
266                 n = snprintf(tmp, sizeof tmp, TIMESTAMP_DIR "/.tmp-%d", getpid());
267                 if (n < 0 || n >= (int)sizeof tmp)
268                         return -1;
269
270                 fd = open(tmp, O_RDONLY|O_CREAT|O_EXCL|O_NOFOLLOW, 0000);
271                 if (fd == -1)
272                         return -1;
273                 if (futimens(fd, ts) == -1 || rename(tmp, path) == -1) {
274                         serrno = errno;
275                         close(fd);
276                         unlink(tmp);
277                         errno = serrno;
278                         return -1;
279                 }
280         } else {
281                 *valid = timestamp_check(fd, secs);
282         }
283         return fd;
284 }
285
286 int
287 timestamp_clear()
288 {
289         char path[256];
290
291         if (timestamp_path(path, sizeof path) == -1)
292                 return -1;
293         if (unlink(path) == -1 && errno != ENOENT)
294                 return -1;
295         return 0;
296 }