1 /*
2  * SPDX-License-Identifier: ISC
3  *
4  * Copyright (c) 2012-2018 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 #include <sys/types.h>
27 #include <sys/stat.h>
28 #if defined(MAJOR_IN_MKDEV)
29 # include <sys/mkdev.h>
30 #elif defined(MAJOR_IN_SYSMACROS)
31 # include <sys/sysmacros.h>
32 #else
33 # include <sys/param.h>
34 #endif
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <unistd.h>
39 #include <errno.h>
40 #include <fcntl.h>
41 #include <limits.h>
42 #include <dirent.h>
43 
44 #include "pathnames.h"
45 #include "sudo_compat.h"
46 #include "sudo_debug.h"
47 #include "sudo_conf.h"
48 #include "sudo_util.h"
49 
50 #if defined(HAVE_DEVNAME)
51 /*
52  * Like ttyname() but uses a dev_t instead of an open fd.
53  * Returns name on success and NULL on failure, setting errno.
54  * The BSD version uses devname().
55  */
56 char *
sudo_ttyname_dev_v1(dev_t tdev,char * name,size_t namelen)57 sudo_ttyname_dev_v1(dev_t tdev, char *name, size_t namelen)
58 {
59     char *dev;
60     debug_decl(sudo_ttyname_dev, SUDO_DEBUG_UTIL);
61 
62     /* Some versions of devname() return NULL on failure, others do not. */
63     dev = devname(tdev, S_IFCHR);
64     if (dev != NULL && *dev != '?' && *dev != '#') {
65 	if (strlcpy(name, _PATH_DEV, namelen) < namelen &&
66 	    strlcat(name, dev, namelen) < namelen)
67 	    debug_return_str(name);
68 	errno = ERANGE;
69     } else {
70 	/* Not all versions of devname() set errno. */
71 	errno = ENOENT;
72     }
73     debug_return_str(NULL);
74 }
75 #elif defined(HAVE__TTYNAME_DEV)
76 extern char *_ttyname_dev(dev_t rdev, char *buffer, size_t buflen);
77 
78 /*
79  * Like ttyname() but uses a dev_t instead of an open fd.
80  * Returns name on success and NULL on failure, setting errno.
81  * This version is just a wrapper around _ttyname_dev().
82  */
83 char *
sudo_ttyname_dev_v1(dev_t tdev,char * name,size_t namelen)84 sudo_ttyname_dev_v1(dev_t tdev, char *name, size_t namelen)
85 {
86     int serrno = errno;
87     debug_decl(sudo_ttyname_dev, SUDO_DEBUG_UTIL);
88 
89     /*
90      * _ttyname_dev() sets errno to ERANGE if namelen is too small
91      * but does not modify it if tdev is not found.
92      */
93     errno = ENOENT;
94     if (_ttyname_dev(tdev, name, namelen) == NULL)
95 	debug_return_str(NULL);
96     errno = serrno;
97 
98     debug_return_str(name);
99 }
100 #else
101 /*
102  * Device nodes to ignore.
103  */
104 static const char *ignore_devs[] = {
105     _PATH_DEV "stdin",
106     _PATH_DEV "stdout",
107     _PATH_DEV "stderr",
108     NULL
109 };
110 
111 /*
112  * Do a scan of a directory looking for the specified device.
113  * Does not descend into subdirectories.
114  * Returns name on success and NULL on failure, setting errno.
115  */
116 static char *
sudo_ttyname_scan(const char * dir,dev_t rdev,char * name,size_t namelen)117 sudo_ttyname_scan(const char *dir, dev_t rdev, char *name, size_t namelen)
118 {
119     size_t sdlen;
120     char pathbuf[PATH_MAX];
121     char *ret = NULL;
122     struct dirent *dp;
123     struct stat sb;
124     unsigned int i;
125     DIR *d = NULL;
126     debug_decl(sudo_ttyname_scan, SUDO_DEBUG_UTIL);
127 
128     if (dir[0] == '\0') {
129 	errno = ENOENT;
130 	goto done;
131     }
132     if ((d = opendir(dir)) == NULL)
133 	goto done;
134 
135     if (fstat(dirfd(d), &sb) == -1) {
136 	sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
137 	    "unable to fstat %s", dir);
138 	goto done;
139     }
140     if ((sb.st_mode & S_IWOTH) != 0) {
141 	sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
142 	    "ignoring world-writable directory %s", dir);
143 	errno = ENOENT;
144 	goto done;
145     }
146 
147     sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
148 	"scanning for dev %u in %s", (unsigned int)rdev, dir);
149 
150     sdlen = strlen(dir);
151     while (sdlen > 0 && dir[sdlen - 1] == '/')
152 	sdlen--;
153     if (sdlen + 1 >= sizeof(pathbuf)) {
154 	errno = ERANGE;
155 	goto done;
156     }
157     memcpy(pathbuf, dir, sdlen);
158     pathbuf[sdlen++] = '/';
159 
160     while ((dp = readdir(d)) != NULL) {
161 	/* Skip anything starting with "." */
162 	if (dp->d_name[0] == '.')
163 	    continue;
164 
165 	pathbuf[sdlen] = '\0';
166 	if (strlcat(pathbuf, dp->d_name, sizeof(pathbuf)) >= sizeof(pathbuf)) {
167 	    sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
168 		"%s%s is too big to fit in pathbuf", pathbuf, dp->d_name);
169 	    continue;
170 	}
171 
172 	/* Ignore device nodes listed in ignore_devs[]. */
173 	for (i = 0; ignore_devs[i] != NULL; i++) {
174 	    if (strcmp(pathbuf, ignore_devs[i]) == 0)
175 		break;
176 	}
177 	if (ignore_devs[i] != NULL) {
178 	    sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
179 		"ignoring %s", pathbuf);
180 	    continue;
181 	}
182 
183 # if defined(HAVE_STRUCT_DIRENT_D_TYPE)
184 	/*
185 	 * Avoid excessive stat() calls by checking dp->d_type.
186 	 */
187 	switch (dp->d_type) {
188 	    case DT_CHR:
189 	    case DT_LNK:
190 	    case DT_UNKNOWN:
191 		break;
192 	    default:
193 		/* Not a character device or link, skip it. */
194 		sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
195 		    "skipping non-device %s", pathbuf);
196 		continue;
197 	}
198 # endif
199 	if (stat(pathbuf, &sb) == -1) {
200 	    sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
201 		"unable to stat %s", pathbuf);
202 	    continue;
203 	}
204 	if (S_ISCHR(sb.st_mode) && sb.st_rdev == rdev) {
205 	    sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
206 		"resolved dev %u as %s", (unsigned int)rdev, pathbuf);
207 	    if (strlcpy(name, pathbuf, namelen) < namelen) {
208 		ret = name;
209 	    } else {
210 		sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
211 		    "unable to store %s, have %zu, need %zu",
212 		    pathbuf, namelen, strlen(pathbuf) + 1);
213 		errno = ERANGE;
214 	    }
215 	    goto done;
216 	}
217     }
218 
219 done:
220     if (d != NULL)
221 	closedir(d);
222     debug_return_str(ret);
223 }
224 
225 static char *
sudo_dev_check(dev_t rdev,const char * devname,char * buf,size_t buflen)226 sudo_dev_check(dev_t rdev, const char *devname, char *buf, size_t buflen)
227 {
228     struct stat sb;
229     debug_decl(sudo_dev_check, SUDO_DEBUG_UTIL);
230 
231     if (stat(devname, &sb) == 0) {
232 	if (S_ISCHR(sb.st_mode) && sb.st_rdev == rdev) {
233 	    sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
234 		"comparing dev %u to %s: match!",
235 		(unsigned int)rdev, devname);
236 	    if (strlcpy(buf, devname, buflen) < buflen)
237 		debug_return_str(buf);
238 	    sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
239 		"unable to store %s, have %zu, need %zu",
240 		devname, buflen, strlen(devname) + 1);
241 	    errno = ERANGE;
242 	}
243     }
244     sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
245 	"comparing dev %u to %s: no", (unsigned int)rdev, devname);
246     debug_return_str(NULL);
247 }
248 
249 /*
250  * Like ttyname() but uses a dev_t instead of an open fd.
251  * Returns name on success and NULL on failure, setting errno.
252  * Generic version.
253  */
254 char *
sudo_ttyname_dev_v1(dev_t rdev,char * buf,size_t buflen)255 sudo_ttyname_dev_v1(dev_t rdev, char *buf, size_t buflen)
256 {
257     const char *devsearch, *devsearch_end;
258     char path[PATH_MAX], *ret;
259     const char *cp, *ep;
260     size_t len;
261     debug_decl(sudo_ttyname_dev, SUDO_DEBUG_UTIL);
262 
263     /*
264      * First, check /dev/console.
265      */
266     ret = sudo_dev_check(rdev, _PATH_DEV "console", buf, buflen);
267     if (ret != NULL)
268 	goto done;
269 
270     /*
271      * Then check the device search path.
272      */
273     devsearch = sudo_conf_devsearch_path();
274     devsearch_end = devsearch + strlen(devsearch);
275     for (cp = sudo_strsplit(devsearch, devsearch_end, ":", &ep);
276 	cp != NULL; cp = sudo_strsplit(NULL, devsearch_end, ":", &ep)) {
277 
278 	len = (size_t)(ep - cp);
279 	if (len >= sizeof(path)) {
280 	    sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
281 		"devsearch entry %.*s too long", (int)len, cp);
282 	    continue;
283 	}
284 	memcpy(path, cp, len);
285 	path[len] = '\0';
286 
287 	if (strcmp(path, _PATH_DEV "pts") == 0) {
288 	    /* Special case /dev/pts */
289 	    len = (size_t)snprintf(path, sizeof(path), "%spts/%u",
290 		_PATH_DEV, (unsigned int)minor(rdev));
291 	    if (len >= sizeof(path)) {
292 		sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
293 		    "devsearch entry %spts/%u too long",
294 		    _PATH_DEV, (unsigned int)minor(rdev));
295 		continue;
296 	    }
297 	    ret = sudo_dev_check(rdev, path, buf, buflen);
298 	    if (ret != NULL)
299 		goto done;
300 	} else {
301 	    /* Scan path, looking for rdev. */
302 	    ret = sudo_ttyname_scan(path, rdev, buf, buflen);
303 	    if (ret != NULL || errno == ENOMEM)
304 		goto done;
305 	}
306     }
307 
308 done:
309     debug_return_str(ret);
310 }
311 #endif
312