]> granicus.if.org Git - sudo/blob - plugins/sudoers/starttime.c
Add SPDX-License-Identifier to files.
[sudo] / plugins / sudoers / starttime.c
1 /*
2  * SPDX-License-Identifier: ISC
3  *
4  * Copyright (c) 2012-2019 Todd C. Miller <Todd.Miller@sudo.ws>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18
19 /*
20  * This is an open source non-commercial project. Dear PVS-Studio, please check it.
21  * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
22  */
23
24 #include <config.h>
25
26 /* Large files not supported by procfs.h on Solaris. */
27 #if defined(HAVE_STRUCT_PSINFO_PR_TTYDEV)
28 # undef _FILE_OFFSET_BITS
29 # undef _LARGE_FILES
30 #endif
31
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #if defined(HAVE_KINFO_PROC_44BSD) || defined (HAVE_KINFO_PROC_OPENBSD) || defined(HAVE_KINFO_PROC2_NETBSD2)
35 # include <sys/sysctl.h>
36 #elif defined(HAVE_KINFO_PROC_FREEBSD)
37 # include <sys/sysctl.h>
38 # include <sys/user.h>
39 #endif
40 #if defined(HAVE_PROCFS_H)
41 # include <procfs.h>
42 #elif defined(HAVE_SYS_PROCFS_H)
43 # include <sys/procfs.h>
44 #endif
45 #ifdef HAVE_PSTAT_GETPROC
46 # include <sys/pstat.h>
47 #endif
48
49 #include <stdio.h>
50 #include <stdlib.h>
51 #ifdef HAVE_STRING_H
52 # include <string.h>
53 #endif /* HAVE_STRING_H */
54 #ifdef HAVE_STRINGS_H
55 # include <strings.h>
56 #endif /* HAVE_STRINGS_H */
57 #include <ctype.h>
58 #include <errno.h>
59 #include <fcntl.h>
60 #include <limits.h>
61 #include <unistd.h>
62
63 #include "sudoers.h"
64 #include "check.h"
65
66 /*
67  * Arguments for sysctl(2) when reading the process start time.
68  */
69 #if defined(HAVE_KINFO_PROC2_NETBSD)
70 # define SUDO_KERN_PROC         KERN_PROC2
71 # define sudo_kinfo_proc        kinfo_proc2
72 # define sudo_kp_namelen        6
73 #elif defined(HAVE_KINFO_PROC_OPENBSD)
74 # define SUDO_KERN_PROC         KERN_PROC
75 # define sudo_kinfo_proc        kinfo_proc
76 # define sudo_kp_namelen        6
77 #elif defined(HAVE_KINFO_PROC_FREEBSD) || defined(HAVE_KINFO_PROC_44BSD)
78 # define SUDO_KERN_PROC         KERN_PROC
79 # define sudo_kinfo_proc        kinfo_proc
80 # define sudo_kp_namelen        4
81 #endif
82
83 /*
84  * Store start time of the specified process in starttime.
85  */
86
87 #if defined(sudo_kinfo_proc)
88 int
89 get_starttime(pid_t pid, struct timespec *starttime)
90 {
91     struct sudo_kinfo_proc *ki_proc = NULL;
92     size_t size = sizeof(*ki_proc);
93     int mib[6], rc;
94     debug_decl(get_starttime, SUDOERS_DEBUG_UTIL)
95
96     /*
97      * Lookup start time for pid via sysctl.
98      */
99     mib[0] = CTL_KERN;
100     mib[1] = SUDO_KERN_PROC;
101     mib[2] = KERN_PROC_PID;
102     mib[3] = (int)pid;
103     mib[4] = sizeof(*ki_proc);
104     mib[5] = 1;
105     do {
106         struct sudo_kinfo_proc *kp;
107
108         size += size / 10;
109         if ((kp = realloc(ki_proc, size)) == NULL) {
110             rc = -1;
111             break;              /* really out of memory. */
112         }
113         ki_proc = kp;
114         rc = sysctl(mib, sudo_kp_namelen, ki_proc, &size, NULL, 0);
115     } while (rc == -1 && errno == ENOMEM);
116     if (rc != -1) {
117 #if defined(HAVE_KINFO_PROC_FREEBSD)
118         /* FreeBSD and Dragonfly */
119         TIMEVAL_TO_TIMESPEC(&ki_proc->ki_start, starttime);
120 #elif defined(HAVE_KINFO_PROC_44BSD)
121         /* 4.4BSD and macOS */
122         TIMEVAL_TO_TIMESPEC(&ki_proc->kp_proc.p_starttime, starttime);
123 #else
124         /* NetBSD and OpenBSD */
125         starttime->tv_sec = ki_proc->p_ustart_sec;
126         starttime->tv_nsec = ki_proc->p_ustart_usec * 1000;
127 #endif
128         sudo_debug_printf(SUDO_DEBUG_INFO,
129             "%s: start time for %d: { %lld, %ld }", __func__,
130             (int)pid, (long long)starttime->tv_sec, (long)starttime->tv_nsec);
131     } else {
132         sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
133             "unable to get start time for %d via KERN_PROC", (int)pid);
134     }
135     free(ki_proc);
136
137     debug_return_int(rc == -1 ? -1 : 0);
138 }
139 #elif defined(HAVE_STRUCT_PSINFO_PR_TTYDEV)
140 int
141 get_starttime(pid_t pid, struct timespec *starttime)
142 {
143     struct psinfo psinfo;
144     char path[PATH_MAX];
145     ssize_t nread;
146     int fd, ret = -1;
147     debug_decl(get_starttime, SUDOERS_DEBUG_UTIL)
148
149     /* Determine the start time from pr_start in /proc/pid/psinfo. */
150     (void)snprintf(path, sizeof(path), "/proc/%u/psinfo", (unsigned int)pid);
151     if ((fd = open(path, O_RDONLY, 0)) != -1) {
152         nread = read(fd, &psinfo, sizeof(psinfo));
153         close(fd);
154         if (nread == (ssize_t)sizeof(psinfo)) {
155             starttime->tv_sec = psinfo.pr_start.tv_sec;
156             starttime->tv_nsec = psinfo.pr_start.tv_nsec;
157             ret = 0;
158
159             sudo_debug_printf(SUDO_DEBUG_INFO,
160                 "%s: start time for %d: { %lld, %ld }", __func__, (int)pid,
161                 (long long)starttime->tv_sec, (long)starttime->tv_nsec);
162         }
163     }
164
165     if (ret == -1)
166         sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
167             "unable to get start time for %d via %s", (int)pid, path);
168     debug_return_int(ret);
169 }
170 #elif defined(__linux__)
171 int
172 get_starttime(pid_t pid, struct timespec *starttime)
173 {
174     char path[PATH_MAX];
175     char *cp, buf[1024];
176     ssize_t nread;
177     int ret = -1;
178     int fd = -1;
179     long tps;
180     debug_decl(get_starttime, SUDOERS_DEBUG_UTIL)
181
182     /*
183      * Start time is in ticks per second on Linux.
184      */
185     tps = sysconf(_SC_CLK_TCK);
186     if (tps == -1)
187         goto done;
188
189     /*
190      * Determine the start time from 22nd field in /proc/pid/stat.
191      * Ignore /proc/self/stat if it contains embedded NUL bytes.
192      * XXX - refactor common code with ttyname.c?
193      */
194     (void)snprintf(path, sizeof(path), "/proc/%u/stat", (unsigned int)pid);
195     if ((fd = open(path, O_RDONLY | O_NOFOLLOW)) != -1) {
196         cp = buf;
197         while ((nread = read(fd, cp, buf + sizeof(buf) - cp)) != 0) {
198             if (nread == -1) {
199                 if (errno == EAGAIN || errno == EINTR)
200                     continue;
201                 break;
202             }
203             cp += nread;
204             if (cp >= buf + sizeof(buf))
205                 break;
206         }
207         if (nread == 0 && memchr(buf, '\0', cp - buf) == NULL) {
208             /*
209              * Field 22 is the start time (%ull).
210              * Since the process name at field 2 "(comm)" may include
211              * whitespace (including newlines), start at the last ')' found.
212              */
213             *cp = '\0';
214             cp = strrchr(buf, ')');
215             if (cp != NULL) {
216                 char *ep = cp;
217                 int field = 1;
218
219                 while (*++ep != '\0') {
220                     if (*ep == ' ') {
221                         if (++field == 22) {
222                             unsigned long long ullval;
223
224                             /* Must start with a digit (not negative). */
225                             if (!isdigit((unsigned char)*cp)) {
226                                 errno = EINVAL;
227                                 goto done;
228                             }
229
230                             /* starttime is %ul in 2.4 and %ull in >= 2.6 */
231                             errno = 0;
232                             ullval = strtoull(cp, &ep, 10);
233                             if (ep == cp || *ep != ' ') {
234                                 errno = EINVAL;
235                                 goto done;
236                             }
237                             if (errno == ERANGE && ullval == ULLONG_MAX)
238                                 goto done;
239
240                             /* Convert from ticks to timespec */
241                             starttime->tv_sec = ullval / tps;
242                             starttime->tv_nsec =
243                                 (ullval % tps) * (1000000000 / tps);
244                             ret = 0;
245
246                             sudo_debug_printf(SUDO_DEBUG_INFO,
247                                 "%s: start time for %d: { %lld, %ld }",
248                                 __func__, (int)pid,
249                                 (long long)starttime->tv_sec,
250                                 (long)starttime->tv_nsec);
251
252                             goto done;
253                         }
254                         cp = ep + 1;
255                     }
256                 }
257             }
258         }
259     }
260     errno = ENOENT;
261
262 done:
263     if (fd != -1)
264         close(fd);
265     if (ret == -1)
266         sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
267             "unable to get start time for %d via %s", (int)pid, path);
268
269     debug_return_int(ret);
270 }
271 #elif defined(HAVE_PSTAT_GETPROC)
272 int
273 get_starttime(pid_t pid, struct timespec *starttime)
274 {
275     struct pst_status pstat;
276     int rc;
277     debug_decl(get_starttime, SUDOERS_DEBUG_UTIL)
278
279     /*
280      * Determine the start time from pst_start in struct pst_status.
281      * EOVERFLOW is not a fatal error for the fields we use.
282      * See the "EOVERFLOW Error" section of pstat_getvminfo(3).
283      */
284     rc = pstat_getproc(&pstat, sizeof(pstat), 0, pid);
285     if (rc != -1 || errno == EOVERFLOW) {
286         starttime->tv_sec = pstat.pst_start;
287         starttime->tv_nsec = 0;
288
289         sudo_debug_printf(SUDO_DEBUG_INFO,
290             "%s: start time for %d: { %lld, %ld }", __func__,
291             (int)pid, (long long)starttime->tv_sec, (long)starttime->tv_nsec);
292
293         debug_return_int(0);
294     }
295
296     sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
297         "unable to get start time for %d via pstat_getproc", (int)pid);
298     debug_return_int(-1);
299 }
300 #else
301 int
302 get_starttime(pid_t pid, struct timespec *starttime)
303 {
304     debug_decl(get_starttime, SUDOERS_DEBUG_UTIL)
305
306     sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
307         "process start time not supported by sudo on this system");
308     debug_return_int(-1);
309 }
310 #endif