20 # define TIMESTAMP_DIR "/tmp/doas"
23 # define TMPFS_MAGIC 0x01021994
26 #define timespecisset(tsp) ((tsp)->tv_sec || (tsp)->tv_nsec)
27 #define timespeccmp(tsp, usp, cmp) \
28 (((tsp)->tv_sec == (usp)->tv_sec) ? \
29 ((tsp)->tv_nsec cmp (usp)->tv_nsec) : \
30 ((tsp)->tv_sec cmp (usp)->tv_sec))
31 #define timespecadd(tsp, usp, vsp) do { \
32 (vsp)->tv_sec = (tsp)->tv_sec + (usp)->tv_sec; \
33 (vsp)->tv_nsec = (tsp)->tv_nsec + (usp)->tv_nsec; \
34 if ((vsp)->tv_nsec >= 1000000000L) { \
36 (vsp)->tv_nsec -= 1000000000L; \
42 /* Use tty_nr from /proc/self/stat instead of using
43 * ttyname(3), stdin, stdout and stderr are user
44 * controllable and would allow to reuse timestamps
45 * from another writable terminal.
46 * See https://www.sudo.ws/alerts/tty_tickets.html
52 char *p, *p1, *saveptr;
58 if ((fd = open("/proc/self/stat", O_RDONLY)) == -1)
61 while ((n = read(fd, p, buf + sizeof buf - p)) != 0) {
63 if (errno == EAGAIN || errno == EINTR)
69 if (p >= buf + sizeof buf)
73 /* error if it contains NULL bytes */
74 if (n != 0 || memchr(buf, '\0', p - buf))
77 /* Get the 7th field, 5 fields after the last ')',
78 * because the 5th field 'comm' can include spaces
79 * and closing paranthesis too.
80 * See https://www.sudo.ws/alerts/linux_tty.html
82 if ((p = strrchr(buf, ')')) == NULL)
84 for ((p1 = strtok_r(p, " ", &saveptr)), n = 0; p1;
85 (p1 = strtok_r(NULL, " ", &saveptr)), n++)
88 if (p1 == NULL || n != 5)
91 n = strtonum(p1, INT_MIN, INT_MAX, &errstr);
98 #error "ttynr not implemented"
101 static char pathbuf[PATH_MAX];
104 tspath(const char **path)
108 if (*pathbuf == '\0') {
109 if ((tty = ttynr()) == -1)
110 errx(1, "failed to get tty number");
112 if (snprintf(pathbuf, sizeof pathbuf, "%s/.%d_%d",
113 TIMESTAMP_DIR, tty, ppid) == -1)
121 checktsdir(const char *path)
128 if (!(buf = strdup(path)))
133 if (lstat(dir, &st) == -1) {
134 if (errno == ENOENT) {
138 if (mkdir(dir, (S_IRUSR|S_IWUSR|S_IXUSR)) != 0)
140 if (setegid(gid) != 0)
148 if ((st.st_mode & S_IFMT) != S_IFDIR)
149 errx(1, "timestamp directory is not a directory");
150 if ((st.st_mode & (S_IWGRP|S_IRGRP|S_IWOTH|S_IROTH)) != 0)
151 errx(1, "timestamp directory permissions wrong");
152 if (st.st_uid != 0 || st.st_gid != 0)
153 errx(1, "timestamp directory is not owned by root");
154 if (statfs(dir, &sf) == -1)
156 if (sf.f_type != TMPFS_MAGIC)
157 errx(1, "timestamp directory not on tmpfs");
164 timestamp_read(int fd, struct timespec *mono, struct timespec *real)
166 if (read(fd, (void *)mono, sizeof *mono) != sizeof *mono ||
167 read(fd, (void *)real, sizeof *real) != sizeof *mono)
169 if (!timespecisset(mono) || !timespecisset(real))
170 errx(1, "timespecisset");
175 persist_check(int fd, int secs)
177 struct timespec mono, real, ts_mono, ts_real, timeout;
179 if (timestamp_read(fd, &ts_mono, &ts_real) != 0)
182 if (clock_gettime(CLOCK_MONOTONIC, &mono) != 0 || !timespecisset(&mono))
183 err(1, "clock_gettime(CLOCK_MONOTONIC, ?)");
184 if (clock_gettime(CLOCK_REALTIME, &real) != 0 || !timespecisset(&real))
185 err(1, "clock_gettime(CLOCK_REALTIME, ?)");
187 if (timespeccmp(&mono, &ts_mono, >) ||
188 timespeccmp(&real, &ts_real, >))
191 memset(&timeout, 0, sizeof timeout);
192 timeout.tv_sec = secs;
193 timespecadd(&timeout, &mono, &mono);
194 timespecadd(&timeout, &real, &real);
196 if (timespeccmp(&mono, &ts_mono, <) ||
197 timespeccmp(&real, &ts_real, <))
198 errx(1, "timestamp is too far in the future");
204 persist_set(int fd, int secs)
206 struct timespec mono, real, ts_mono, ts_real, timeout;
208 if (clock_gettime(CLOCK_MONOTONIC, &mono) != 0 || !timespecisset(&mono))
209 err(1, "clock_gettime(XLOCK_MONOTONIC, ?)");
210 if (clock_gettime(CLOCK_REALTIME, &real) != 0 || !timespecisset(&real))
211 err(1, "clock_gettime(CLOCK_REALTIME, ?)");
213 memset(&timeout, 0, sizeof timeout);
214 timeout.tv_sec = secs;
215 timespecadd(&timeout, &mono, &ts_mono);
216 timespecadd(&timeout, &real, &ts_real);
218 if (lseek(fd, 0, SEEK_SET) == -1)
220 if (write(fd, (void *)&ts_mono, sizeof ts_mono) != sizeof ts_mono ||
221 write(fd, (void *)&ts_real, sizeof ts_real) != sizeof ts_real)
228 persist_open(int *valid, int secs)
235 if (tspath(&path) == -1)
236 errx(1, "failed to get timestamp path");
237 if (checktsdir(path))
238 errx(1, "checktsdir");
240 if ((fd = open(path, (O_RDWR), (S_IRUSR|S_IWUSR))) == -1)
242 err(1, "open: %s", path);
245 if ((fd = open(path, (O_RDWR|O_CREAT|O_EXCL), (S_IRUSR|S_IWUSR))) == -1)
246 err(1, "open: %s", path);
251 if (fstat(fd, &st) == -1)
253 if ((st.st_mode & S_IFMT) != S_IFREG)
254 errx(1, "timestamp is not a file");
255 if ((st.st_mode & (S_IWGRP|S_IRGRP|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH)) != 0)
256 errx(1, "timestamp permissions wrong");
259 if (st.st_uid != 0 || st.st_gid != gid)
260 errx(1, "timestamp has wrong owner");
262 if (st.st_size == 0) {
267 if (st.st_size != sizeof(struct timespec) * 2)
268 errx(1, "corrupt timestamp file");
270 *valid = persist_check(fd, secs) == 0;
279 if (tspath(&path) == -1)
280 errx(1, "failed to get timestamp path");
281 if (unlink(path) == -1 && errno != ENOENT)