/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1992, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "allocate.h" extern void print_error(int, char *); #if defined(DEBUG) || defined(lint) #define dprintf(s, a) (void) fprintf(stderr, s, a) #define dperror(s) perror(s) #else /* !DEBUG */ #define dprintf(s, a) 0 #define dperror(s) 0 #endif /* DEBUG */ #define DEV_ERRORED(sbuf) (((sbuf).st_mode & ~S_IFMT) == ALLOC_ERR_MODE) #define DEV_ALLOCATED(sbuf) ((sbuf).st_uid != DA_UID || \ !(((sbuf).st_mode & ~S_IFMT) == DEALLOC_MODE || \ DEV_ERRORED(sbuf))) #define ALLOC_CLEAN "-A" #define DEALLOC_CLEAN "-D" #define DAC_DIR "/etc/security/dev" #define DEVICE_AUTH_SEPARATOR "," #define LOCALDEVICE "/dev/console" #define PROCFS "/proc/" #define SFF_NO_ERROR 0x1 #define ALLOC_BY_NONE -1 #define CHECK_DRANGE 1 #define CHECK_URANGE 2 #define CHECK_ZLABEL 3 extern void audit_allocate_list(char *); extern void audit_allocate_device(char *); extern int system_labeled; extern char *newenv[]; struct state_file { int sf_flags; char sf_path[MAXPATHLEN]; }; struct file_info { struct stat fi_stat; char *fi_message; }; struct zone_path { int count; char **path; }; struct dev_names { char **dnames; }; static int _dev_file_name(struct state_file *, devmap_t *); static int lock_dev(char *, struct stat *); static int _check_label(devalloc_t *, char *, uid_t, int); static int create_znode(char *, struct zone_path *, devmap_t *); static int remove_znode(char *, devmap_t *); static int update_device(char **, char *, int); /* * checks if the invoking user is local to the device */ /*ARGSUSED*/ int _is_local(uid_t uid) { struct stat statbuf; if (stat(LOCALDEVICE, &statbuf) == 0 && statbuf.st_uid == uid) return (1); return (0); } /* * Checks if the user with the specified uid has the specified authorization */ int _is_authorized(char *auths, uid_t uid) { char *dcp, *authlist, *lasts; char pw_buf[NSS_BUFLEN_PASSWD]; struct passwd pw_ent; /* * first, the easy cases */ if (strcmp(auths, "@") == 0) return (1); if (strcmp(auths, "*") == 0) return (ALLOC_BY_NONE); if (getpwuid_r(uid, &pw_ent, pw_buf, sizeof (pw_buf)) == NULL) return (0); if (strpbrk(auths, DEVICE_AUTH_SEPARATOR) == NULL) return (chkauthattr(auths, pw_ent.pw_name)); authlist = strdup(auths); if (authlist == NULL) return (0); for (dcp = authlist; (dcp = strtok_r(dcp, DEVICE_AUTH_SEPARATOR, &lasts)) != NULL; dcp = NULL) { if (chkauthattr(dcp, pw_ent.pw_name)) break; } free(authlist); return (dcp != NULL); } /* * Checks if the specified user has authorization for the device */ int _is_dev_authorized(devalloc_t *da, uid_t uid) { int ares; char *auth_list, *dcp, *subauth = NULL; auth_list = da->da_devauth; if (auth_list == NULL) return (0); dcp = strpbrk(auth_list, KV_TOKEN_DELIMIT); if (dcp == NULL) return (_is_authorized(auth_list, uid)); if (_is_local(uid)) { /* the local authorization is before the separator */ ares = dcp - auth_list; subauth = malloc(ares + 1); if (subauth == NULL) return (0); (void) strlcpy(subauth, auth_list, (ares + 1)); auth_list = subauth; } else auth_list = dcp + 1; ares = _is_authorized(auth_list, uid); if (subauth != NULL) free(subauth); return (ares); } int check_devs(devmap_t *dm) { int status = 0; char **file; if (dm->dmap_devarray == NULL) return (NODMAPERR); for (file = dm->dmap_devarray; *file != NULL; file++) { if ((status = access(*file, F_OK)) == -1) { dprintf("Unable to access file %s\n", *file); break; } } return (status); } int print_da_defs(da_defs_t *da_defs) { char optbuf[BUFSIZ]; char *p = NULL; if (da_defs->devopts == NULL) { dprintf("No default attributes for %s\n", da_defs->devtype); return (DEFATTRSERR); } (void) printf("dev_type=%s\n", da_defs->devtype); if (_kva2str(da_defs->devopts, optbuf, sizeof (optbuf), KV_ASSIGN, KV_TOKEN_DELIMIT) == 0) { if (p = rindex(optbuf, ':')) *p = '\0'; (void) printf("\t%s\n", optbuf); } return (0); } void print_dev_attrs(int optflag, devalloc_t *da, devmap_t *dm, struct file_info *fip) { char *p = NULL; char optbuf[BUFSIZ]; (void) printf("device=%s%s", dm->dmap_devname, KV_DELIMITER); (void) printf("type=%s%s", dm->dmap_devtype, KV_DELIMITER); (void) printf("auths=%s%s", (da->da_devauth ? da->da_devauth : ""), KV_DELIMITER); (void) printf("clean=%s%s", (da->da_devexec ? da->da_devexec : ""), KV_DELIMITER); if (da->da_devopts != NULL) { if (_kva2str(da->da_devopts, optbuf, sizeof (optbuf), KV_ASSIGN, KV_TOKEN_DELIMIT) == 0) { if (p = rindex(optbuf, ':')) *p = '\0'; (void) printf("%s", optbuf); } } (void) printf("%s", KV_DELIMITER); if (optflag & WINDOWING) { if ((fip->fi_message != NULL) && (strcmp(fip->fi_message, DAOPT_CLASS) == 0)) (void) printf("owner=/FREE%s", KV_DELIMITER); else if (DEV_ERRORED(fip->fi_stat)) (void) printf("owner=/ERROR%s", KV_DELIMITER); else if (!DEV_ALLOCATED(fip->fi_stat)) (void) printf("owner=/FREE%s", KV_DELIMITER); else (void) printf("owner=%u%s", fip->fi_stat.st_uid, KV_DELIMITER); } (void) printf("files=%s", dm->dmap_devlist); (void) printf("\n"); } void print_dev(devmap_t *dm) { char **file; (void) printf(gettext("device: %s "), dm->dmap_devname); (void) printf(gettext("type: %s "), dm->dmap_devtype); (void) printf(gettext("files:")); file = dm->dmap_devarray; if (file != NULL) { for (; *file != NULL; file++) (void) printf(" %s", *file); } (void) printf("\n"); } /* ARGSUSED */ int _list_device(int optflag, uid_t uid, devalloc_t *da, char *zonename) { int bytes = 0; int error = 0; int is_authorized = 0; char *fname = NULL; char file_name[MAXPATHLEN]; devmap_t *dm; struct file_info fi; struct state_file sf; fi.fi_message = NULL; setdmapent(); if ((dm = getdmapnam(da->da_devname)) == NULL) { enddmapent(); dprintf("Unable to find %s in the maps database\n", da->da_devname); return (NODMAPERR); } enddmapent(); if ((optflag & CLASS) && (!(optflag & (LISTALL | LISTFREE | LISTALLOC)))) { fi.fi_message = DAOPT_CLASS; if (optflag & LISTATTRS) print_dev_attrs(optflag, da, dm, &fi); else print_dev(dm); goto out; } if (system_labeled) { if ((error = _dev_file_name(&sf, dm)) != 0) { freedmapent(dm); dprintf("Unable to find %s device files\n", da->da_devname); error = NODMAPERR; goto out; } fname = sf.sf_path; } else { bytes = snprintf(file_name, MAXPATHLEN, "%s/%s", DAC_DIR, da->da_devname); if (bytes <= 0) { error = DEVNAMEERR; goto out; } else if (bytes >= MAXPATHLEN) { dprintf("device name %s is too long.\n", da->da_devname); error = DEVLONGERR; goto out; } fname = file_name; } if (stat(fname, &fi.fi_stat) != 0) { dprintf("Unable to stat %s\n", fname); dperror("Error:"); error = DACACCERR; goto out; } is_authorized = _is_dev_authorized(da, uid); if (optflag & LISTFREE) { /* list_devices -n */ /* * list all free devices */ if (DEV_ALLOCATED(fi.fi_stat)) { error = PREALLOCERR; goto out; } if (system_labeled) { /* * for this free device, check if - * 1. user has authorization to allocate * 2. the zone label is within the label range of the * device */ if (is_authorized == ALLOC_BY_NONE) { error = DAUTHERR; goto out; } else if (is_authorized == 0) { error = UAUTHERR; goto out; } if (_check_label(da, zonename, uid, CHECK_DRANGE) != 0) { error = LABELRNGERR; goto out; } } } else if (optflag & LISTALLOC) { /* list_devices -u */ /* * list all allocated devices */ if (!DEV_ALLOCATED(fi.fi_stat)) { error = DEVNALLOCERR; goto out; } if (fi.fi_stat.st_uid != uid) { error = DEVSTATEERR; goto out; } if (system_labeled) { /* * check if the zone label equals the label at which * the device is allocated. */ if (_check_label(da, zonename, uid, CHECK_ZLABEL) != 0) { error = LABELRNGERR; goto out; } } } else if (optflag & LISTALL) { /* list_devices -l */ /* * list all devices - free and allocated - available */ if (DEV_ALLOCATED(fi.fi_stat)) { if (optflag & WINDOWING && (is_authorized == ALLOC_BY_NONE)) { /* * don't complain if we're here for the GUI. */ error = 0; } else if (fi.fi_stat.st_uid != uid) { if (!(optflag & WINDOWING)) { error = ALLOCUERR; goto out; } } if (system_labeled && !(optflag & WINDOWING)) { /* * if we're not displaying in the GUI, * check if the zone label equals the label * at which the device is allocated. */ if (_check_label(da, zonename, uid, CHECK_ZLABEL) != 0) { error = LABELRNGERR; goto out; } } } else if (system_labeled && !(optflag & WINDOWING)) { /* * if we're not displaying in the GUI, * for this free device, check if - * 1. user has authorization to allocate * 2. the zone label is within the label range of the * device */ if (is_authorized == ALLOC_BY_NONE) { error = DAUTHERR; goto out; } else if (is_authorized == 0) { error = UAUTHERR; goto out; } if (_check_label(da, zonename, uid, CHECK_DRANGE) != 0) { error = LABELRNGERR; goto out; } } } if (system_labeled && DEV_ERRORED(fi.fi_stat) && !(optflag & LISTALL)) { error = DEVSTATEERR; goto out; } if (check_devs(dm) == -1) { error = DSPMISSERR; goto out; } if (optflag & LISTATTRS) print_dev_attrs(optflag, da, dm, &fi); else print_dev(dm); error = 0; out: freedmapent(dm); return (error); } /* ARGSUSED */ int list_devices(int optflag, uid_t uid, char *device, char *zonename) { int error = 0; char *class = NULL; da_defs_t *da_defs; devalloc_t *da; if (system_labeled && optflag & WINDOWING && !(optflag & LISTATTRS)) { /* * Private interface for GUI. */ (void) puts(DA_DB_LOCK); return (0); } if (optflag & USERID) { /* * we need device.revoke to list someone else's devices */ if (!_is_authorized(DEVICE_REVOKE_AUTH, getuid())) return (UAUTHERR); } if (system_labeled) { if (!(optflag & USERID) && !_is_authorized(DEFAULT_DEV_ALLOC_AUTH, uid)) /* * we need device.allocate to list our devices */ return (UAUTHERR); if (optflag & LISTDEFS) { /* * list default attrs from devalloc_defaults */ setdadefent(); if (device) { /* * list default attrs for this device type */ da_defs = getdadeftype(device); if (da_defs == NULL) { enddadefent(); dprintf("No default attributes for " "%s\n", device); return (DEFATTRSERR); } error = print_da_defs(da_defs); freedadefent(da_defs); } else { /* * list everything in devalloc_defaults */ while ((da_defs = getdadefent()) != NULL) { (void) print_da_defs(da_defs); freedadefent(da_defs); } } enddadefent(); return (error); } } /* * Lock the database to make sure no body writes to it while we are * reading. */ (void) lock_dev(NULL, NULL); setdaent(); if (device) { if (optflag & CLASS) { /* * list all devices of this class. */ while ((da = getdaent()) != NULL) { class = kva_match(da->da_devopts, DAOPT_CLASS); if (class && (strcmp(class, device) == 0)) { (void) _list_device(optflag, uid, da, zonename); } freedaent(da); } } else { /* * list this device */ if ((da = getdanam(device)) == NULL) { enddaent(); return (NODAERR); } error = _list_device(optflag, uid, da, zonename); freedaent(da); } } else { /* * list all devices */ while ((da = getdaent()) != NULL) { (void) _list_device(optflag, uid, da, zonename); freedaent(da); } } enddaent(); return (error); } /* * Set the DAC characteristics of the file. * This uses a fancy chmod() by setting a minimal ACL which sets the mode * and discards any existing ACL. */ int _newdac(char *file, uid_t owner, gid_t group, o_mode_t mode) { int err = 0; if (mode == ALLOC_MODE) { if (chown(file, owner, group) == -1) { dperror("newdac: unable to chown"); err = CHOWNERR; } } else do { if (chown(file, owner, group) == -1) { dperror("newdac: unable to chown"); err = CHOWNERR; } } while (fdetach(file) == 0); if (err) return (err); if (strncmp(file, "/dev/", strlen("/dev/")) != 0) { /* * This could be a SunRay device that is in /tmp. */ if (chmod(file, mode) == -1) { dperror("newdac: unable to chmod"); err = SETACLERR; } } else { err = acl_strip(file, owner, group, (mode_t)mode); } if (err != 0) { dperror("newdac: unable to setacl"); err = SETACLERR; } return (err); } /* * lock_dev - * locks a section of DA_DB_LOCK. * returns lock fd if successful, else -1 on error. */ static int lock_dev(char *file, struct stat *statbuf) { static int lockfd = -1; int ret; int count = 0; int retry = 10; off_t size = 0; off_t offset; char *lockfile; if (system_labeled) lockfile = DA_DB_LOCK; else lockfile = file; if (statbuf) { offset = statbuf->st_rdev; dprintf("locking %s\n", file); } else { offset = 0; dprintf("locking %s\n", lockfile); } if ((lockfd == -1) && (lockfd = open(lockfile, O_RDWR | O_CREAT, 0600)) == -1) { dperror("lock_dev: cannot open lock file"); return (-1); } if (system_labeled) { (void) _newdac(lockfile, DA_UID, DA_GID, 0600); if (lseek(lockfd, offset, SEEK_SET) == -1) { dperror("lock_dev: cannot position lock file"); return (-1); } size = 1; } errno = 0; while (retry) { count++; ret = lockf(lockfd, F_TLOCK, size); if (ret == 0) return (lockfd); if ((errno != EACCES) && (errno != EAGAIN)) { dperror("lock_dev: cannot set lock"); return (-1); } retry--; (void) sleep(count); errno = 0; } return (-1); } int mk_alloc(devmap_t *list, uid_t uid, struct zone_path *zpath) { int i; int error = 0; char **file; gid_t gid = getgid(); mode_t mode = ALLOC_MODE; file = list->dmap_devarray; if (file == NULL) return (NODMAPERR); for (; *file != NULL; file++) { dprintf("Allocating %s\n", *file); if ((error = _newdac(*file, uid, gid, mode)) != 0) { (void) _newdac(*file, ALLOC_ERRID, DA_GID, ALLOC_ERR_MODE); break; } } if (system_labeled && zpath->count && (error == 0)) { /* * mark as allocated any new device nodes that we * created in local zone */ for (i = 0; i < zpath->count; i++) { dprintf("Allocating %s\n", zpath->path[i]); if ((error = _newdac(zpath->path[i], uid, gid, mode)) != 0) { (void) _newdac(zpath->path[i], ALLOC_ERRID, DA_GID, ALLOC_ERR_MODE); break; } } } return (error); } /* * mk_revoke() is used instead of system("/usr/sbin/fuser -k file") * because "/usr/sbin/fuser -k file" kills all processes * working with the file, even "vold" (bug #4095152). */ int mk_revoke(int optflag, char *file) { int r = 0, p[2], fp, lock; int fuserpid; char buf[MAXPATHLEN]; FILE *ptr; pid_t c_pid; prpsinfo_t info; (void) strcpy(buf, PROCFS); /* * vfork() and execl() just to make the same output * as before fixing of bug #4095152. * The problem is that the "fuser" command prints * one part of output into stderr and another into stdout, * but user sees them mixed. Of course, better to change "fuser" * or to intercept and not to print its output. */ if (!(optflag & SILENT)) { c_pid = vfork(); if (c_pid == -1) return (-1); if (c_pid == 0) { dprintf("first exec fuser %s\n", file); (void) execl("/usr/sbin/fuser", "fuser", file, NULL); dperror("first exec fuser"); _exit(1); } (void) waitpid(c_pid, &lock, 0); dprintf("exit status %x\n", lock); if (WEXITSTATUS(lock) != 0) return (-1); } dprintf("first continuing c_pid=%d\n", (int)c_pid); if (pipe(p)) { dperror("pipe"); return (-1); } /* vfork() and execl() to catch output and to process it */ c_pid = vfork(); if (c_pid == -1) { dperror("second vfork"); return (-1); } dprintf("second continuing c_pid=%d\n", (int)c_pid); if (c_pid == 0) { (void) close(p[0]); (void) close(1); (void) fcntl(p[1], F_DUPFD, 1); (void) close(p[1]); (void) close(2); dprintf("second exec fuser %s\n", file); (void) execl("/usr/sbin/fuser", "fuser", file, NULL); dperror("second exec fuser"); _exit(1); } (void) close(p[1]); if ((ptr = fdopen(p[0], "r")) != NULL) { while (!feof(ptr)) { if (fscanf(ptr, "%d", &fuserpid) > 0) { (void) sprintf(buf + strlen(PROCFS), "%d", fuserpid); if ((fp = open(buf, O_RDONLY)) == -1) { dperror(buf); continue; } if (ioctl(fp, PIOCPSINFO, (char *)&info) == -1) { dprintf("%d psinfo failed", fuserpid); dperror(""); (void) close(fp); continue; } (void) close(fp); if (strcmp(info.pr_fname, "vold") == 0) { dprintf("%d matched vold name\n", fuserpid); continue; } if (strcmp(info.pr_fname, "deallocate") == 0) { dprintf("%d matched deallocate name\n", fuserpid); continue; } dprintf("killing %s", info.pr_fname); dprintf("(%d)\n", fuserpid); if ((r = kill((pid_t)fuserpid, SIGKILL)) == -1) { dprintf("kill %d", fuserpid); dperror(""); break; } } } } else { dperror("fdopen(p[0], r)"); r = -1; } (void) fclose(ptr); return (r); } int mk_unalloc(int optflag, devmap_t *list) { int error = 0; int status; char **file; audit_allocate_list(list->dmap_devlist); file = list->dmap_devarray; if (file == NULL) return (NODMAPERR); for (; *file != NULL; file++) { dprintf("Deallocating %s\n", *file); if (mk_revoke(optflag, *file) < 0) { dprintf("mk_unalloc: unable to revoke %s\n", *file); dperror(""); error = CNTFRCERR; } status = _newdac(*file, DA_UID, DA_GID, DEALLOC_MODE); if (error == 0) error = status; } return (error); } int mk_error(devmap_t *list) { int status = 0; char **file; audit_allocate_list(list->dmap_devlist); file = list->dmap_devarray; if (file == NULL) return (NODMAPERR); for (; *file != NULL; file++) { dprintf("Putting %s in error state\n", *file); status = _newdac(*file, ALLOC_ERRID, DA_GID, ALLOC_ERR_MODE); } return (status); } int exec_clean(int optflag, char *devname, char *path, uid_t uid, char *zonename, char *clean_arg) { int c; int status = 0, exit_status; char *mode, *cmd, *wdwcmd, *zoneroot; char *devzone = zonename; char wdwpath[PATH_MAX]; char zonepath[MAXPATHLEN]; char title[100]; char pw_buf[NSS_BUFLEN_PASSWD]; struct passwd pw_ent; zonepath[0] = '\0'; if (system_labeled) { if ((zoneroot = getzonerootbyname(zonename)) == NULL) { if (strcmp(clean_arg, ALLOC_CLEAN) == 0) { return (-1); } else if (optflag & FORCE) { (void) strcpy(zonepath, "/"); devzone = GLOBAL_ZONENAME; } else { dprintf("unable to get label for %s zone\n", zonename); return (-1); } } else { (void) strcpy(zonepath, zoneroot); free(zoneroot); } } if (getpwuid_r(uid, &pw_ent, pw_buf, sizeof (pw_buf)) == NULL) return (-1); if (optflag & FORCE_ALL) mode = "-I"; else if (optflag & FORCE) mode = "-f"; else mode = "-s"; if (path == NULL) return (0); if ((cmd = strrchr(path, '/')) == NULL) cmd = path; else cmd++; /* skip leading '/' */ c = vfork(); switch (c) { case -1: return (-1); case 0: (void) setuid(0); if (system_labeled && (optflag & WINDOWING)) { /* First try .windowing version of script */ (void) strncpy(wdwpath, path, PATH_MAX); (void) strncat(wdwpath, ".windowing", PATH_MAX); if ((wdwcmd = strrchr(wdwpath, '/')) == NULL) wdwcmd = wdwpath; (void) execl(wdwpath, wdwcmd, mode, devname, clean_arg, pw_ent.pw_name, devzone, zonepath, NULL); /* If that failed, run regular version via dtterm */ (void) snprintf(title, sizeof (title), "Device %s for %s", strcmp(clean_arg, ALLOC_CLEAN) == 0 ? "allocation" : "deallocation", devname); (void) execl("/usr/dt/bin/dtterm", "dtterm", "-title", title, "-geometry", "x10+100+400", "-e", "/etc/security/lib/wdwwrapper", path, mode, devname, clean_arg, pw_ent.pw_name, devzone, zonepath, NULL); /* * And if that failed, continue on to try * running regular version directly. */ } dprintf("clean script: %s, ", path); dprintf("cmd=%s, ", cmd); dprintf("mode=%s, ", mode); if (system_labeled) { dprintf("devname=%s ", devname); dprintf("zonename=%s ", devzone); dprintf("zonepath=%s ", zonepath); dprintf("username=%s\n", pw_ent.pw_name); (void) execl(path, cmd, mode, devname, clean_arg, pw_ent.pw_name, devzone, zonepath, NULL); } else { dprintf("devname=%s\n", devname); (void) execle(path, cmd, mode, devname, NULL, newenv); } dprintf("Unable to execute clean up script %s\n", path); dperror(""); exit(CNTDEXECERR); default: (void) waitpid(c, &status, 0); dprintf("Child %d", c); if (WIFEXITED(status)) { exit_status = WEXITSTATUS(status); dprintf(" exited, status: %d\n", exit_status); return (exit_status); } else if (WIFSIGNALED(status)) { dprintf(" killed, signal %d\n", WTERMSIG(status)); } else { dprintf(": exit status %d\n", status); } return (-1); } } int _deallocate_dev(int optflag, devalloc_t *da, devmap_t *dm_in, uid_t uid, char *zonename, int *lock_fd) { int bytes = 0; int error = 0; int is_authorized = 0; uid_t nuid; char *fname = NULL; char file_name[MAXPATHLEN]; char *devzone = NULL; devmap_t *dm = NULL, *dm_new = NULL; struct stat stat_buf; struct state_file sf; if (dm_in == NULL) { setdmapent(); if ((dm_new = getdmapnam(da->da_devname)) == NULL) { enddmapent(); dprintf("Unable to find %s in device map database\n", da->da_devname); return (NODMAPERR); } enddmapent(); dm = dm_new; } else { dm = dm_in; } if (system_labeled) { if (_dev_file_name(&sf, dm) != 0) { if (dm_new) freedmapent(dm_new); dprintf("Unable to find %s device files\n", da->da_devname); error = NODMAPERR; goto out; } fname = sf.sf_path; } else { bytes = snprintf(file_name, MAXPATHLEN, "%s/%s", DAC_DIR, da->da_devname); if (bytes <= 0) { error = DEVNAMEERR; goto out; } else if (bytes >= MAXPATHLEN) { dprintf("device name %s is too long.\n", da->da_devname); error = DEVLONGERR; goto out; } fname = file_name; } audit_allocate_device(fname); if (stat(fname, &stat_buf) != 0) { dprintf("Unable to stat %s\n", fname); error = DACACCERR; goto out; } is_authorized = _is_dev_authorized(da, uid); if (is_authorized == ALLOC_BY_NONE) { dprintf("Not deallocating %s, not allocatable\n", da->da_devname); goto out; } if (!(optflag & (FORCE | FORCE_ALL)) && !is_authorized) { dprintf("User %d is unauthorized to deallocate\n", (int)uid); error = UAUTHERR; goto out; } if (system_labeled) { /* * unless we're here to deallocate by force, check if the * label at which the device is currently allocated is * within the user label range. */ if (!(optflag & FORCE) && _check_label(da, zonename, uid, CHECK_URANGE) != 0) { error = LABELRNGERR; goto out; } } if (!(optflag & FORCE) && stat_buf.st_uid != uid && DEV_ALLOCATED(stat_buf)) { error = ALLOCUERR; goto out; } if (!DEV_ALLOCATED(stat_buf)) { if (DEV_ERRORED(stat_buf)) { if (!(optflag & FORCE)) { error = DEVSTATEERR; goto out; } } else { error = DEVNALLOCERR; goto out; } } /* All checks passed, time to lock and deallocate */ if ((*lock_fd = lock_dev(fname, &stat_buf)) == -1) { error = DEVLKERR; goto out; } if (system_labeled) { devzone = kva_match(da->da_devopts, DAOPT_ZONE); if (devzone == NULL) { devzone = GLOBAL_ZONENAME; } else if (strcmp(devzone, GLOBAL_ZONENAME) != 0) { if ((remove_znode(devzone, dm) != 0) && !(optflag & FORCE)) { error = ZONEERR; goto out; } } } if ((error = mk_unalloc(optflag, dm)) != 0) { if (!(optflag & FORCE)) goto out; } if (system_labeled == 0) { if ((error = _newdac(fname, DA_UID, DA_GID, DEALLOC_MODE)) != 0) { (void) _newdac(file_name, DA_UID, DA_GID, ALLOC_ERR_MODE); goto out; } } /* * if we are deallocating device owned by someone else, * pass the owner's uid to the cleaning script. */ nuid = (stat_buf.st_uid == uid) ? uid : stat_buf.st_uid; error = exec_clean(optflag, da->da_devname, da->da_devexec, nuid, devzone, DEALLOC_CLEAN); if (error != 0) { if (!(optflag & (FORCE | FORCE_ALL))) { error = CLEANERR; (void) mk_error(dm); } else { error = 0; } } out: if (dm_new) freedmapent(dm_new); return (error); } int _allocate_dev(int optflag, uid_t uid, devalloc_t *da, char *zonename, int *lock_fd) { int i; int bytes = 0; int error = 0; int is_authorized = 0; int dealloc_optflag = 0; char *fname = NULL; char file_name[MAXPATHLEN]; devmap_t *dm; struct stat stat_buf; struct state_file sf; struct zone_path zpath; zpath.count = 0; zpath.path = NULL; setdmapent(); if ((dm = getdmapnam(da->da_devname)) == NULL) { enddmapent(); dprintf("Unable to find %s in device map database\n", da->da_devname); return (NODMAPERR); } enddmapent(); if (system_labeled) { if (_dev_file_name(&sf, dm) != 0) { freedmapent(dm); dprintf("Unable to find %s device files\n", da->da_devname); error = NODMAPERR; goto out; } fname = sf.sf_path; } else { bytes = snprintf(file_name, MAXPATHLEN, "%s/%s", DAC_DIR, da->da_devname); if (bytes <= 0) { error = DEVNAMEERR; goto out; } else if (bytes >= MAXPATHLEN) { dprintf("device name %s is too long.\n", da->da_devname); error = DEVLONGERR; goto out; } fname = file_name; } (void) audit_allocate_device(fname); if (stat(fname, &stat_buf) != 0) { dprintf("Unable to stat %s\n", fname); dperror("Error:"); error = DACACCERR; goto out; } if (DEV_ERRORED(stat_buf)) { error = DEVSTATEERR; goto out; } is_authorized = _is_dev_authorized(da, uid); if (is_authorized == ALLOC_BY_NONE) { dprintf("Device %s is not allocatable\n", da->da_devname); error = UAUTHERR; goto out; } else if (!is_authorized && !(optflag & USERNAME)) { dprintf("User %d is unauthorized to allocate\n", (int)uid); error = UAUTHERR; goto out; } if (system_labeled) { /* * check if label of the zone to which the device is being * allocated is within the device label range. */ if (_check_label(da, zonename, uid, CHECK_DRANGE) != 0) { error = LABELRNGERR; goto out; } } if (check_devs(dm) == -1) { error = DSPMISSERR; goto out; } if (DEV_ALLOCATED(stat_buf)) { if (optflag & FORCE) { if (optflag & SILENT) dealloc_optflag = FORCE|SILENT; else dealloc_optflag = FORCE; if (_deallocate_dev(dealloc_optflag, da, dm, uid, zonename, lock_fd)) { dprintf("Couldn't force deallocate device %s\n", da->da_devname); error = CNTFRCERR; goto out; } } else if (stat_buf.st_uid == uid) { error = PREALLOCERR; goto out; } else { error = ALLOCUERR; goto out; } } /* All checks passed, time to lock and allocate */ if ((*lock_fd = lock_dev(fname, &stat_buf)) == -1) { error = DEVLKERR; goto out; } if (system_labeled) { /* * Run the cleaning program; it also mounts allocated * device if required. */ error = exec_clean(optflag, da->da_devname, da->da_devexec, uid, zonename, ALLOC_CLEAN); if (error != DEVCLEAN_OK) { switch (error) { case DEVCLEAN_ERROR: case DEVCLEAN_SYSERR: dprintf("allocate: " "Error in device clean program %s\n", da->da_devexec); error = CLEANERR; (void) mk_error(dm); goto out; case DEVCLEAN_BADMOUNT: dprintf("allocate: Failed to mount device %s\n", da->da_devexec); goto out; case DEVCLEAN_MOUNTOK: break; default: error = 0; goto out; } } /* * If not mounted, create zonelinks, if this is not the * global zone. */ if ((strcmp(zonename, GLOBAL_ZONENAME) != 0) && (error != DEVCLEAN_MOUNTOK)) { if (create_znode(zonename, &zpath, dm) != 0) { error = ZONEERR; goto out; } } } (void) audit_allocate_list(dm->dmap_devlist); if ((error = mk_alloc(dm, uid, &zpath)) != 0) { (void) mk_unalloc(optflag, dm); goto out; } if (system_labeled == 0) { if ((error = _newdac(file_name, uid, getgid(), ALLOC_MODE)) != 0) { (void) _newdac(file_name, DA_UID, DA_GID, ALLOC_ERR_MODE); goto out; } } error = 0; out: if (zpath.count) { for (i = 0; i < zpath.count; i++) free(zpath.path[i]); free(zpath.path); } freedmapent(dm); return (error); } void _store_devnames(int *count, struct dev_names *dnms, char *zonename, devalloc_t *da, int flag) { int i; dnms->dnames = (char **)realloc(dnms->dnames, (*count + 1) * sizeof (char *)); if (da) { dnms->dnames[*count] = strdup(da->da_devname); (*count)++; } else { dnms->dnames[*count] = NULL; if (flag == DA_ADD_ZONE) (void) update_device(dnms->dnames, zonename, DA_ADD_ZONE); else if (flag == DA_REMOVE_ZONE) (void) update_device(dnms->dnames, NULL, DA_REMOVE_ZONE); for (i = 0; i < *count; i++) free(dnms->dnames[i]); free(dnms->dnames); } } int allocate(int optflag, uid_t uid, char *device, char *zonename) { int count = 0; int error = 0; int lock_fd = -1; devalloc_t *da; struct dev_names dnms; if (optflag & (FORCE | USERID | USERNAME)) { if (!_is_authorized(DEVICE_REVOKE_AUTH, getuid())) return (UAUTHERR); } dnms.dnames = NULL; setdaent(); if (optflag & TYPE) { /* * allocate devices of this type */ while ((da = getdatype(device)) != NULL) { if (system_labeled && da_check_logindevperm(da->da_devname)) { freedaent(da); continue; } dprintf("trying to allocate %s\n", da->da_devname); error = _allocate_dev(optflag, uid, da, zonename, &lock_fd); if (system_labeled && (error == 0)) { /* * we need to record in device_allocate the * label (zone name) at which this device is * being allocated. store this device entry. */ _store_devnames(&count, &dnms, zonename, da, 0); } freedaent(da); error = 0; } } else { /* * allocate this device */ if ((da = getdanam(device)) == NULL) { enddaent(); return (NODAERR); } if (system_labeled && da_check_logindevperm(device)) { freedaent(da); return (LOGINDEVPERMERR); } dprintf("trying to allocate %s\n", da->da_devname); error = _allocate_dev(optflag, uid, da, zonename, &lock_fd); /* * we need to record in device_allocate the label (zone name) * at which this device is being allocated. store this device * entry. */ if (system_labeled && (error == 0)) _store_devnames(&count, &dnms, zonename, da, 0); freedaent(da); if (error == DEVCLEAN_BADMOUNT) error = 0; } enddaent(); if (lock_fd != -1) (void) close(lock_fd); /* * add to device_allocate labels (zone names) for the devices we * allocated. */ if (dnms.dnames) _store_devnames(&count, &dnms, zonename, NULL, DA_ADD_ZONE); return (error); } /* ARGSUSED */ int deallocate(int optflag, uid_t uid, char *device, char *zonename) { int count = 0; int error = 0; int lock_fd = -1; char *class = NULL; devalloc_t *da; struct dev_names dnms; if (optflag & (FORCE | FORCE_ALL)) { if (!_is_authorized(DEVICE_REVOKE_AUTH, getuid())) return (UAUTHERR); } if (optflag & FORCE_ALL) optflag |= FORCE; dnms.dnames = NULL; setdaent(); if (optflag & FORCE_ALL) { /* * deallocate all devices */ while ((da = getdaent()) != NULL) { if (system_labeled && da_check_logindevperm(da->da_devname)) { freedaent(da); continue; } dprintf("trying to deallocate %s\n", da->da_devname); error = _deallocate_dev(optflag, da, NULL, uid, zonename, &lock_fd); if (system_labeled && (error == 0)) { /* * we need to remove this device's allocation * label (zone name) from device_allocate. * store this device name. */ _store_devnames(&count, &dnms, zonename, da, 0); } freedaent(da); error = 0; } } else if (system_labeled && (optflag & TYPE)) { /* * deallocate all devices of this type */ while ((da = getdatype(device)) != NULL) { if (da_check_logindevperm(da->da_devname)) { freedaent(da); continue; } dprintf("trying to deallocate %s\n", da->da_devname); error = _deallocate_dev(optflag, da, NULL, uid, zonename, &lock_fd); if (error == 0) { /* * we need to remove this device's allocation * label (zone name) from device_allocate. * store this device name. */ _store_devnames(&count, &dnms, zonename, da, 0); } freedaent(da); error = 0; } } else if (system_labeled && (optflag & CLASS)) { /* * deallocate all devices of this class (for sunray) */ while ((da = getdaent()) != NULL) { class = kva_match(da->da_devopts, DAOPT_CLASS); if (class && (strcmp(class, device) == 0)) { dprintf("trying to deallocate %s\n", da->da_devname); error = _deallocate_dev(optflag, da, NULL, uid, zonename, &lock_fd); if (error == 0) { /* * we need to remove this device's * allocation label (zone name) from * device_allocate. store this device * name. */ _store_devnames(&count, &dnms, zonename, da, 0); } error = 0; } freedaent(da); } } else if (!(optflag & TYPE)) { /* * deallocate this device */ if ((da = getdanam(device)) == NULL) { enddaent(); return (NODAERR); } if (system_labeled && da_check_logindevperm(da->da_devname)) { freedaent(da); return (LOGINDEVPERMERR); } dprintf("trying to deallocate %s\n", da->da_devname); error = _deallocate_dev(optflag, da, NULL, uid, zonename, &lock_fd); if (system_labeled && (error == 0)) { /* * we need to remove this device's allocation label * (zone name) from device_allocate. store this * device name. */ _store_devnames(&count, &dnms, zonename, da, 0); } freedaent(da); if (error == DEVCLEAN_BADMOUNT) error = 0; } enddaent(); if (lock_fd != -1) (void) close(lock_fd); /* * remove from device_allocate labels (zone names) for the devices we * deallocated. */ if (dnms.dnames) _store_devnames(&count, &dnms, zonename, NULL, DA_REMOVE_ZONE); return (error); } static int _dev_file_name(struct state_file *sfp, devmap_t *dm) { sfp->sf_flags = 0; /* if devlist is generated, never leave device in error state */ if (dm->dmap_devlist[0] == '`') sfp->sf_flags |= SFF_NO_ERROR; if (dm->dmap_devarray == NULL || dm->dmap_devarray[0] == NULL) return (NODMAPERR); (void) strncpy(sfp->sf_path, dm->dmap_devarray[0], sizeof (sfp->sf_path)); sfp->sf_path[sizeof (sfp->sf_path) - 1] = '\0'; if (sfp->sf_path[0] == '\0') { dprintf("dev_file_name: no device list for %s\n", dm->dmap_devname); return (NODMAPERR); } return (0); } /* * _check_label - * checks the device label range against zone label, which is also * user's current label. * returns 0 if in range, -1 for all other conditions. * */ static int _check_label(devalloc_t *da, char *zonename, uid_t uid, int flag) { int err; int in_range = 0; char *alloczone, *lstr; char pw_buf[NSS_BUFLEN_PASSWD]; blrange_t *range; m_label_t *zlabel; struct passwd pw_ent; if ((da == NULL) || (zonename == NULL)) return (-1); if ((zlabel = getzonelabelbyname(zonename)) == NULL) { dprintf("unable to get label for %s zone\n", zonename); return (-1); } if (flag == CHECK_DRANGE) { blrange_t drange; drange.lower_bound = blabel_alloc(); lstr = kva_match(da->da_devopts, DAOPT_MINLABEL); if (lstr == NULL) { bsllow(drange.lower_bound); } else if (stobsl(lstr, drange.lower_bound, NO_CORRECTION, &err) == 0) { dprintf("bad min_label for device %s\n", da->da_devname); free(zlabel); blabel_free(drange.lower_bound); return (-1); } drange.upper_bound = blabel_alloc(); lstr = kva_match(da->da_devopts, DAOPT_MAXLABEL); if (lstr == NULL) { bslhigh(drange.upper_bound); } else if (stobsl(lstr, drange.upper_bound, NO_CORRECTION, &err) == 0) { dprintf("bad max_label for device %s\n", da->da_devname); free(zlabel); blabel_free(drange.lower_bound); blabel_free(drange.upper_bound); return (-1); } if (blinrange(zlabel, &drange) == 0) { char *zlbl = NULL, *min = NULL, *max = NULL; (void) bsltos(zlabel, &zlbl, 0, 0); (void) bsltos(drange.lower_bound, &min, 0, 0); (void) bsltos(drange.upper_bound, &max, 0, 0); dprintf("%s zone label ", zonename); dprintf("%s outside device label range: ", zlbl); dprintf("min - %s, ", min); dprintf("max - %s\n", max); free(zlabel); blabel_free(drange.lower_bound); blabel_free(drange.upper_bound); return (-1); } } else if (flag == CHECK_URANGE) { if (getpwuid_r(uid, &pw_ent, pw_buf, sizeof (pw_buf)) == NULL) { dprintf("Unable to get passwd entry for userid %d\n", (int)uid); free(zlabel); return (-1); } if ((range = getuserrange(pw_ent.pw_name)) == NULL) { dprintf("Unable to get label range for userid %d\n", (int)uid); free(zlabel); return (-1); } in_range = blinrange(zlabel, range); free(zlabel); blabel_free(range->lower_bound); blabel_free(range->upper_bound); free(range); if (in_range == 0) { dprintf("%s device label ", da->da_devname); dprintf("out of user %d label range\n", (int)uid); return (-1); } } else if (flag == CHECK_ZLABEL) { alloczone = kva_match(da->da_devopts, DAOPT_ZONE); if (alloczone == NULL) { free(zlabel); return (-1); } if (strcmp(zonename, alloczone) != 0) { dprintf("%s zone is different than ", zonename); dprintf("%s zone to which the device ", alloczone); dprintf("%s is allocated\n", da->da_devname); free(zlabel); return (-1); } } free(zlabel); return (0); } int create_znode(char *zonename, struct zone_path *zpath, devmap_t *list) { int size; int len = 0; int fcount = 0; char *p, *tmpfile, *zoneroot; char **file; char zonepath[MAXPATHLEN]; di_prof_t prof = NULL; file = list->dmap_devarray; if (file == NULL) return (NODMAPERR); if ((zoneroot = getzonerootbyname(zonename)) == NULL) { dprintf("unable to get label for %s zone\n", zonename); return (1); } (void) strcpy(zonepath, zoneroot); free(zoneroot); len = strlen(zonepath); size = sizeof (zonepath); (void) strlcat(zonepath, "/dev", size); if (di_prof_init(zonepath, &prof)) { dprintf("failed to initialize dev profile at %s\n", zonepath); return (1); } zonepath[len] = '\0'; for (; *file != NULL; file++) { /* * First time initialization */ tmpfile = strdup(*file); /* * Most devices have pathnames starting in /dev * but SunRay devices do not. In SRRS 3.1 they use /tmp. * * If the device pathname is not in /dev then create * a symbolic link to it and put the device in /dev */ if (strncmp(tmpfile, "/dev/", strlen("/dev/")) != 0) { char *linkdir; char srclinkdir[MAXPATHLEN]; char dstlinkdir[MAXPATHLEN]; linkdir = strchr(tmpfile + 1, '/'); p = strchr(linkdir + 1, '/'); *p = '\0'; (void) strcpy(dstlinkdir, "/dev"); (void) strncat(dstlinkdir, linkdir, MAXPATHLEN); (void) snprintf(srclinkdir, MAXPATHLEN, "%s/root%s", zonepath, tmpfile); (void) symlink(dstlinkdir, srclinkdir); *p = '/'; (void) strncat(dstlinkdir, p, MAXPATHLEN); free(tmpfile); tmpfile = strdup(dstlinkdir); } if (di_prof_add_dev(prof, tmpfile)) { dprintf("failed to add %s to profile\n", tmpfile); di_prof_fini(prof); return (1); } if (strlcat(zonepath, tmpfile, size) >= size) { dprintf("Buffer overflow in create_znode for %s\n", *file); free(tmpfile); di_prof_fini(prof); return (1); } free(tmpfile); fcount++; if ((zpath->path = (char **)realloc(zpath->path, (fcount * sizeof (char *)))) == NULL) { di_prof_fini(prof); return (1); } zpath->path[zpath->count] = strdup(zonepath); zpath->count = fcount; zonepath[len] = '\0'; } if (di_prof_commit(prof)) dprintf("failed to add devices to zone %s\n", zonename); di_prof_fini(prof); return (0); } int remove_znode(char *zonename, devmap_t *dm) { int len = 0; char *zoneroot; char **file; char zonepath[MAXPATHLEN]; di_prof_t prof = NULL; file = dm->dmap_devarray; if (file == NULL) return (NODMAPERR); if ((zoneroot = getzonerootbyname(zonename)) == NULL) { (void) snprintf(zonepath, MAXPATHLEN, "/zone/%s", zonename); } else { (void) strcpy(zonepath, zoneroot); free(zoneroot); } /* * To support SunRay we will just deal with the * file in /dev, not the symlinks. */ (void) strncat(zonepath, "/dev", MAXPATHLEN); len = strlen(zonepath); if (di_prof_init(zonepath, &prof)) { dprintf("failed to initialize dev profile at %s\n", zonepath); return (1); } for (; *file != NULL; file++) { char *devrelpath; /* * remove device node from zone. * * SunRay devices don't start with /dev * so skip over first directory to make * sure it is /dev. SunRay devices in zones * will have a symlink into /dev but * we don't ever delete it. */ devrelpath = strchr(*file + 1, '/'); if (di_prof_add_exclude(prof, devrelpath + 1)) { dprintf("Failed exclude %s in dev profile\n", *file); di_prof_fini(prof); return (1); } zonepath[len] = '\0'; } if (di_prof_commit(prof)) dprintf("failed to remove devices from zone %s\n", zonename); di_prof_fini(prof); return (0); } int update_device(char **devnames, char *zonename, int flag) { int len, rc; char *optstr = NULL; da_args dargs; devinfo_t devinfo; dargs.optflag = flag; dargs.optflag |= DA_UPDATE|DA_ALLOC_ONLY; dargs.rootdir = NULL; dargs.devnames = devnames; devinfo.devname = devinfo.devtype = devinfo.devauths = devinfo.devexec = devinfo.devlist = NULL; if (dargs.optflag & DA_ADD_ZONE) { len = strlen(DAOPT_ZONE) + strlen(zonename) + 3; if ((optstr = (char *)malloc(len)) == NULL) return (-1); (void) snprintf(optstr, len, "%s%s%s", DAOPT_ZONE, KV_ASSIGN, zonename); devinfo.devopts = optstr; } dargs.devinfo = &devinfo; rc = da_update_device(&dargs); if (optstr) free(optstr); return (rc); }