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 ((fd = open(TIMESTAMP_DIR, O_RDONLY | O_DIRECTORY)) == -1) {
210 if (errno == ENOENT && isnew == 0) {
211 if (mkdir(TIMESTAMP_DIR, (S_IRUSR|S_IWUSR|S_IXUSR)) != 0)
216 err(1, "failed to open timestamp directory: %s", TIMESTAMP_DIR);
221 if (fstat(fd, &st) == -1)
224 if ((st.st_mode & S_IFMT) != S_IFDIR)
225 errx(1, "timestamp directory is not a directory");
226 if ((st.st_mode & (S_IWGRP|S_IRGRP|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH)) != 0)
227 errx(1, "timestamp directory has wrong permissions");
230 if (fchown(fd, 0, 0) == -1)
235 if (st.st_uid != 0 || st.st_gid != 0)
236 errx(1, "timestamp directory has wrong owner or group");
239 #if defined(TIMESTAMP_TMPFS) && defined(__linux__)
241 if (fstatfs(fd, &sf) == -1)
244 if (sf.f_type != TMPFS_MAGIC)
245 errx(1, "timestamp directory not on tmpfs");
252 * Returns 1 if the timestamp is valid, 0 if its invalid
255 timestamp_valid(int fd, int secs)
257 struct timespec mono, real, ts_mono, ts_real, timeout;
259 if (read(fd, &ts_mono, sizeof ts_mono) != sizeof ts_mono ||
260 read(fd, &ts_real, sizeof ts_real) != sizeof ts_real)
262 if (!timespecisset(&ts_mono) || !timespecisset(&ts_real))
263 errx(1, "timespecisset");
265 if (clock_gettime(CLOCK_MONOTONIC_RAW, &mono) == -1 ||
266 clock_gettime(CLOCK_REALTIME, &real) == -1)
267 err(1, "clock_gettime");
269 if (timespeccmp(&mono, &ts_mono, >) ||
270 timespeccmp(&real, &ts_real, >))
273 memset(&timeout, 0, sizeof timeout);
274 timeout.tv_sec = secs;
275 timespecadd(&timeout, &mono, &mono);
276 timespecadd(&timeout, &real, &real);
278 if (timespeccmp(&mono, &ts_mono, <) ||
279 timespeccmp(&real, &ts_real, <))
280 errx(1, "timestamp is too far in the future");
286 timestamp_set(int fd, int secs)
288 struct timespec mono, real, ts_mono, ts_real, timeout;
290 if (clock_gettime(CLOCK_MONOTONIC_RAW, &mono) == -1 ||
291 clock_gettime(CLOCK_REALTIME, &real) == -1)
292 err(1, "clock_gettime");
294 memset(&timeout, 0, sizeof timeout);
295 timeout.tv_sec = secs;
296 timespecadd(&timeout, &mono, &ts_mono);
297 timespecadd(&timeout, &real, &ts_real);
299 if (lseek(fd, 0, SEEK_SET) == -1)
301 if (ftruncate(fd, 0) == -1)
303 if (write(fd, (void *)&ts_mono, sizeof ts_mono) != sizeof ts_mono ||
304 write(fd, (void *)&ts_real, sizeof ts_real) != sizeof ts_real)
311 timestamp_open(int *valid, int secs)
320 if ((name = timestamp_name()) == NULL)
321 errx(1, "failed to get timestamp name");
322 if ((dirfd = opentsdir()) == -1)
323 errx(1, "opentsdir");
325 if ((fd = openat(dirfd, name, (O_RDWR), (S_IRUSR|S_IWUSR))) == -1)
327 err(1, "open timestamp file");
330 if ((fd = openat(dirfd, name, (O_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW),
331 (S_IRUSR|S_IWUSR))) == -1)
332 err(1, "open timestamp file");
335 if (fstat(fd, &st) == -1)
337 if ((st.st_mode & S_IFMT) != S_IFREG)
338 errx(1, "timestamp is not a regular file");
339 if ((st.st_mode & (S_IWGRP|S_IRGRP|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH)) != 0)
340 errx(1, "timestamp file has wrong permissions");
343 if (st.st_uid != 0 || st.st_gid != gid)
344 errx(1, "timestamp file has wrong owner or group");
346 /* The timestamp size is 0 if its a new file or a
347 * timestamp that was never set, its not valid but
348 * can be used to write the new timestamp.
349 * If the size does not match the expected size it
350 * is incomplete and should never be used
352 if (st.st_size == sizeof (struct timespec) * 2) {
353 *valid = timestamp_valid(fd, secs);
354 } else if (st.st_size == 0) {
357 errx(1, "corrupt timestamp file");
374 if ((prefix = session_prefix()) == NULL)
375 errx(1, "failed to get timestamp session prefix");
376 plen = strlen(prefix);
378 if ((dirfd = opentsdir()) == -1)
379 errx(1, "opentsdir");
380 if ((dp = fdopendir(dirfd)) == NULL)
384 * Delete all files in the timestamp directory
385 * with the same session prefix.
387 while ((ent = readdir(dp)) != NULL) {
388 if (ent->d_type != DT_REG)
390 if (strncmp(prefix, ent->d_name, plen) != 0)
392 if (unlinkat(dirfd, ent->d_name, 0) == -1)