2 * 1) Timestamp files and directories
4 * Timestamp files MUST NOT be accessible to users other than root,
5 * this includes the name, metadata and the content of timestamp files
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.
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.
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.
20 * 2) Clock sources for timestamps
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.
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.
30 * 3) Timestamp lifetime
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
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
45 #include <sys/ioctl.h>
49 #if !defined(timespecisset) || \
50 !defined(timespeccmp) || \
52 # include "sys-time.h"
70 # define TIMESTAMP_DIR "/run/doas"
73 #if defined(TIMESTAMP_TMPFS) && defined(__linux__)
75 # define TMPFS_MAGIC 0x01021994
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
87 proc_info(pid_t pid, int *ttynr, unsigned long long *starttime)
91 char *p, *saveptr, *ep;
97 if (snprintf(path, sizeof path, "/proc/%d/stat", pid) == -1)
100 if ((fd = open(path, O_RDONLY)) == -1)
103 while ((n = read(fd, p, buf + sizeof buf - p)) != 0) {
105 if (errno == EAGAIN || errno == EINTR)
111 if (p >= buf + sizeof buf)
116 /* error if it contains NULL bytes */
117 if (n != 0 || memchr(buf, '\0', p - buf))
120 /* Get the 7th field, 5 fields after the last ')',
121 * (2th field) because the 5th field 'comm' can include
122 * spaces and closing paranthesis too.
123 * See https://www.sudo.ws/alerts/linux_tty.html
125 if ((p = strrchr(buf, ')')) == NULL)
129 for ((p = strtok_r(p, " ", &saveptr)); p;
130 (p = strtok_r(NULL, " ", &saveptr))) {
133 *ttynr = strtonum(p, INT_MIN, INT_MAX, &errstr);
139 *starttime = strtoull(p, &ep, 10);
141 (errno == ERANGE && *starttime == ULLONG_MAX))
152 #error "proc_info not implemented"
155 /* The session prefix is the tty number, the pid
156 * of the session leader and the start time of the
162 static char prefix[128];
164 unsigned long long starttime;
167 if ((fd = open("/dev/tty", O_RDONLY)) == -1)
168 err(1, "open: /dev/tty");
169 if (ioctl(fd, TIOCGSID, &leader) == -1)
170 err(1, "ioctl: failed to get session leader");
173 if (proc_info(leader, &tty, &starttime) == -1)
174 errx(1, "failed to get tty number");
175 if (snprintf(prefix, sizeof prefix, ".%d_%d_%llu_",
176 tty, leader, starttime) == -1)
184 static char name[128];
189 if ((sid = getsid(0)) == -1)
191 if ((prefix = session_prefix()) == NULL)
193 if (snprintf(name, sizeof name, "%s_%d_%d", prefix, ppid, sid) == -1)
209 if (lstat(TIMESTAMP_DIR, &stl) == -1) {
210 if (!(errno == ENOENT && isnew == 0))
211 err(1, "lstat: %s", TIMESTAMP_DIR);
212 } else if ((stl.st_mode & S_IFMT) != S_IFDIR) {
213 errx(1, "%s: not a directory", TIMESTAMP_DIR);
216 if ((fd = open(TIMESTAMP_DIR, O_RDONLY | O_DIRECTORY)) == -1) {
217 if (errno == ENOENT && isnew == 0) {
218 if (mkdir(TIMESTAMP_DIR, (S_IRUSR|S_IWUSR|S_IXUSR)) != 0)
223 err(1, "failed to open timestamp directory: %s", TIMESTAMP_DIR);
228 if (fstat(fd, &st) == -1)
231 if (stl.st_ino != st.st_ino || stl.st_dev != st.st_dev)
232 errx(1, "timestamp directory lstat and fstat are different files");
234 if ((st.st_mode & (S_IWGRP|S_IRGRP|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH)) != 0)
235 errx(1, "timestamp directory has wrong permissions");
238 if (fchown(fd, 0, 0) == -1)
243 if (st.st_uid != 0 || st.st_gid != 0)
244 errx(1, "timestamp directory has wrong owner or group");
247 #if defined(TIMESTAMP_TMPFS) && defined(__linux__)
249 if (fstatfs(fd, &sf) == -1)
252 if (sf.f_type != TMPFS_MAGIC)
253 errx(1, "timestamp directory not on tmpfs");
260 * Returns 1 if the timestamp is valid, 0 if its invalid
263 timestamp_valid(int fd, int secs)
265 struct timespec mono, real, ts_mono, ts_real, timeout;
267 if (read(fd, &ts_mono, sizeof ts_mono) != sizeof ts_mono ||
268 read(fd, &ts_real, sizeof ts_real) != sizeof ts_real)
270 if (!timespecisset(&ts_mono) || !timespecisset(&ts_real))
271 errx(1, "timespecisset");
273 if (clock_gettime(CLOCK_MONOTONIC_RAW, &mono) == -1 ||
274 clock_gettime(CLOCK_REALTIME, &real) == -1)
275 err(1, "clock_gettime");
277 if (timespeccmp(&mono, &ts_mono, >) ||
278 timespeccmp(&real, &ts_real, >))
281 memset(&timeout, 0, sizeof timeout);
282 timeout.tv_sec = secs;
283 timespecadd(&timeout, &mono, &mono);
284 timespecadd(&timeout, &real, &real);
286 if (timespeccmp(&mono, &ts_mono, <) ||
287 timespeccmp(&real, &ts_real, <))
288 errx(1, "timestamp is too far in the future");
294 timestamp_set(int fd, int secs)
296 struct timespec mono, real, ts_mono, ts_real, timeout;
298 if (clock_gettime(CLOCK_MONOTONIC_RAW, &mono) == -1 ||
299 clock_gettime(CLOCK_REALTIME, &real) == -1)
300 err(1, "clock_gettime");
302 memset(&timeout, 0, sizeof timeout);
303 timeout.tv_sec = secs;
304 timespecadd(&timeout, &mono, &ts_mono);
305 timespecadd(&timeout, &real, &ts_real);
307 if (lseek(fd, 0, SEEK_SET) == -1)
309 if (ftruncate(fd, 0) == -1)
311 if (write(fd, (void *)&ts_mono, sizeof ts_mono) != sizeof ts_mono ||
312 write(fd, (void *)&ts_real, sizeof ts_real) != sizeof ts_real)
319 timestamp_open(int *valid, int secs)
328 if ((name = timestamp_name()) == NULL)
329 errx(1, "failed to get timestamp name");
330 if ((dirfd = opentsdir()) == -1)
331 errx(1, "opentsdir");
333 if (fstatat(dirfd, name, &stl, AT_SYMLINK_NOFOLLOW) == -1) {
336 } else if ((stl.st_mode & S_IFMT) != S_IFREG) {
337 errx(1, "timestamp: not a regular file");
340 if ((fd = openat(dirfd, name, (O_RDWR), (S_IRUSR|S_IWUSR))) == -1)
342 err(1, "open timestamp file");
345 * If the file was opened, check if fstat and lstat results are
347 * If the file doesn't exist and we create it with O_CREAT|O_EXCL,
348 * it is already known that the file is a regular file.
351 if (fstat(fd, &st) == -1)
353 if (stl.st_ino != st.st_ino || stl.st_dev != st.st_dev)
354 errx(1, "timestamp file lstat and fstat are different files");
356 if ((fd = openat(dirfd, name, (O_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW),
357 (S_IRUSR|S_IWUSR))) == -1)
358 err(1, "open timestamp file");
359 if (fstat(fd, &st) == -1)
363 if ((st.st_mode & (S_IWGRP|S_IRGRP|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH)) != 0)
364 errx(1, "timestamp file has wrong permissions");
367 if (st.st_uid != 0 || st.st_gid != gid)
368 errx(1, "timestamp file has wrong owner or group");
370 /* The timestamp size is 0 if its a new file or a
371 * timestamp that was never set, its not valid but
372 * can be used to write the new timestamp.
373 * If the size does not match the expected size it
374 * is incomplete and should never be used
376 if (st.st_size == sizeof (struct timespec) * 2) {
377 *valid = timestamp_valid(fd, secs);
378 } else if (st.st_size == 0) {
381 errx(1, "corrupt timestamp file");
398 if ((prefix = session_prefix()) == NULL)
399 errx(1, "failed to get timestamp session prefix");
400 plen = strlen(prefix);
402 if ((dirfd = opentsdir()) == -1)
403 errx(1, "opentsdir");
404 if ((dp = fdopendir(dirfd)) == NULL)
408 * Delete all files in the timestamp directory
409 * with the same session prefix.
411 while ((ent = readdir(dp)) != NULL) {
412 if (ent->d_type != DT_REG)
414 if (strncmp(prefix, ent->d_name, plen) != 0)
416 if (unlinkat(dirfd, ent->d_name, 0) == -1)