]> git.armaanb.net Git - opendoas.git/blob - persist_timestamp.c
persist_timestamp: use open directory fd to check and work with timestamp files
[opendoas.git] / persist_timestamp.c
1 #include <ctype.h>
2 #include <err.h>
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <libgen.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <time.h>
10 #include <unistd.h>
11 #include <limits.h>
12 #include <string.h>
13
14 #include <sys/stat.h>
15 #include <sys/vfs.h>
16
17 #include "includes.h"
18
19 #ifndef TIMESTAMP_DIR
20 #       define TIMESTAMP_DIR "/tmp/doas"
21 #endif
22
23 #if defined(TIMESTAMP_TMPFS) && defined(__linux__)
24 #       ifndef TMPFS_MAGIC
25 #               define TMPFS_MAGIC 0x01021994
26 #       endif
27 #endif
28
29 #define timespecisset(tsp)              ((tsp)->tv_sec || (tsp)->tv_nsec)
30 #define timespeccmp(tsp, usp, cmp)                                      \
31         (((tsp)->tv_sec == (usp)->tv_sec) ?                             \
32             ((tsp)->tv_nsec cmp (usp)->tv_nsec) :               \
33             ((tsp)->tv_sec cmp (usp)->tv_sec))
34 #define timespecadd(tsp, usp, vsp) do {                                         \
35                 (vsp)->tv_sec = (tsp)->tv_sec + (usp)->tv_sec;          \
36                 (vsp)->tv_nsec = (tsp)->tv_nsec + (usp)->tv_nsec;       \
37                 if ((vsp)->tv_nsec >= 1000000000L) {                            \
38                         (vsp)->tv_sec++;                                                                \
39                         (vsp)->tv_nsec -= 1000000000L;                                  \
40                 }                                                                                                       \
41         } while (0)
42
43
44 #ifdef __linux__
45 /* Use tty_nr from /proc/self/stat instead of using
46  * ttyname(3), stdin, stdout and stderr are user
47  * controllable and would allow to reuse timestamps
48  * from another writable terminal.
49  * See https://www.sudo.ws/alerts/tty_tickets.html
50  */
51 static int
52 ttynr()
53 {
54         char buf[1024];
55         char *p, *saveptr;
56         const char *errstr;
57         int fd, n;
58
59         p = buf;
60
61         if ((fd = open("/proc/self/stat", O_RDONLY)) == -1)
62                 return -1;
63
64         while ((n = read(fd, p, buf + sizeof buf - p)) != 0) {
65                 if (n == -1) {
66                         if (errno == EAGAIN || errno == EINTR)
67                                 continue;
68                         break;
69                 }
70                 p += n;
71                 if (p >= buf + sizeof buf)
72                         break;
73         }
74         close(fd);
75
76         /* error if it contains NULL bytes */
77         if (n != 0 || memchr(buf, '\0', p - buf))
78                 return -1;
79
80         /* Get the 7th field, 5 fields after the last ')',
81          * because the 5th field 'comm' can include spaces
82          * and closing paranthesis too.
83          * See https://www.sudo.ws/alerts/linux_tty.html
84          */
85         if ((p = strrchr(buf, ')')) == NULL)
86                 return -1;
87         for ((p = strtok_r(p, " ", &saveptr)), n = 0; p && n < 5;
88             (p = strtok_r(NULL, " ", &saveptr)), n++)
89                 ;
90         if (p == NULL || n != 5)
91                 return -1;
92
93         n = strtonum(p, INT_MIN, INT_MAX, &errstr);
94         if (errstr)
95                 return -1;
96
97         return n;
98 }
99 #else
100 #error "ttynr not implemented"
101 #endif
102
103 static const char *
104 tsname()
105 {
106         static char buf[128];
107         int tty;
108         pid_t ppid, sid;
109         if ((tty = ttynr()) == -1)
110                 errx(1, "failed to get tty number");
111         ppid = getppid();
112         if ((sid = getsid(0)) == -1)
113                 err(1, "getsid");
114         if (snprintf(buf, sizeof buf, ".%d_%d_%d", tty, ppid, sid) == -1)
115                 return NULL;
116         return buf;
117 }
118
119 static int
120 checktsdir(int fd)
121 {
122         struct stat st;
123
124         if (fstat(fd, &st) == -1)
125                 err(1, "fstatat");
126
127         if ((st.st_mode & S_IFMT) != S_IFDIR)
128                 errx(0, "timestamp directory is not a directory");
129         if ((st.st_mode & (S_IWGRP|S_IRGRP|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH)) != 0)
130                 errx(1, "timestamp directory permissions wrong");
131         if (st.st_uid != 0 || st.st_gid != 0)
132                 errx(1, "timestamp directory is not owned by root");
133
134 #if defined(TIMESTAMP_TMPFS) && defined(__linux__)
135         struct statfs sf;
136         if (fstatfs(fd, &sf) == -1)
137                 err(1, "statfs");
138
139         if (sf.f_type != TMPFS_MAGIC)
140                 errx(1, "timestamp directory not on tmpfs");
141 #endif
142
143         return 0;
144 }
145
146 static int
147 opentsdir()
148 {
149         gid_t gid;
150         int fd;
151
152 reopen:
153         if ((fd = open(TIMESTAMP_DIR, O_RDONLY | O_DIRECTORY)) == -1) {
154                 if (errno == ENOENT) {
155                         gid = getegid();
156                         if (setegid(0) != 0)
157                                 err(1, "setegid");
158                         if (mkdir(TIMESTAMP_DIR, (S_IRUSR|S_IWUSR|S_IXUSR)) != 0)
159                                 err(1, "mkdir");
160                         if (setegid(gid) != 0)
161                                 err(1, "setegid");
162                         goto reopen;
163                 } else {
164                         err(1, "failed to open timestamp directory: %s", TIMESTAMP_DIR);
165                 }
166         }
167
168         if (checktsdir(fd) != 0)
169                 return -1;
170
171         return fd;
172 }
173
174 static int
175 checktsfile(int fd, size_t *tssize)
176 {
177         struct stat st;
178         gid_t gid;
179
180         if (fstat(fd, &st) == -1)
181                 err(1, "stat");
182         if ((st.st_mode & S_IFMT) != S_IFREG)
183                 errx(1, "timestamp is not a file");
184         if ((st.st_mode & (S_IWGRP|S_IRGRP|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH)) != 0)
185                 errx(1, "timestamp permissions wrong");
186
187         gid = getegid();
188         if (st.st_uid != 0 || st.st_gid != gid)
189                 errx(1, "timestamp has wrong owner");
190
191         *tssize = st.st_size;
192
193         return 0;
194 }
195
196 int
197 persist_check(int fd, int secs)
198 {
199         struct timespec mono, real, ts_mono, ts_real, timeout;
200
201         if (read(fd, &ts_mono, sizeof ts_mono) != sizeof ts_mono ||
202             read(fd, &ts_real, sizeof ts_real) != sizeof ts_mono)
203                 err(1, "read");
204         if (!timespecisset(&ts_mono) || !timespecisset(&ts_real))
205                 errx(1, "timespecisset");
206
207         if (clock_gettime(CLOCK_MONOTONIC_RAW, &mono) == -1 ||
208             clock_gettime(CLOCK_REALTIME, &real) == -1)
209                 err(1, "clock_gettime");
210
211         if (timespeccmp(&mono, &ts_mono, >) ||
212             timespeccmp(&real, &ts_real, >))
213                 return -1;
214
215         memset(&timeout, 0, sizeof timeout);
216         timeout.tv_sec = secs;
217         timespecadd(&timeout, &mono, &mono);
218         timespecadd(&timeout, &real, &real);
219
220         if (timespeccmp(&mono, &ts_mono, <) ||
221             timespeccmp(&real, &ts_real, <))
222                 errx(1, "timestamp is too far in the future");
223
224         return 0;
225 }
226
227 int
228 persist_set(int fd, int secs)
229 {
230         struct timespec mono, real, ts_mono, ts_real, timeout;
231
232         if (clock_gettime(CLOCK_MONOTONIC_RAW, &mono) == -1 ||
233             clock_gettime(CLOCK_REALTIME, &real) == -1)
234                 err(1, "clock_gettime");
235
236         memset(&timeout, 0, sizeof timeout);
237         timeout.tv_sec = secs;
238         timespecadd(&timeout, &mono, &ts_mono);
239         timespecadd(&timeout, &real, &ts_real);
240
241         if (lseek(fd, 0, SEEK_SET) == -1)
242                 err(1, "lseek");
243         if (write(fd, (void *)&ts_mono, sizeof ts_mono) != sizeof ts_mono ||
244             write(fd, (void *)&ts_real, sizeof ts_real) != sizeof ts_real)
245                 err(1, "write");
246
247         return 0;
248 }
249
250 int
251 persist_open(int *valid, int secs)
252 {
253         int dirfd, fd;
254         const char *name;
255
256         if ((name = tsname()) == NULL)
257                 errx(1, "failed to get timestamp name");
258         if ((dirfd = opentsdir()) == -1)
259                 errx(1, "opentsdir");
260
261         if ((fd = openat(dirfd, name, (O_RDWR), (S_IRUSR|S_IWUSR))) == -1)
262                 if (errno != ENOENT)
263                         err(1, "open: %s", name);
264
265         if (fd == -1) {
266                 if ((fd = openat(dirfd, name, (O_RDWR|O_CREAT|O_EXCL), (S_IRUSR|S_IWUSR))) == -1)
267                         err(1, "open: %s", name);
268                 *valid = 0;
269                 goto ret;
270         }
271
272         size_t tssize;
273         if (checktsfile(fd, &tssize) == -1)
274                 err(1, "checktsfile");
275
276         if (tssize == 0) {
277                 *valid = 0;
278                 goto ret;
279         }
280
281         if (tssize != sizeof(struct timespec) * 2)
282                 errx(1, "corrupt timestamp file");
283
284         *valid = persist_check(fd, secs) == 0;
285 ret:
286         close(dirfd);
287         return fd;
288 }
289
290 int
291 persist_clear()
292 {
293         const char *name;
294         int dirfd;
295         if ((name = tsname()) == NULL)
296                 errx(1, "failed to get timestamp name");
297         if ((dirfd = opentsdir()) == -1)
298                 errx(1, "opentsdir");
299         if (unlinkat(dirfd, name, 0) == -1 && errno != ENOENT)
300                 return -1;
301         close(dirfd);
302         return 0;
303 }