]> git.armaanb.net Git - opendoas.git/blob - persist_timestamp.c
persist_timestamp: use /proc/self/stat to get tty_nr
[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 #ifndef TMPFS_MAGIC
23 # define TMPFS_MAGIC 0x01021994
24 #endif
25
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) {                            \
35                         (vsp)->tv_sec++;                                                                \
36                         (vsp)->tv_nsec -= 1000000000L;                                  \
37                 }                                                                                                       \
38         } while (0)
39
40
41 #ifdef __linux__
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
47  */
48 static int
49 ttynr()
50 {
51         char buf[1024];
52         char *p, *p1, *saveptr;
53         const char *errstr;
54         int fd, n;
55
56         p = buf;
57
58         if ((fd = open("/proc/self/stat", O_RDONLY)) == -1)
59                 return -1;
60
61         while ((n = read(fd, p, buf + sizeof buf - p)) != 0) {
62                 if (n == -1) {
63                         if (errno == EAGAIN || errno == EINTR)
64                                 continue;
65                         else
66                                 break;
67                 }
68                 p += n;
69                 if (p >= buf + sizeof buf)
70                         break;
71         }
72         close(fd);
73         /* error if it contains NULL bytes */
74         if (n != 0 || memchr(buf, '\0', p - buf))
75                 return -1;
76
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
81          */
82         if ((p = strrchr(buf, ')')) == NULL)
83                 return -1;
84         for ((p1 = strtok_r(p, " ", &saveptr)), n = 0; p1;
85             (p1 = strtok_r(NULL, " ", &saveptr)), n++)
86                 if (n == 5)
87                         break;
88         if (p1 == NULL || n != 5)
89                 return -1;
90
91         n = strtonum(p1, INT_MIN, INT_MAX, &errstr);
92         if (errstr)
93                 return -1;
94
95         return n;
96 }
97 #else
98 #error "ttynr not implemented"
99 #endif
100
101 static char pathbuf[PATH_MAX];
102
103 static int
104 tspath(const char **path)
105 {
106         int tty;
107         pid_t ppid;
108         if (*pathbuf == '\0') {
109                 if ((tty = ttynr()) == -1)
110                         errx(1, "failed to get tty number");
111                 ppid = getppid();
112                 if (snprintf(pathbuf, sizeof pathbuf, "%s/.%d_%d",
113                     TIMESTAMP_DIR, tty, ppid) == -1)
114                         return -1;
115         }
116         *path = pathbuf;
117         return 0;
118 }
119
120 static int
121 checktsdir(const char *path)
122 {
123         char *dir, *buf;
124         struct stat st;
125         struct statfs sf;
126         gid_t gid;
127
128         if (!(buf = strdup(path)))
129                 err(1, "strdup");
130         dir = dirname(buf);
131
132 check:
133         if (lstat(dir, &st) == -1) {
134                 if (errno == ENOENT) {
135                         gid = getegid();
136                         if (setegid(0) != 0)
137                                 err(1, "setegid");
138                         if (mkdir(dir, (S_IRUSR|S_IWUSR|S_IXUSR)) != 0)
139                                 err(1, "mkdir");
140                         if (setegid(gid) != 0)
141                                 err(1, "setegid");
142                         goto check;
143                 } else {
144                         err(1, "stat");
145                 }
146         }
147
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)
155                 err(1, "statfs");
156         if (sf.f_type != TMPFS_MAGIC)
157                 errx(1, "timestamp directory not on tmpfs");
158
159         free(buf);
160         return 0;
161 }
162
163 static int
164 timestamp_read(int fd, struct timespec *mono, struct timespec *real)
165 {
166         if (read(fd, (void *)mono, sizeof *mono) != sizeof *mono ||
167             read(fd, (void *)real, sizeof *real) != sizeof *mono)
168                 err(1, "read");
169         if (!timespecisset(mono) || !timespecisset(real))
170                 errx(1, "timespecisset");
171         return 0;
172 }
173
174 int
175 persist_check(int fd, int secs)
176 {
177         struct timespec mono, real, ts_mono, ts_real, timeout;
178
179         if (timestamp_read(fd, &ts_mono, &ts_real) != 0)
180                 return 1;
181
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, ?)");
186
187         if (timespeccmp(&mono, &ts_mono, >) ||
188             timespeccmp(&real, &ts_real, >))
189                 return 1;
190
191         memset(&timeout, 0, sizeof timeout);
192         timeout.tv_sec = secs;
193         timespecadd(&timeout, &mono, &mono);
194         timespecadd(&timeout, &real, &real);
195
196         if (timespeccmp(&mono, &ts_mono, <) ||
197             timespeccmp(&real, &ts_real, <))
198                 errx(1, "timestamp is too far in the future");
199
200         return 0;
201 }
202
203 int
204 persist_set(int fd, int secs)
205 {
206         struct timespec mono, real, ts_mono, ts_real, timeout;
207
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, ?)");
212
213         memset(&timeout, 0, sizeof timeout);
214         timeout.tv_sec = secs;
215         timespecadd(&timeout, &mono, &ts_mono);
216         timespecadd(&timeout, &real, &ts_real);
217
218         if (lseek(fd, 0, SEEK_SET) == -1)
219                 err(1, "lseek");
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)
222                 err(1, "write");
223
224         return 0;
225 }
226
227 int
228 persist_open(int *valid, int secs)
229 {
230         struct stat st;
231         int fd;
232         gid_t gid;
233         const char *path;
234
235         if (tspath(&path) == -1)
236                 errx(1, "failed to get timestamp path");
237         if (checktsdir(path))
238                 errx(1, "checktsdir");
239
240         if ((fd = open(path, (O_RDWR), (S_IRUSR|S_IWUSR))) == -1)
241                 if (errno != ENOENT)
242                         err(1, "open: %s", path);
243
244         if (fd == -1) {
245                 if ((fd = open(path, (O_RDWR|O_CREAT|O_EXCL), (S_IRUSR|S_IWUSR))) == -1)
246                         err(1, "open: %s", path);
247                 *valid = 0;
248                 return fd;
249         }
250
251         if (fstat(fd, &st) == -1)
252                 err(1, "stat");
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");
257
258         gid = getegid();
259         if (st.st_uid != 0 || st.st_gid != gid)
260                 errx(1, "timestamp has wrong owner");
261
262         if (st.st_size == 0) {
263                 *valid = 0;
264                 return fd;
265         }
266
267         if (st.st_size != sizeof(struct timespec) * 2)
268                 errx(1, "corrupt timestamp file");
269
270         *valid = persist_check(fd, secs) == 0;
271
272         return fd;
273 }
274
275 int
276 persist_clear()
277 {
278         const char *path;
279         if (tspath(&path) == -1)
280                 errx(1, "failed to get timestamp path");
281         if (unlink(path) == -1 && errno != ENOENT)
282                 return -1;
283         return 0;
284 }