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|O_NOFOLLOW)) == -1) {
102 warn("failed to open: %s", path);
106 while ((n = read(fd, p, buf + (sizeof buf - 1) - p)) != 0) {
108 if (errno == EAGAIN || errno == EINTR)
110 warn("read: %s", path);
115 if (p >= buf + (sizeof buf - 1))
120 /* error if it contains NULL bytes */
121 if (n != 0 || memchr(buf, '\0', p - buf - 1) != NULL) {
122 warn("NUL in: %s", path);
128 /* Get the 7th field, 5 fields after the last ')',
129 * (2th field) because the 5th field 'comm' can include
130 * spaces and closing paranthesis too.
131 * See https://www.sudo.ws/alerts/linux_tty.html
133 if ((p = strrchr(buf, ')')) == NULL)
137 for ((p = strtok_r(p, " ", &saveptr)); p;
138 (p = strtok_r(NULL, " ", &saveptr))) {
141 *ttynr = strtonum(p, INT_MIN, INT_MAX, &errstr);
147 *starttime = strtoull(p, &ep, 10);
149 (errno == ERANGE && *starttime == ULLONG_MAX))
158 #error "proc_info not implemented"
162 timestamp_path(char *buf, size_t len)
165 unsigned long long starttime;
169 if ((sid = getsid(0)) == -1)
171 if (proc_info(ppid, &ttynr, &starttime) == -1)
173 n = snprintf(buf, len, TIMESTAMP_DIR "/%d-%d-%d-%llu-%d",
174 ppid, sid, ttynr, starttime, getuid());
175 if (n < 0 || n >= (int)len)
181 timestamp_set(int fd, int secs)
183 struct timespec ts[2], timeout = { .tv_sec = secs, .tv_nsec = 0 };
185 if (clock_gettime(CLOCK_BOOTTIME, &ts[0]) == -1 ||
186 clock_gettime(CLOCK_REALTIME, &ts[1]) == -1)
189 timespecadd(&ts[0], &timeout, &ts[0]);
190 timespecadd(&ts[1], &timeout, &ts[1]);
191 return futimens(fd, ts);
195 * Returns 1 if the timestamp is valid, 0 if its invalid
198 timestamp_check(int fd, int secs)
200 struct timespec ts[2], timeout = { .tv_sec = secs, .tv_nsec = 0 };
203 if (fstat(fd, &st) == -1)
205 if (st.st_uid != 0 || st.st_gid != getgid() || st.st_mode != (S_IFREG | 0000))
206 errx(1, "timestamp uid, gid or mode wrong");
208 if (!timespecisset(&st.st_atim) || !timespecisset(&st.st_mtim)) {
209 warnx("timestamp atim or mtime not set");
213 if (clock_gettime(CLOCK_BOOTTIME, &ts[0]) == -1 ||
214 clock_gettime(CLOCK_REALTIME, &ts[1]) == -1) {
215 warn("clock_gettime");
219 /* check if timestamp is too old */
220 if (timespeccmp(&st.st_atim, &ts[0], <) ||
221 timespeccmp(&st.st_mtim, &ts[1], <))
224 /* check if timestamp is too far in the future */
225 timespecadd(&ts[0], &timeout, &ts[0]);
226 timespecadd(&ts[1], &timeout, &ts[1]);
227 if (timespeccmp(&st.st_atim, &ts[0], >) ||
228 timespeccmp(&st.st_mtim, &ts[1], >)) {
229 warnx("timestamp too far in the future");
237 timestamp_open(int *valid, int secs)
239 struct timespec ts[2] = {0};
247 if (stat(TIMESTAMP_DIR, &st) == -1) {
250 if (mkdir(TIMESTAMP_DIR, 0700) == -1)
252 } else if (st.st_uid != 0 || st.st_mode != (S_IFDIR | 0700)) {
256 if (timestamp_path(path, sizeof path) == -1)
259 fd = open(path, O_RDONLY|O_NOFOLLOW);
265 err(1, "open: %s", path);
267 n = snprintf(tmp, sizeof tmp, TIMESTAMP_DIR "/.tmp-%d", getpid());
268 if (n < 0 || n >= (int)sizeof tmp)
271 fd = open(tmp, O_RDONLY|O_CREAT|O_EXCL|O_NOFOLLOW, 0000);
274 if (futimens(fd, ts) == -1 || rename(tmp, path) == -1) {
282 *valid = timestamp_check(fd, secs);
292 if (timestamp_path(path, sizeof path) == -1)
294 if (unlink(path) == -1 && errno != ENOENT)