nldev.c (6568B)
1 /* See LICENSE for copyright information. */ 2 3 #include <unistd.h> 4 #include <stdio.h> 5 #include <stdlib.h> 6 #include <stdarg.h> 7 #include <poll.h> 8 #include <ctype.h> 9 #include <string.h> 10 #include <errno.h> 11 #include <signal.h> 12 #include <libgen.h> 13 #include <fcntl.h> 14 #include <sys/socket.h> 15 #include <sys/types.h> 16 #include <sys/wait.h> 17 #include <sys/stat.h> 18 #include <linux/types.h> 19 #include <linux/netlink.h> 20 21 typedef struct { 22 char *action; /* ACTION to run rule for */ 23 char *subsystem; /* SUBSYSTEM to run the rule for, NULL for any */ 24 char *envvar; /* other environment variable to run rule for, NULL for any */ 25 char *runpath; 26 } Rule; 27 28 #include "arg.h" 29 #include "config.h" 30 31 #define LENGTH(X) (sizeof X / sizeof X[0]) 32 33 char *argv0; 34 int listfd = -1; 35 int dofork = 0, dodebug = 0; 36 37 void 38 edie(char *fmt, ...) 39 { 40 va_list fmtargs; 41 42 va_start(fmtargs, fmt); 43 vfprintf(stderr, fmt, fmtargs); 44 va_end(fmtargs); 45 fprintf(stderr, ": "); 46 47 perror(NULL); 48 49 exit(1); 50 } 51 52 void 53 die(char *fmt, ...) 54 { 55 va_list fmtargs; 56 57 va_start(fmtargs, fmt); 58 vfprintf(stderr, fmt, fmtargs); 59 va_end(fmtargs); 60 61 exit(1); 62 } 63 64 void 65 dbg(char *fmt, ...) 66 { 67 va_list fmtargs; 68 69 if (dodebug) { 70 fprintf(stderr, "%s: ", argv0); 71 va_start(fmtargs, fmt); 72 vfprintf(stderr, fmt, fmtargs); 73 va_end(fmtargs); 74 fprintf(stderr, "\n"); 75 } 76 } 77 78 void 79 disableoom(void) 80 { 81 int fd; 82 83 fd = open("/proc/self/oom_score_adj", O_RDWR); 84 if (fd < 0) { 85 fd = open("/proc/self/oom_adj", O_RDWR); 86 if (fd < 0) 87 edie("disabling oom failed."); 88 write(fd, "-17", 3); 89 close(fd); 90 } else { 91 write(fd, "-1000", 5); 92 close(fd); 93 } 94 } 95 96 void 97 child(char *runpath) 98 { 99 int fd, pid; 100 101 if (!(pid = fork())) { 102 if (dofork && !dodebug) { 103 fd = open("/dev/null", O_RDWR); 104 if (fd >= 0) { 105 dup2(fd, 1); 106 dup2(fd, 2); 107 if (fd > 2) 108 close(fd); 109 } 110 } 111 112 dbg("running %s", runpath); 113 if (execlp(runpath, basename(runpath), NULL) < 0) 114 edie("execvp"); 115 exit(0); 116 } 117 if (pid < 0) 118 edie("fork"); 119 } 120 121 void 122 sighandler(int sig) 123 { 124 switch(sig) { 125 case SIGHUP: 126 case SIGINT: 127 case SIGQUIT: 128 case SIGABRT: 129 case SIGTERM: 130 if (listfd >= 0) { 131 shutdown(listfd, SHUT_RDWR); 132 close(listfd); 133 } 134 exit(0); 135 break; 136 default: 137 break; 138 } 139 } 140 141 void 142 initsignals(void) 143 { 144 signal(SIGHUP, sighandler); 145 signal(SIGINT, sighandler); 146 signal(SIGQUIT, sighandler); 147 signal(SIGABRT, sighandler); 148 signal(SIGTERM, sighandler); 149 150 signal(SIGCHLD, SIG_IGN); 151 signal(SIGPIPE, SIG_IGN); 152 } 153 154 int 155 init_netlink_socket(void) 156 { 157 struct sockaddr_nl nls; 158 int fd, slen; 159 160 memset(&nls, 0, sizeof(nls)); 161 nls.nl_family = AF_NETLINK; 162 nls.nl_pid = getpid(); 163 nls.nl_groups = -1; 164 165 fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); 166 if (fd < 0) 167 edie("socket"); 168 169 slen = 16*1024; 170 if (setsockopt(fd, SOL_SOCKET, SO_RCVBUFFORCE, &slen, 171 sizeof(slen)) < 0) { 172 edie("setsockopt"); 173 } 174 slen = 1; 175 if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &slen, 176 sizeof(slen)) < 0) { 177 edie("setsockopt"); 178 } 179 180 if (bind(fd, (void *)&nls, sizeof(nls))) 181 edie("bind"); 182 183 fcntl(fd, F_SETFD, FD_CLOEXEC); 184 return fd; 185 } 186 187 void 188 usage(void) 189 { 190 die("usage: %s [-hdb] [-ku] [-f subsystem] [-r run]\n", argv0); 191 } 192 193 int 194 main(int argc, char *argv[]) 195 { 196 struct sockaddr_nl cnls; 197 struct pollfd fds; 198 struct msghdr hdr; 199 struct iovec iov; 200 char buf[4097], *subsystem, *runpath, *key, *value, 201 cbuf[CMSG_SPACE(sizeof(struct ucred))]; 202 struct cmsghdr *chdr; 203 struct ucred *cred; 204 int i, len, slen, showudev, showkernel; 205 206 showkernel = 1; 207 showudev = 1; 208 subsystem = NULL; 209 runpath = NULL; 210 211 ARGBEGIN { 212 case 'b': 213 dofork = 1; 214 break; 215 case 'd': 216 dodebug = 1; 217 break; 218 case 'f': 219 subsystem = EARGF(usage()); 220 if (!runpath) 221 runpath = "/bin/mdev"; 222 break; 223 case 'k': 224 showudev = 0; 225 break; 226 case 'u': 227 showkernel = 0; 228 break; 229 case 'r': 230 runpath = EARGF(usage()); 231 break; 232 default: 233 usage(); 234 } ARGEND; 235 236 fds.events = POLLIN; 237 fds.fd = init_netlink_socket(); 238 listfd = fds.fd; 239 240 if (dofork) { 241 if (daemon(0, 0) < 0) 242 edie("daemon"); 243 umask(022); 244 } 245 246 initsignals(); 247 disableoom(); 248 249 buf[sizeof(buf)-1] = '\0'; 250 while (poll(&fds, 1, -1) > -1) { 251 clearenv(); 252 setenv("PATH", "/sbin:/bin", 1); 253 254 iov.iov_base = &buf; 255 iov.iov_len = sizeof(buf); 256 memset(&hdr, 0, sizeof(hdr)); 257 hdr.msg_iov = &iov; 258 hdr.msg_iovlen = 1; 259 hdr.msg_control = cbuf; 260 hdr.msg_controllen = sizeof(cbuf); 261 hdr.msg_name = &cnls; 262 hdr.msg_namelen = sizeof(cnls); 263 264 len = recvmsg(fds.fd, &hdr, 0); 265 if (len < 0) { 266 if (errno == EINTR) 267 continue; 268 edie("recvmsg"); 269 } 270 if (len < 32 || len >= sizeof(buf)) 271 continue; 272 273 chdr = CMSG_FIRSTHDR(&hdr); 274 if (chdr == NULL || chdr->cmsg_type != SCM_CREDENTIALS) 275 continue; 276 277 /* 278 * Don't allow anyone but root to send us messages. 279 * 280 * We will allow users to send us messages, when 281 * udev is enabled. Udev is just a toy you should 282 * only use for testing. 283 */ 284 cred = (struct ucred *)CMSG_DATA(chdr); 285 if (cred->uid != 0 && !showudev) 286 continue; 287 288 if (!memcmp(buf, "libudev", 8)) { 289 /* 290 * Receiving messages from udev is insecure. 291 */ 292 if (!showudev) 293 continue; 294 } else { 295 if (!showkernel) 296 continue; 297 /* 298 * Kernel messages shouldn't come from the 299 * userspace. 300 */ 301 if (cnls.nl_pid > 0) 302 continue; 303 } 304 305 slen = 0; 306 for (i = 0; i < len; i += slen + 1) { 307 key = buf + i; 308 value = strchr(key, '='); 309 slen = strlen(buf+i); 310 311 if (!slen || value == NULL) 312 continue; 313 if (subsystem && !strncmp(key, "SUBSYSTEM=", 10) 314 && !strstr(key+10, subsystem)) { 315 dbg("subsystem filter '%s' applied.", 316 subsystem); 317 break; 318 } 319 320 value[0] = '\0'; 321 value++; 322 323 /* 324 * We generally trust the kernel. But there 325 * might be some udev flaw. (It's >20k sloc!) 326 */ 327 if (strcmp(key, "PATH")) { 328 setenv(key, value, 1); 329 dbg("%s = \"%s\"", key, value); 330 } 331 } 332 if (getenv("ACTION") != NULL && 333 getenv("DEVPATH") != NULL && 334 getenv("SUBSYSTEM") != NULL && 335 getenv("SEQNUM") != NULL) { 336 if (runpath) { 337 child(runpath); 338 } else { 339 for (i = 0; i < LENGTH(rules); i+=1) { 340 if (rules[i].action == NULL 341 || rules[i].runpath == NULL) { 342 continue; 343 } 344 if (strcmp(getenv("ACTION"), rules[i].action)) { 345 continue; 346 } 347 if (rules[i].subsystem != NULL) { 348 if (strcmp(getenv("SUBSYSTEM"), 349 rules[i].subsystem)) { 350 continue; 351 } 352 } 353 if (rules[i].envvar != NULL) { 354 if (getenv(rules[i].envvar) == NULL) { 355 continue; 356 } 357 } 358 child(rules[i].runpath); 359 } 360 } 361 } 362 } 363 364 shutdown(listfd, SHUT_RDWR); 365 close(listfd); 366 367 return 0; 368 } 369