From e46f9d510db9351682cf17c49115110870147335 Mon Sep 17 00:00:00 2001 From: Stephen Smalley Date: Fri, 13 Jan 2012 08:48:47 -0500 Subject: [PATCH] Extend init and ueventd for SE Android. Add SE Android support for init and ueventd. init: - Load policy at boot. - Set the security context for service daemons and their sockets. - New built-in commands: setcon, setenforce, restorecon, setsebool. - New option for services: seclabel. ueventd: - Set the security context for device directories and nodes. Change-Id: I98ed752cde503c94d99dfa5b5a47e3c33db16aac --- init/Android.mk | 6 ++ init/builtins.c | 85 ++++++++++++++++++++++ init/devices.c | 82 +++++++++++++++++---- init/init.c | 176 ++++++++++++++++++++++++++++++++++++++++++++- init/init.h | 8 +++ init/init_parser.c | 15 ++++ init/keywords.h | 9 +++ init/util.c | 22 ++++++ 8 files changed, 390 insertions(+), 13 deletions(-) diff --git a/init/Android.mk b/init/Android.mk index 162c22640..e9fd8846c 100644 --- a/init/Android.mk +++ b/init/Android.mk @@ -30,6 +30,12 @@ LOCAL_UNSTRIPPED_PATH := $(TARGET_ROOT_OUT_UNSTRIPPED) LOCAL_STATIC_LIBRARIES := libcutils libc +ifeq ($(HAVE_SELINUX),true) +LOCAL_STATIC_LIBRARIES += libselinux +LOCAL_C_INCLUDES := external/libselinux/include +LOCAL_CFLAGS += -DHAVE_SELINUX +endif + include $(BUILD_EXECUTABLE) # Make a symlink from /sbin/ueventd to /init diff --git a/init/builtins.c b/init/builtins.c index 3781dcdc8..9aa2345e3 100644 --- a/init/builtins.c +++ b/init/builtins.c @@ -33,6 +33,11 @@ #include #include +#ifdef HAVE_SELINUX +#include +#include +#endif + #include "init.h" #include "keywords.h" #include "property_service.h" @@ -436,6 +441,28 @@ exit_success: } +int do_setcon(int nargs, char **args) { +#ifdef HAVE_SELINUX + if (is_selinux_enabled() <= 0) + return 0; + if (setcon(args[1]) < 0) { + return -errno; + } +#endif + return 0; +} + +int do_setenforce(int nargs, char **args) { +#ifdef HAVE_SELINUX + if (is_selinux_enabled() <= 0) + return 0; + if (security_setenforce(atoi(args[1])) < 0) { + return -errno; + } +#endif + return 0; +} + int do_setkey(int nargs, char **args) { struct kbentry kbe; @@ -649,6 +676,64 @@ int do_chmod(int nargs, char **args) { return 0; } +int do_restorecon(int nargs, char **args) { +#ifdef HAVE_SELINUX + char *secontext = NULL; + struct stat sb; + int i; + + if (is_selinux_enabled() <= 0 || !sehandle) + return 0; + + for (i = 1; i < nargs; i++) { + if (lstat(args[i], &sb) < 0) + return -errno; + if (selabel_lookup(sehandle, &secontext, args[i], sb.st_mode) < 0) + return -errno; + if (lsetfilecon(args[i], secontext) < 0) { + freecon(secontext); + return -errno; + } + freecon(secontext); + } +#endif + return 0; +} + +int do_setsebool(int nargs, char **args) { +#ifdef HAVE_SELINUX + SELboolean *b = alloca(nargs * sizeof(SELboolean)); + char *v; + int i; + + if (is_selinux_enabled() <= 0) + return 0; + + for (i = 1; i < nargs; i++) { + char *name = args[i]; + v = strchr(name, '='); + if (!v) { + ERROR("setsebool: argument %s had no =\n", name); + return -EINVAL; + } + *v++ = 0; + b[i-1].name = name; + if (!strcmp(v, "1") || !strcasecmp(v, "true") || !strcasecmp(v, "on")) + b[i-1].value = 1; + else if (!strcmp(v, "0") || !strcasecmp(v, "false") || !strcasecmp(v, "off")) + b[i-1].value = 0; + else { + ERROR("setsebool: invalid value %s\n", v); + return -EINVAL; + } + } + + if (security_set_boolean_list(nargs - 1, b, 0) < 0) + return -errno; +#endif + return 0; +} + int do_loglevel(int nargs, char **args) { if (nargs == 2) { klog_set_level(atoi(args[1])); diff --git a/init/devices.c b/init/devices.c index a2f84aa3c..3b4d369d8 100644 --- a/init/devices.c +++ b/init/devices.c @@ -29,6 +29,12 @@ #include #include #include + +#ifdef HAVE_SELINUX +#include +#include +#endif + #include #include #include @@ -45,6 +51,10 @@ #define FIRMWARE_DIR1 "/etc/firmware" #define FIRMWARE_DIR2 "/vendor/firmware" +#ifdef HAVE_SELINUX +static struct selabel_handle *sehandle; +#endif + static int device_fd = -1; struct uevent { @@ -180,8 +190,17 @@ static void make_device(const char *path, unsigned gid; mode_t mode; dev_t dev; +#ifdef HAVE_SELINUX + char *secontext = NULL; +#endif mode = get_device_perm(path, &uid, &gid) | (block ? S_IFBLK : S_IFCHR); +#ifdef HAVE_SELINUX + if (sehandle) { + selabel_lookup(sehandle, &secontext, path, mode); + setfscreatecon(secontext); + } +#endif dev = makedev(major, minor); /* Temporarily change egid to avoid race condition setting the gid of the * device node. Unforunately changing the euid would prevent creation of @@ -192,8 +211,40 @@ static void make_device(const char *path, mknod(path, mode, dev); chown(path, uid, -1); setegid(AID_ROOT); +#ifdef HAVE_SELINUX + if (secontext) { + freecon(secontext); + setfscreatecon(NULL); + } +#endif } + +static int make_dir(const char *path, mode_t mode) +{ + int rc; + +#ifdef HAVE_SELINUX + char *secontext = NULL; + + if (sehandle) { + selabel_lookup(sehandle, &secontext, path, mode); + setfscreatecon(secontext); + } +#endif + + rc = mkdir(path, mode); + +#ifdef HAVE_SELINUX + if (secontext) { + freecon(secontext); + setfscreatecon(NULL); + } +#endif + return rc; +} + + static void add_platform_device(const char *name) { int name_len = strlen(name); @@ -506,7 +557,7 @@ static void handle_block_device_event(struct uevent *uevent) return; snprintf(devpath, sizeof(devpath), "%s%s", base, name); - mkdir(base, 0755); + make_dir(base, 0755); if (!strncmp(uevent->path, "/devices/platform/", 18)) links = parse_platform_block_device(uevent); @@ -535,10 +586,10 @@ static void handle_generic_device_event(struct uevent *uevent) int bus_id = uevent->minor / 128 + 1; int device_id = uevent->minor % 128 + 1; /* build directories */ - mkdir("/dev/bus", 0755); - mkdir("/dev/bus/usb", 0755); + make_dir("/dev/bus", 0755); + make_dir("/dev/bus/usb", 0755); snprintf(devpath, sizeof(devpath), "/dev/bus/usb/%03d", bus_id); - mkdir(devpath, 0755); + make_dir(devpath, 0755); snprintf(devpath, sizeof(devpath), "/dev/bus/usb/%03d/%03d", bus_id, device_id); } else { /* ignore other USB events */ @@ -546,29 +597,29 @@ static void handle_generic_device_event(struct uevent *uevent) } } else if (!strncmp(uevent->subsystem, "graphics", 8)) { base = "/dev/graphics/"; - mkdir(base, 0755); + make_dir(base, 0755); } else if (!strncmp(uevent->subsystem, "oncrpc", 6)) { base = "/dev/oncrpc/"; - mkdir(base, 0755); + make_dir(base, 0755); } else if (!strncmp(uevent->subsystem, "adsp", 4)) { base = "/dev/adsp/"; - mkdir(base, 0755); + make_dir(base, 0755); } else if (!strncmp(uevent->subsystem, "msm_camera", 10)) { base = "/dev/msm_camera/"; - mkdir(base, 0755); + make_dir(base, 0755); } else if(!strncmp(uevent->subsystem, "input", 5)) { base = "/dev/input/"; - mkdir(base, 0755); + make_dir(base, 0755); } else if(!strncmp(uevent->subsystem, "mtd", 3)) { base = "/dev/mtd/"; - mkdir(base, 0755); + make_dir(base, 0755); } else if(!strncmp(uevent->subsystem, "sound", 5)) { base = "/dev/snd/"; - mkdir(base, 0755); + make_dir(base, 0755); } else if(!strncmp(uevent->subsystem, "misc", 4) && !strncmp(name, "log_", 4)) { base = "/dev/log/"; - mkdir(base, 0755); + make_dir(base, 0755); name += 4; } else base = "/dev/"; @@ -819,7 +870,14 @@ void device_init(void) suseconds_t t0, t1; struct stat info; int fd; +#ifdef HAVE_SELINUX + struct selinux_opt seopts[] = { + { SELABEL_OPT_PATH, "/file_contexts" } + }; + if (is_selinux_enabled() > 0) + sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1); +#endif /* is 64K enough? udev uses 16MB! */ device_fd = uevent_open_socket(64*1024, true); if(device_fd < 0) diff --git a/init/init.c b/init/init.c index c3be93d03..71c28b508 100755 --- a/init/init.c +++ b/init/init.c @@ -31,6 +31,13 @@ #include #include #include + +#ifdef HAVE_SELINUX +#include +#include +#include +#endif + #include #include @@ -52,6 +59,10 @@ #include "util.h" #include "ueventd.h" +#ifdef HAVE_SELINUX +struct selabel_handle *sehandle; +#endif + static int property_triggers_enabled = 0; #if BOOTCHART @@ -64,6 +75,11 @@ static char hardware[32]; static unsigned revision = 0; static char qemu[32]; +#ifdef HAVE_SELINUX +static int selinux_enabled = 1; +static int selinux_enforcing = 0; +#endif + static struct action *cur_action = NULL; static struct command *cur_command = NULL; static struct listnode *command_queue = NULL; @@ -145,7 +161,10 @@ void service_start(struct service *svc, const char *dynamic_args) pid_t pid; int needs_console; int n; - +#ifdef HAVE_SELINUX + char *scon = NULL; + int rc; +#endif /* starting a service removes it from the disabled or reset * state and immediately takes it out of the restarting * state if it was in there @@ -182,6 +201,34 @@ void service_start(struct service *svc, const char *dynamic_args) return; } +#ifdef HAVE_SELINUX + if (is_selinux_enabled() > 0) { + char *mycon = NULL, *fcon = NULL; + + INFO("computing context for service '%s'\n", svc->args[0]); + rc = getcon(&mycon); + if (rc < 0) { + ERROR("could not get context while starting '%s'\n", svc->name); + return; + } + + rc = getfilecon(svc->args[0], &fcon); + if (rc < 0) { + ERROR("could not get context while starting '%s'\n", svc->name); + freecon(mycon); + return; + } + + rc = security_compute_create(mycon, fcon, string_to_security_class("process"), &scon); + freecon(mycon); + freecon(fcon); + if (rc < 0) { + ERROR("could not get context while starting '%s'\n", svc->name); + return; + } + } +#endif + NOTICE("starting '%s'\n", svc->name); pid = fork(); @@ -201,6 +248,10 @@ void service_start(struct service *svc, const char *dynamic_args) for (ei = svc->envvars; ei; ei = ei->next) add_environment(ei->name, ei->value); +#ifdef HAVE_SELINUX + setsockcreatecon(scon); +#endif + for (si = svc->sockets; si; si = si->next) { int socket_type = ( !strcmp(si->type, "stream") ? SOCK_STREAM : @@ -212,6 +263,12 @@ void service_start(struct service *svc, const char *dynamic_args) } } +#ifdef HAVE_SELINUX + freecon(scon); + scon = NULL; + setsockcreatecon(NULL); +#endif + if (svc->ioprio_class != IoSchedClass_NONE) { if (android_set_ioprio(getpid(), svc->ioprio_class, svc->ioprio_pri)) { ERROR("Failed to set pid %d ioprio = %d,%d: %s\n", @@ -257,6 +314,15 @@ void service_start(struct service *svc, const char *dynamic_args) } } +#ifdef HAVE_SELINUX + if (svc->seclabel) { + if (is_selinux_enabled() > 0 && setexeccon(svc->seclabel) < 0) { + ERROR("cannot setexeccon('%s'): %s\n", svc->seclabel, strerror(errno)); + _exit(127); + } + } +#endif + if (!dynamic_args) { if (execve(svc->args[0], (char**) svc->args, (char**) ENV) < 0) { ERROR("cannot execve('%s'): %s\n", svc->args[0], strerror(errno)); @@ -282,6 +348,10 @@ void service_start(struct service *svc, const char *dynamic_args) _exit(127); } +#ifdef HAVE_SELINUX + freecon(scon); +#endif + if (pid < 0) { ERROR("failed to start '%s'\n", svc->name); svc->pid = 0; @@ -531,6 +601,14 @@ static void import_kernel_nv(char *name, int for_emulator) *value++ = 0; if (name_len == 0) return; +#ifdef HAVE_SELINUX + if (!strcmp(name,"enforcing")) { + selinux_enforcing = atoi(value); + } else if (!strcmp(name,"selinux")) { + selinux_enabled = atoi(value); + } +#endif + if (for_emulator) { /* in the emulator, export any kernel option with the * ro.kernel. prefix */ @@ -678,6 +756,97 @@ static int bootchart_init_action(int nargs, char **args) } #endif +#ifdef HAVE_SELINUX +void selinux_load_policy(void) +{ + const char path_prefix[] = "/sepolicy"; + struct selinux_opt seopts[] = { + { SELABEL_OPT_PATH, "/file_contexts" } + }; + char path[PATH_MAX]; + int fd, rc, vers; + struct stat sb; + void *map; + + sehandle = NULL; + if (!selinux_enabled) { + INFO("SELinux: Disabled by command line option\n"); + return; + } + + mkdir(SELINUXMNT, 0755); + if (mount("selinuxfs", SELINUXMNT, "selinuxfs", 0, NULL)) { + if (errno == ENODEV) { + /* SELinux not enabled in kernel */ + return; + } + ERROR("SELinux: Could not mount selinuxfs: %s\n", + strerror(errno)); + return; + } + set_selinuxmnt(SELINUXMNT); + + vers = security_policyvers(); + if (vers <= 0) { + ERROR("SELinux: Unable to read policy version\n"); + return; + } + INFO("SELinux: Maximum supported policy version: %d\n", vers); + + snprintf(path, sizeof(path), "%s.%d", + path_prefix, vers); + fd = open(path, O_RDONLY); + while (fd < 0 && errno == ENOENT && --vers) { + snprintf(path, sizeof(path), "%s.%d", + path_prefix, vers); + fd = open(path, O_RDONLY); + } + if (fd < 0) { + ERROR("SELinux: Could not open %s: %s\n", + path, strerror(errno)); + return; + } + if (fstat(fd, &sb) < 0) { + ERROR("SELinux: Could not stat %s: %s\n", + path, strerror(errno)); + return; + } + map = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (map == MAP_FAILED) { + ERROR("SELinux: Could not map %s: %s\n", + path, strerror(errno)); + return; + } + + rc = security_load_policy(map, sb.st_size); + if (rc < 0) { + ERROR("SELinux: Could not load policy: %s\n", + strerror(errno)); + return; + } + + rc = security_setenforce(selinux_enforcing); + if (rc < 0) { + ERROR("SELinux: Could not set enforcing mode to %s: %s\n", + selinux_enforcing ? "enforcing" : "permissive", strerror(errno)); + return; + } + + munmap(map, sb.st_size); + close(fd); + INFO("SELinux: Loaded policy from %s\n", path); + + sehandle = selabel_open(SELABEL_CTX_FILE, seopts, 1); + if (!sehandle) { + ERROR("SELinux: Could not load file_contexts: %s\n", + strerror(errno)); + return; + } + INFO("SELinux: Loaded file contexts from %s\n", seopts[0].value); + return; +} +#endif + int main(int argc, char **argv) { int fd_count = 0; @@ -728,6 +897,11 @@ int main(int argc, char **argv) process_kernel_cmdline(); +#ifdef HAVE_SELINUX + INFO("loading selinux policy\n"); + selinux_load_policy(); +#endif + is_charger = !strcmp(bootmode, "charger"); INFO("property init\n"); diff --git a/init/init.h b/init/init.h index a91d9d4b5..58bbbfe9b 100644 --- a/init/init.h +++ b/init/init.h @@ -95,6 +95,10 @@ struct service { gid_t supp_gids[NR_SVC_SUPP_GIDS]; size_t nr_supp_gids; +#ifdef HAVE_SELINUX + char *seclabel; +#endif + struct socketinfo *sockets; struct svcenvinfo *envvars; @@ -132,4 +136,8 @@ void property_changed(const char *name, const char *value); int load_565rle_image( char *file_name ); +#ifdef HAVE_SELINUX +extern struct selabel_handle *sehandle; +#endif + #endif /* _INIT_INIT_H */ diff --git a/init/init_parser.c b/init/init_parser.c index d255db969..f53845091 100644 --- a/init/init_parser.c +++ b/init/init_parser.c @@ -131,15 +131,20 @@ int lookup_keyword(const char *s) break; case 'r': if (!strcmp(s, "estart")) return K_restart; + if (!strcmp(s, "estorecon")) return K_restorecon; if (!strcmp(s, "mdir")) return K_rmdir; if (!strcmp(s, "m")) return K_rm; break; case 's': + if (!strcmp(s, "eclabel")) return K_seclabel; if (!strcmp(s, "ervice")) return K_service; + if (!strcmp(s, "etcon")) return K_setcon; + if (!strcmp(s, "etenforce")) return K_setenforce; if (!strcmp(s, "etenv")) return K_setenv; if (!strcmp(s, "etkey")) return K_setkey; if (!strcmp(s, "etprop")) return K_setprop; if (!strcmp(s, "etrlimit")) return K_setrlimit; + if (!strcmp(s, "etsebool")) return K_setsebool; if (!strcmp(s, "ocket")) return K_socket; if (!strcmp(s, "tart")) return K_start; if (!strcmp(s, "top")) return K_stop; @@ -792,6 +797,16 @@ static void parse_line_service(struct parse_state *state, int nargs, char **args svc->uid = decode_uid(args[1]); } break; + case K_seclabel: +#ifdef HAVE_SELINUX + if (nargs != 2) { + parse_error(state, "seclabel option requires a label string\n"); + } else { + svc->seclabel = args[1]; + } +#endif + break; + default: parse_error(state, "invalid option '%s'\n", args[0]); } diff --git a/init/keywords.h b/init/keywords.h index 3e3733f28..307c0845d 100644 --- a/init/keywords.h +++ b/init/keywords.h @@ -14,11 +14,15 @@ int do_insmod(int nargs, char **args); int do_mkdir(int nargs, char **args); int do_mount(int nargs, char **args); int do_restart(int nargs, char **args); +int do_restorecon(int nargs, char **args); int do_rm(int nargs, char **args); int do_rmdir(int nargs, char **args); +int do_setcon(int nargs, char **args); +int do_setenforce(int nargs, char **args); int do_setkey(int nargs, char **args); int do_setprop(int nargs, char **args); int do_setrlimit(int nargs, char **args); +int do_setsebool(int nargs, char **args); int do_start(int nargs, char **args); int do_stop(int nargs, char **args); int do_trigger(int nargs, char **args); @@ -61,13 +65,18 @@ enum { KEYWORD(oneshot, OPTION, 0, 0) KEYWORD(onrestart, OPTION, 0, 0) KEYWORD(restart, COMMAND, 1, do_restart) + KEYWORD(restorecon, COMMAND, 1, do_restorecon) KEYWORD(rm, COMMAND, 1, do_rm) KEYWORD(rmdir, COMMAND, 1, do_rmdir) + KEYWORD(seclabel, OPTION, 0, 0) KEYWORD(service, SECTION, 0, 0) + KEYWORD(setcon, COMMAND, 1, do_setcon) + KEYWORD(setenforce, COMMAND, 1, do_setenforce) KEYWORD(setenv, OPTION, 2, 0) KEYWORD(setkey, COMMAND, 0, do_setkey) KEYWORD(setprop, COMMAND, 2, do_setprop) KEYWORD(setrlimit, COMMAND, 3, do_setrlimit) + KEYWORD(setsebool, COMMAND, 1, do_setsebool) KEYWORD(socket, OPTION, 0, 0) KEYWORD(start, COMMAND, 1, do_start) KEYWORD(stop, COMMAND, 1, do_stop) diff --git a/init/util.c b/init/util.c index 13c9ca25a..3a4b10b0e 100755 --- a/init/util.c +++ b/init/util.c @@ -23,6 +23,10 @@ #include #include +#ifdef HAVE_SELINUX +#include +#endif + #include #include #include @@ -33,6 +37,7 @@ #include +#include "init.h" #include "log.h" #include "util.h" @@ -84,6 +89,9 @@ int create_socket(const char *name, int type, mode_t perm, uid_t uid, gid_t gid) { struct sockaddr_un addr; int fd, ret; +#ifdef HAVE_SELINUX + char *secon; +#endif fd = socket(PF_UNIX, type, 0); if (fd < 0) { @@ -102,12 +110,26 @@ int create_socket(const char *name, int type, mode_t perm, uid_t uid, gid_t gid) goto out_close; } +#ifdef HAVE_SELINUX + secon = NULL; + if (sehandle) { + ret = selabel_lookup(sehandle, &secon, addr.sun_path, S_IFSOCK); + if (ret == 0) + setfscreatecon(secon); + } +#endif + ret = bind(fd, (struct sockaddr *) &addr, sizeof (addr)); if (ret) { ERROR("Failed to bind socket '%s': %s\n", name, strerror(errno)); goto out_unlink; } +#ifdef HAVE_SELINUX + setfscreatecon(NULL); + freecon(secon); +#endif + chown(addr.sun_path, uid, gid); chmod(addr.sun_path, perm);