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"
69 # define TIMESTAMP_DIR "/tmp/doas"
72 #if defined(TIMESTAMP_TMPFS) && defined(__linux__)
74 # define TMPFS_MAGIC 0x01021994
79 /* Use tty_nr from /proc/self/stat instead of using
80 * ttyname(3), stdin, stdout and stderr are user
81 * controllable and would allow to reuse timestamps
82 * from another writable terminal.
83 * See https://www.sudo.ws/alerts/tty_tickets.html
86 proc_info(pid_t pid, int *ttynr, unsigned long long *starttime)
90 char *p, *saveptr, *ep;
96 if (snprintf(path, sizeof path, "/proc/%d/stat", pid) == -1)
99 if ((fd = open(path, O_RDONLY)) == -1)
102 while ((n = read(fd, p, buf + sizeof buf - p)) != 0) {
104 if (errno == EAGAIN || errno == EINTR)
109 if (p >= buf + sizeof buf)
114 /* error if it contains NULL bytes */
115 if (n != 0 || memchr(buf, '\0', p - buf))
118 /* Get the 7th field, 5 fields after the last ')',
119 * (2th field) because the 5th field 'comm' can include
120 * spaces and closing paranthesis too.
121 * See https://www.sudo.ws/alerts/linux_tty.html
123 if ((p = strrchr(buf, ')')) == NULL)
127 for ((p = strtok_r(p, " ", &saveptr)); p;
128 (p = strtok_r(NULL, " ", &saveptr))) {
131 *ttynr = strtonum(p, INT_MIN, INT_MAX, &errstr);
137 *starttime = strtoull(p, &ep, 10);
139 (errno == ERANGE && *starttime == ULLONG_MAX))
150 #error "proc_info not implemented"
156 static char buf[128];
158 unsigned long long starttime;
159 pid_t ppid, sid, leader;
160 if ((fd = open("/dev/tty", O_RDONLY)) == -1)
161 err(1, "open: /dev/tty");
162 if (ioctl(fd, TIOCGSID, &leader) == -1)
163 err(1, "ioctl: failed to get session leader");
165 if (proc_info(leader, &tty, &starttime) == -1)
166 errx(1, "failed to get tty number");
168 if ((sid = getsid(0)) == -1)
170 if (snprintf(buf, sizeof buf, ".%d_%d_%llu_%d_%d",
171 tty, leader, starttime, ppid, sid) == -1)
181 if (fstat(fd, &st) == -1)
184 if ((st.st_mode & S_IFMT) != S_IFDIR)
185 errx(0, "timestamp directory is not a directory");
186 if ((st.st_mode & (S_IWGRP|S_IRGRP|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH)) != 0)
187 errx(1, "timestamp directory permissions wrong");
188 if (st.st_uid != 0 || st.st_gid != 0)
189 errx(1, "timestamp directory is not owned by root");
191 #if defined(TIMESTAMP_TMPFS) && defined(__linux__)
193 if (fstatfs(fd, &sf) == -1)
196 if (sf.f_type != TMPFS_MAGIC)
197 errx(1, "timestamp directory not on tmpfs");
210 if ((fd = open(TIMESTAMP_DIR, O_RDONLY | O_DIRECTORY)) == -1) {
211 if (errno == ENOENT) {
215 if (mkdir(TIMESTAMP_DIR, (S_IRUSR|S_IWUSR|S_IXUSR)) != 0)
217 if (setegid(gid) != 0)
221 err(1, "failed to open timestamp directory: %s", TIMESTAMP_DIR);
225 if (checktsdir(fd) != 0)
232 checktsfile(int fd, size_t *tssize)
237 if (fstat(fd, &st) == -1)
239 if ((st.st_mode & S_IFMT) != S_IFREG)
240 errx(1, "timestamp is not a regular file");
241 if ((st.st_mode & (S_IWGRP|S_IRGRP|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH)) != 0)
242 errx(1, "timestamp has wrong permissions");
245 if (st.st_uid != 0 || st.st_gid != gid)
246 errx(1, "timestamp has wrong owner");
248 *tssize = st.st_size;
254 validts(int fd, int secs)
256 struct timespec mono, real, ts_mono, ts_real, timeout;
258 if (read(fd, &ts_mono, sizeof ts_mono) != sizeof ts_mono ||
259 read(fd, &ts_real, sizeof ts_real) != sizeof ts_real)
261 if (!timespecisset(&ts_mono) || !timespecisset(&ts_real))
262 errx(1, "timespecisset");
264 if (clock_gettime(CLOCK_MONOTONIC_RAW, &mono) == -1 ||
265 clock_gettime(CLOCK_REALTIME, &real) == -1)
266 err(1, "clock_gettime");
268 if (timespeccmp(&mono, &ts_mono, >) ||
269 timespeccmp(&real, &ts_real, >))
272 memset(&timeout, 0, sizeof timeout);
273 timeout.tv_sec = secs;
274 timespecadd(&timeout, &mono, &mono);
275 timespecadd(&timeout, &real, &real);
277 if (timespeccmp(&mono, &ts_mono, <) ||
278 timespeccmp(&real, &ts_real, <))
279 errx(1, "timestamp is too far in the future");
285 persist_set(int fd, int secs)
287 struct timespec mono, real, ts_mono, ts_real, timeout;
289 if (clock_gettime(CLOCK_MONOTONIC_RAW, &mono) == -1 ||
290 clock_gettime(CLOCK_REALTIME, &real) == -1)
291 err(1, "clock_gettime");
293 memset(&timeout, 0, sizeof timeout);
294 timeout.tv_sec = secs;
295 timespecadd(&timeout, &mono, &ts_mono);
296 timespecadd(&timeout, &real, &ts_real);
298 if (lseek(fd, 0, SEEK_SET) == -1)
300 if (write(fd, (void *)&ts_mono, sizeof ts_mono) != sizeof ts_mono ||
301 write(fd, (void *)&ts_real, sizeof ts_real) != sizeof ts_real)
308 persist_open(int *valid, int secs)
315 if ((name = tsname()) == NULL)
316 errx(1, "failed to get timestamp name");
317 if ((dirfd = opentsdir()) == -1)
318 errx(1, "opentsdir");
320 if ((fd = openat(dirfd, name, (O_RDWR), (S_IRUSR|S_IWUSR))) == -1)
322 err(1, "open timestamp file");
325 if ((fd = openat(dirfd, name, (O_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW),
326 (S_IRUSR|S_IWUSR))) == -1)
327 err(1, "open timestamp file");
331 if (checktsfile(fd, &tssize) == -1)
332 err(1, "checktsfile");
334 /* The timestamp size is 0 if its a new file or a
335 * timestamp that was never set, its not valid but
336 * can be used to write the new timestamp.
337 * If the size does not match the expected size it
338 * is incomplete and should never be used
340 if (tssize == sizeof(struct timespec) * 2)
341 *valid = validts(fd, secs) == 0;
342 else if (tssize != 0)
343 errx(1, "corrupt timestamp file");
354 if ((name = tsname()) == NULL)
355 errx(1, "failed to get timestamp name");
356 if ((dirfd = opentsdir()) == -1)
357 errx(1, "opentsdir");
358 if (unlinkat(dirfd, name, 0) == -1 && errno != ENOENT)