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 n = snprintf(path, sizeof path, "/proc/%d/stat", pid);
98 if (n < 0 || n >= (int)sizeof path)
101 if ((fd = open(path, O_RDONLY)) == -1)
104 while ((n = read(fd, p, buf + sizeof buf - p)) != 0) {
106 if (errno == EAGAIN || errno == EINTR)
112 if (p >= buf + sizeof buf)
117 /* error if it contains NULL bytes */
118 if (n != 0 || memchr(buf, '\0', p - buf))
121 /* Get the 7th field, 5 fields after the last ')',
122 * (2th field) because the 5th field 'comm' can include
123 * spaces and closing paranthesis too.
124 * See https://www.sudo.ws/alerts/linux_tty.html
126 if ((p = strrchr(buf, ')')) == NULL)
130 for ((p = strtok_r(p, " ", &saveptr)); p;
131 (p = strtok_r(NULL, " ", &saveptr))) {
134 *ttynr = strtonum(p, INT_MIN, INT_MAX, &errstr);
140 *starttime = strtoull(p, &ep, 10);
142 (errno == ERANGE && *starttime == ULLONG_MAX))
153 #error "proc_info not implemented"
157 timestamp_path(char *buf, size_t len)
160 unsigned long long starttime;
164 if ((sid = getsid(0)) == -1)
166 if (proc_info(ppid, &ttynr, &starttime) == -1)
168 n = snprintf(buf, len, TIMESTAMP_DIR "/%d-%d-%d-%llu-%d",
169 ppid, sid, ttynr, starttime, getuid());
170 if (n < 0 || n >= (int)len)
176 timestamp_set(int fd, int secs)
178 struct timespec ts[2], timeout = { .tv_sec = secs, .tv_nsec = 0 };
180 if (clock_gettime(CLOCK_BOOTTIME, &ts[0]) == -1 ||
181 clock_gettime(CLOCK_REALTIME, &ts[1]) == -1)
184 timespecadd(&ts[0], &timeout, &ts[0]);
185 timespecadd(&ts[1], &timeout, &ts[1]);
186 return futimens(fd, ts);
190 * Returns 1 if the timestamp is valid, 0 if its invalid
193 timestamp_check(int fd, int secs)
195 struct timespec ts[2], timeout = { .tv_sec = secs, .tv_nsec = 0 };
198 if (fstat(fd, &st) == -1)
201 if (!timespecisset(&st.st_atim) || !timespecisset(&st.st_mtim))
204 if (clock_gettime(CLOCK_BOOTTIME, &ts[0]) == -1 ||
205 clock_gettime(CLOCK_REALTIME, &ts[1]) == -1)
208 /* check if timestamp is too old */
209 if (timespeccmp(&st.st_atim, &ts[0], <) ||
210 timespeccmp(&st.st_mtim, &ts[1], <))
213 /* check if timestamp is too far in the future */
214 timespecadd(&ts[0], &timeout, &ts[0]);
215 timespecadd(&ts[1], &timeout, &ts[1]);
216 if (timespeccmp(&st.st_atim, &ts[0], >) ||
217 timespeccmp(&st.st_mtim, &ts[1], >))
224 timestamp_open(int *valid, int secs)
226 struct timespec ts[2] = {0};
234 if (stat(TIMESTAMP_DIR, &st) == -1) {
237 if (mkdir(TIMESTAMP_DIR, 0700) == -1)
239 } else if (st.st_uid != 0 || st.st_mode != (S_IFDIR | 0700)) {
243 if (timestamp_path(path, sizeof path) == -1)
246 if (stat(path, &st) != -1 && (st.st_uid != 0 || st.st_gid != getgid()|| st.st_mode != (S_IFREG | 0000)))
249 fd = open(path, O_RDONLY|O_NOFOLLOW);
254 n = snprintf(tmp, sizeof tmp, TIMESTAMP_DIR "/.tmp-%d", getpid());
255 if (n < 0 || n >= (int)sizeof tmp)
258 fd = open(tmp, O_RDONLY|O_CREAT|O_EXCL|O_NOFOLLOW, 0000);
261 if (futimens(fd, ts) == -1 || rename(tmp, path) == -1) {
269 *valid = timestamp_check(fd, secs);
279 if (timestamp_path(path, sizeof path) == -1)
281 if (unlink(path) == -1 && errno != ENOENT)