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