Attempt to create .deps directory every time we build objects.
[doas.git] / timestamp.c
blobd7bcbc41a832b35077f99528e93a9ac1263dc092
1 /*
2 * Copyright (c) 2020 Duncan Overbruck <mail@duncano.de>
3 * Copyright (c) 2021-2022 Sergey Sushilin <sergeysushilin@protonmail.com>
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 * 1) Timestamp files and directories
21 * Timestamp files MUST NOT be accessible to users other than root,
22 * this includes the name, metadata and the content of timestamp files
23 * and directories.
25 * Symlinks can be used to create, manipulate or delete wrong files
26 * and directories. The Implementation MUST reject any symlinks for
27 * timestamp files or directories.
29 * To avoid race conditions the implementation MUST use the same
30 * file descriptor for permission checks and do read or write
31 * write operations after the permission checks.
33 * The timestamp files MUST be opened with openat(2) using the
34 * timestamp directory file descriptor. Permissions of the directory
35 * MUST be checked before opening the timestamp file descriptor.
37 * 2) Clock sources for timestamps
39 * Timestamp files MUST NOT rely on only one clock source, using the
40 * wall clock would allow to reset the clock to an earlier point in
41 * time to reuse a timestamp.
43 * The timestamp MUST consist of multiple clocks and MUST reject the
44 * timestamp if there is a change to any clock because there is no way
45 * to differentiate between malicious and legitimate clock changes.
47 * 3) Timestamp lifetime
49 * The implementation MUST NOT use the user controlled stdin, stdout
50 * and stderr file descriptors to determine the controlling terminal.
51 * On linux the /proc/$pid/stat file MUST be used to get the terminal
52 * number.
54 * There is no reliable way to determine the lifetime of a tty/pty.
55 * The start time of the session leader MUST be used as part of the
56 * timestamp to determine if the tty is still the same.
57 * If the start time of the session leader changed the timestamp MUST
58 * be rejected.
61 #include <sys/ioctl.h>
62 #include <sys/stat.h>
63 #include <sys/vfs.h>
65 #if !defined(timespecisset) || !defined(timespeccmp) || !defined(timespecadd)
66 # include "sys/time.h"
67 #endif
69 #include <ctype.h>
70 #include <dirent.h>
71 #include <err.h>
72 #include <errno.h>
73 #include <fcntl.h>
74 #include <limits.h>
75 #include <stdio.h>
76 #include <stdlib.h>
77 #include <string.h>
78 #include <time.h>
79 #include <unistd.h>
81 #include "compat.h"
82 #include "wrappers.h"
83 #include "timestamp.h"
85 #if !defined(TIMESTAMP_DIR)
86 # define TIMESTAMP_DIR "/run/doas"
87 #endif
89 #if defined(__linux__)
90 # define PROC_STATUS_PATH_FORMAT "/proc/%lu/stat"
91 # define TTY_NUMBER_FIELD_NUMBER 7
92 # define START_TIME_FIELD_NUMBER 22
93 #elif defined(__FreeBSD__)
94 # define PROC_STATUS_PATH_FORMAT "/proc/%lu/status"
95 # define TTY_NUMBER_FIELD_NUMBER 6
96 # define START_TIME_FIELD_NUMBER 8
97 #endif
99 #define TTY_NUMBER_SIZE 32
101 #if defined(__linux__) || defined(__FreeBSD__)
102 /* Use tty_nr from /proc/self/stat instead of using
103 * ttyname(3), stdin, stdout and stderr are user
104 * controllable and would allow to reuse timestamps
105 * from another writable terminal.
106 * See https://www.sudo.ws/alerts/tty_tickets.html
108 static int proc_info(pid_t pid, char ttynr[TTY_NUMBER_SIZE], unsigned long long int *starttime)
110 char path[128], buf[1024], *p, *saveptr;
111 int fd;
112 size_t n;
113 bool succeed = true;
115 if (pid < 0) {
116 errno = EINVAL;
117 return -1;
120 p = buf;
122 if (safe_snprintf(path, sizeof(path), PROC_STATUS_PATH_FORMAT, (u_long)pid) < 0)
123 return -1;
125 if ((fd = open(path, O_RDONLY | O_NOFOLLOW)) < 0) {
126 warn("failed to open: %s", path);
127 return -1;
130 errno = 0;
132 while ((n = full_read(fd, p, endof(buf) - p - 1)) != 0) {
133 p += n;
135 if (p >= endof(buf) - 1)
136 break;
139 if (errno != 0) {
140 warn("read: %s", path);
141 close(fd);
142 return -1;
145 close(fd);
147 /* Error if it contains NULL bytes. */
148 if (memchr(buf, '\0', p - buf - 1) != NULL) {
149 warn("NUL in: %s", path);
150 return -1;
153 *p = '\0';
155 #if defined(__linux__)
157 * Get the 7th field, 5 fields after the last ')',
158 * (2th field) because the 5th field 'comm' can include
159 * spaces and closing paranthesis too.
160 * See https://www.sudo.ws/alerts/linux_tty.html
162 /* Be careful: program name may include ')' character,
163 so we find the last ')' entry searching from the end. */
164 if ((p = strrchr(buf, ')')) == NULL)
165 return -1;
167 n = 2;
168 #elif defined(__FreeBSD__)
169 n = 1;
170 #endif
172 for (p = strtok_r(p, " ", &saveptr); p != NULL; p = strtok_r(NULL, " ", &saveptr)) {
173 switch (n++) {
174 case TTY_NUMBER_FIELD_NUMBER: {
175 size_t ttylen = saveptr - p - 1;
177 if (ttylen >= TTY_NUMBER_SIZE) {
178 errno = EOVERFLOW;
179 return -1;
182 memcpy(ttynr, p, ttylen);
183 break;
185 case START_TIME_FIELD_NUMBER:
186 *starttime = safe_strtounum(p, ULLONG_MAX, &succeed);
187 return succeed ? 0 : -1;
191 return -1;
193 #else
194 /* proc_info not implemented. */
195 static int proc_info(pid_t pid __unused, int *ttynr __unused, unsigned long long int *starttime __unused)
197 errno = ENOSYS;
198 return -1;
200 #endif
202 static char *timestamp_name(char *buf, size_t len)
204 pid_t ppid, sid;
205 unsigned long long starttime;
206 char ttynr[TTY_NUMBER_SIZE] = { 0 };
208 ppid = getppid();
210 if (proc_info(ppid, ttynr, &starttime) < 0)
211 return NULL;
213 if ((sid = getsid(0)) < 0)
214 return NULL;
216 if (safe_snprintf(buf, len, "%d-%d-%s-%llu-%ld",
217 ppid, sid, ttynr, starttime, (long)getuid()) < 0)
218 return NULL;
220 return buf;
223 int timestamp_set(int fd, time_t secs)
225 struct timespec ts[2], timeout = { .tv_sec = secs, .tv_nsec = 0 };
227 if (clock_gettime(CLOCK_BOOTTIME, &ts[0]) < 0
228 || clock_gettime(CLOCK_REALTIME, &ts[1]) < 0)
229 return -1;
231 timespecadd(&ts[0], &timeout, &ts[0]);
232 timespecadd(&ts[1], &timeout, &ts[1]);
234 return futimens(fd, ts);
237 /* Returns true if the timestamp is valid, false if it is invalid. */
238 static bool timestamp_check(int fd, time_t secs)
240 struct timespec ts[2], timeout = { .tv_sec = secs, .tv_nsec = 0 };
241 struct stat st;
243 if (secs < 0) {
244 errno = EINVAL;
245 return false;
248 if (fstat(fd, &st) != 0) {
249 warn("can not get file status");
250 return false;
253 if (st.st_uid != ROOT_UID || st.st_gid != getgid() || st.st_mode != (S_IFREG | 0000)) {
254 warnx("timestamp uid, gid or mode wrong");
255 return false;
258 /* This timestamp was created, but never set.
259 Invalid, but no error. */
260 if (!timespecisset(&st.st_atim)
261 || !timespecisset(&st.st_mtim))
262 return false;
264 if (clock_gettime(CLOCK_BOOTTIME, &ts[0]) < 0
265 || clock_gettime(CLOCK_REALTIME, &ts[1]) < 0) {
266 warn("clock_gettime");
267 return false;
270 /* Check if timestamp is too old. */
271 if (timespeccmp(&st.st_atim, &ts[0], <)
272 || timespeccmp(&st.st_mtim, &ts[1], <))
273 return false;
275 /* Check if timestamp is too far in the future. */
276 timespecadd(&ts[0], &timeout, &ts[0]);
277 timespecadd(&ts[1], &timeout, &ts[1]);
279 if (timespeccmp(&st.st_atim, &ts[0], >)
280 || timespeccmp(&st.st_mtim, &ts[1], >)) {
281 warnx("timestamp too far in the future");
282 return false;
285 return true;
288 int timestamp_open(bool *valid, time_t secs)
290 struct timespec ts[2] = { 0 };
291 struct stat st;
292 int dirfd, fd;
293 char name[256];
295 if (secs < 0) {
296 errno = EINVAL;
297 return -1;
300 *valid = false;
302 dirfd = open(TIMESTAMP_DIR, O_DIRECTORY | O_NOFOLLOW);
304 if (dirfd < 0) {
305 if (errno != ENOENT)
306 return -1;
308 if (mkdir(TIMESTAMP_DIR, 0700) < 0)
309 return -1;
311 dirfd = open(TIMESTAMP_DIR, O_DIRECTORY | O_NOFOLLOW);
313 if (dirfd < 0)
314 return -1;
317 if (fstat(dirfd, &st) < 0) {
318 return -1;
319 } else if (st.st_uid != ROOT_UID || st.st_gid != ROOT_UID || st.st_mode != (S_IFDIR | 0700)) {
320 if (!S_ISDIR(st.st_mode)) {
321 errno = ENOTDIR;
322 return -1;
325 /* If directory exists make sure that it has required mode and owner. */
326 if (fchmod(dirfd, 0700) < 0)
327 return -1;
329 if (fchown(dirfd, ROOT_UID, ROOT_UID) < 0)
330 return -1;
333 if (timestamp_name(name, sizeof(name)) == NULL)
334 return -1;
336 fd = openat(dirfd, name, O_RDONLY | O_NOFOLLOW);
338 if (fd < 0) {
339 char tmp[64];
341 if (errno != ENOENT) {
342 warn("can not open %s", name);
343 return -1;
346 if (safe_snprintf(tmp, sizeof(tmp), ".tmp-%ld", (long int)getpid()) < 0)
347 return -1;
349 fd = openat(dirfd, tmp, O_RDONLY | O_CREAT | O_EXCL | O_NOFOLLOW, 0000);
351 if (fd < 0) {
352 warn("can not open "TIMESTAMP_DIR"/%s", tmp);
353 return -1;
356 if (futimens(fd, ts) < 0 || renameat(dirfd, tmp, dirfd, name) < 0) {
357 int saved_errno = errno;
359 close(fd);
360 unlinkat(dirfd, tmp, 0);
361 errno = saved_errno;
362 return -1;
364 } else {
365 *valid = timestamp_check(fd, secs);
368 return fd;
371 int timestamp_clear(void)
373 char name[256];
374 int dirfd = open(TIMESTAMP_DIR, O_DIRECTORY | O_NOFOLLOW);
376 if (dirfd < 0)
377 return -1;
379 if (timestamp_name(name, sizeof(name)) == NULL)
380 return -1;
382 if (unlinkat(dirfd, name, 0) < 0 && errno != ENOENT)
383 return -1;
385 return 0;