diff --git a/usr.bin/crontab/Makefile b/usr.bin/crontab/Makefile index d6da601..c08d580 100644 --- a/usr.bin/crontab/Makefile +++ b/usr.bin/crontab/Makefile @@ -1,10 +1,11 @@ # $OpenBSD: Makefile,v 1.5 2005/12/19 19:12:17 millert Exp $ PROG= crontab -SRCS= crontab.c misc.c entry.c env.c closeall.c ../../lib/libc/gen/pw_dup.c -CFLAGS+=-I${.CURDIR} -I${.CURDIR}/../../usr.sbin/cron -DDEBUGGING=0 +SRCS= crontab.c misc.c entry.c env.c closeall.c selinux.c ../../lib/libc/gen/pw_dup.c +CFLAGS+=-I${.CURDIR} -I${.CURDIR}/../../usr.sbin/cron -DDEBUGGING=0 -DWITH_SELINUX=1 BINGRP =crontab BINMODE=2555 +LDLIBS+=-lselinux MAN= crontab.1 crontab.5 #.PATH: ${.CURDIR}/../../usr.sbin/cron diff --git a/usr.sbin/cron/Makefile b/usr.sbin/cron/Makefile index 9f4d50e..ee7e7f2 100644 --- a/usr.sbin/cron/Makefile +++ b/usr.sbin/cron/Makefile @@ -2,9 +2,9 @@ PROG= crond SRCS= cron.c database.c user.c entry.c job.c do_command.c \ - misc.c env.c popen.c atrun.c closeall.c pam_auth.c ../../lib/libc/gen/pw_dup.c -CFLAGS+=-I${.CURDIR} -DHAVE_SETPROCTITLE=1 -LDLIBS+=-lpam -lsetproctitle + misc.c env.c popen.c atrun.c closeall.c pam_auth.c selinux.c ../../lib/libc/gen/pw_dup.c +CFLAGS+=-I${.CURDIR} -DHAVE_SETPROCTITLE=1 -DWITH_SELINUX=1 +LDLIBS+=-lpam -lsetproctitle -lselinux MAN= cron.8 #.include diff --git a/usr.sbin/cron/atrun.c b/usr.sbin/cron/atrun.c index 328b729..8a53d71 100644 --- a/usr.sbin/cron/atrun.c +++ b/usr.sbin/cron/atrun.c @@ -529,6 +529,17 @@ run_job(atjob *job, char *atfile) if (job->queue > 'b') (void)setpriority(PRIO_PROCESS, 0, job->queue - 'b'); +#ifdef WITH_SELINUX + if (is_selinux_enabled() > 0) { + char *err_msg = NULL; + if (set_at_selinux_context(pw->pw_name, STDIN, &err_msg) < 0) { + fprintf(stderr, "at job %s: %s\n", atfile, err_msg); + free(err_msg); + if (security_getenforce() > 0) + _exit(ERROR_EXIT); + } + } +#endif /* WITH_SELINUX */ #if DEBUGGING if (DebugFlags & DTEST) { fprintf(stderr, diff --git a/usr.sbin/cron/crontab.1 b/usr.sbin/cron/crontab.1 index 26f19c9..603f1ff 100644 --- a/usr.sbin/cron/crontab.1 +++ b/usr.sbin/cron/crontab.1 @@ -117,6 +117,11 @@ environment variables. After you exit from the editor, the modified .Xr crontab 5 will be installed automatically. +.It Fl s +It will append the current SELinux security context string as an +MLS_LEVEL setting to the crontab file before editing / replacement +occurs - see the documentation of MLS_LEVEL in +.Xr crontab 5 . .El .Sh FILES .Bl -tag -width "/etc/cron.allow" -compact diff --git a/usr.sbin/cron/crontab.5 b/usr.sbin/cron/crontab.5 index 838a330..2cba75d 100644 --- a/usr.sbin/cron/crontab.5 +++ b/usr.sbin/cron/crontab.5 @@ -167,6 +167,24 @@ Otherwise mail is sent to the owner of the This option is useful for pseudo-users that lack an alias that would otherwise redirect the mail to a real person. .Pp +The +.Ev MLS_LEVEL +environment variable provides support for multiple per-job +SELinux security contexts in the same crontab. +By default, cron jobs execute with the default SELinux security context of the +user that created the crontab file. +When using multiple security levels and roles, this may not be sufficient, because +the same user may be running in a different role or at a different security level. +For more about roles and SELinux MLS/MCS see +.Xr selinux 8 +and undermentioned crontab example. +You can set MLS_LEVEL to the SELinux security context string specifying +the SELinux security context in which you want the job to run, and crond will set +the execution context of the or jobs to which the setting applies to the specified +context. +See also the +.Xr crontab 1 -s option. +.Pp .Em cron Commands .Pp The format of a @@ -368,6 +386,27 @@ MAILTO=paul 23 0-23/2 * * * echo "run 23 minutes after midn, 2am, 4am ..., everyday" 5 4 * * sun echo "run at 5 after 4 every sunday" .Ed +.Sh SELinux with multi level security (MLS) +In crontab is important specified security level by \fIcrontab\ -s\fR or specifying +the required level on the first line of the crontab. Each level is specified +in \fI/etc/selinux/targeted/seusers\fR. For using crontab in MLS mode is really important: +.br +- check/change actual role, +.br +- set correct \fIrole for directory\fR, which is used for input/output. +.Sh EXAMPLE FOR SELINUX MLS +.nf +# login as root +newrole -r sysadm_r +mkdir /tmp/SystemHigh +chcon -l SystemHigh /tmp/SystemHigh +crontab -e +# write in crontab file +MLS_LEVEL=SystemHigh +0-59 * * * * id -Z > /tmp/SystemHigh/crontest +When I log in as a normal user, it can't work, because \fI/tmp/SystemHigh\fR is +higher than my level. +.fi .Sh SEE ALSO .Xr crontab 1 , .Xr cron 8 diff --git a/usr.sbin/cron/crontab.c b/usr.sbin/cron/crontab.c index 31889af..4335ef4 100644 --- a/usr.sbin/cron/crontab.c +++ b/usr.sbin/cron/crontab.c @@ -35,13 +35,20 @@ static char const rcsid[] = "$OpenBSD: crontab.c,v 1.49 2005/11/29 20:43:31 mill #define NHEADER_LINES 3 #define CRONTAB_TEMPLATE "/etc/crontab.template" +#ifdef WITH_SELINUX +static int set_mls_level; +#define OPT_S "s" +#else +#define OPT_S +#endif /* WITH_SELINUX */ + enum opt_t { opt_unknown, opt_list, opt_delete, opt_edit, opt_replace }; #if DEBUGGING static char *Options[] = { "???", "list", "delete", "edit", "replace" }; -static char *getoptargs = "u:lerx:"; +static char *getoptargs = "u:lerx:" OPT_S; #else -static char *getoptargs = "u:ler"; +static char *getoptargs = "u:ler" OPT_S; #endif static PID_T Pid; @@ -71,6 +78,9 @@ usage(const char *msg) { fprintf(stderr, "\t-e\t(edit user's crontab)\n"); fprintf(stderr, "\t-l\t(list user's crontab)\n"); fprintf(stderr, "\t-r\t(delete user's crontab)\n"); +#ifdef WITH_SELINUX + fprintf(stderr, "\t-s\t(selinux context)\n"); +#endif /* WITH_SELINUX */ exit(ERROR_EXIT); } @@ -154,6 +164,13 @@ parse_args(int argc, char *argv[]) { "must be privileged to use -u\n"); exit(ERROR_EXIT); } +#ifdef WITH_SELINUX + if (selinux_crontab_access_denied()) { + fprintf(stderr, + "access denied by SELinux, must be privileged to use -u\n"); + exit(ERROR_EXIT); + } +#endif /* WITH_SELINUX */ if (!(pw = getpwnam(optarg))) { fprintf(stderr, "%s: user `%s' unknown\n", ProgramName, optarg); @@ -177,6 +194,12 @@ parse_args(int argc, char *argv[]) { usage("only one operation permitted"); Option = opt_edit; break; +#ifdef WITH_SELINUX + case 's': + set_mls_level = 1; + break; +#endif /* WITH_SELINUX */ + default: usage("unrecognized option"); } @@ -363,6 +386,15 @@ edit_cmd(void) { Set_LineNum(1) copy_crontab(f, NewCrontab); fclose(f); +#ifdef WITH_SELINUX + if (set_mls_level) { + /* Do not write MLS_LEVEL again in replace_cmd() */ + set_mls_level = 0; + if (write_crontab_mls_level(NewCrontab) < 0) + goto fatal; + } +#endif /* WITH_SELINUX */ + if (fflush(NewCrontab) < OK) { perror(Filename); exit(ERROR_EXIT); @@ -592,6 +624,14 @@ replace_cmd(void) { fprintf(tmp, "# (%s installed on %-24.24s)\n", Filename, ctime(&now)); fprintf(tmp, "# (Cron version %s -- %s)\n", CRON_VERSION, rcsid); +#ifdef WITH_SELINUX + if (set_mls_level && write_crontab_mls_level(NewCrontab) < 0) { + fclose(tmp); + error = -2; + goto done; + } +#endif /* WITH_SELINUX */ + /* copy the crontab to the tmp */ rewind(NewCrontab); diff --git a/usr.sbin/cron/database.c b/usr.sbin/cron/database.c index 28b0dd6..d0cbf84 100644 --- a/usr.sbin/cron/database.c +++ b/usr.sbin/cron/database.c @@ -40,7 +40,7 @@ struct spooldir { static struct spooldir spools[] = { {SPOOL_DIR, NULL, NULL}, - {"/etc/cron.d", "root", "*system*"}, + {"/etc/cron.d", "root", SYSUSERNAME}, {NULL, NULL, NULL} }; @@ -99,7 +99,7 @@ load_database(cron_db *old_db) { new_db.head = new_db.tail = NULL; if (syscron_stat.st_mtime) { - process_crontab(ROOT_USER, "*system*", SYSCRONTAB, &syscron_stat, + process_crontab(ROOT_USER, SYSUSERNAME, SYSCRONTAB, &syscron_stat, &new_db, old_db); } @@ -210,7 +210,7 @@ process_crontab(const char *uname, const char *fname, const char *tabname, struct stat lstatbuf; user *u; - if (strcmp(fname, "*system*") && !(pw = getpwnam(uname))) { + if (strcmp(fname, SYSUSERNAME) && !(pw = getpwnam(uname))) { /* file doesn't have a user in passwd file. * */ log_it(fname, getpid(), "ORPHAN", "no passwd entry"); @@ -295,7 +295,8 @@ process_crontab(const char *uname, const char *fname, const char *tabname, free_user(u); log_it(fname, getpid(), "RELOAD", tabname); } - u = load_user(crontab_fd, pw, fname); + + u = load_user(crontab_fd, pw, fname, tabname); if (u != NULL) { u->mtime = statbuf->st_mtime; link_user(new_db, u); diff --git a/usr.sbin/cron/do_command.c b/usr.sbin/cron/do_command.c index d84fc0d..ab4961a 100644 --- a/usr.sbin/cron/do_command.c +++ b/usr.sbin/cron/do_command.c @@ -313,6 +313,13 @@ child_process(entry *e, user *u) { _exit(OK_EXIT); } # endif /*DEBUGGING*/ +#ifdef WITH_SELINUX + if (is_selinux_enabled() > 0 && + set_job_selinux_context(u, envp) && + security_getenforce() > 0) { + _exit(ERROR_EXIT); + } +#endif /* WITH_SELINUX */ execle(shell, shell, "-c", e->cmd, (char *)NULL, envp); fprintf(stderr, "execle: couldn't exec `%s'\n", shell); perror("execle"); diff --git a/usr.sbin/cron/externs.h b/usr.sbin/cron/externs.h index 20b1dec..8af57eb 100644 --- a/usr.sbin/cron/externs.h +++ b/usr.sbin/cron/externs.h @@ -68,6 +68,10 @@ # include #endif /*BSD_AUTH*/ +#ifdef WITH_SELINUX +#include +#endif /* WITH_SELINUX */ + #define DIR_T struct dirent #define WAIT_T int #define SIG_T sig_t diff --git a/usr.sbin/cron/funcs.h b/usr.sbin/cron/funcs.h index 49f9c4d..2460abe 100644 --- a/usr.sbin/cron/funcs.h +++ b/usr.sbin/cron/funcs.h @@ -69,7 +69,7 @@ char *env_get(char *, char **), struct passwd *pw_dup(const struct passwd *); void mkprint(char *, unsigned char *, int); -user *load_user(int, struct passwd *, const char *), +user *load_user(int, struct passwd *, const char *, const char *), *find_user(cron_db *, const char *); entry *load_entry(FILE *, @@ -89,3 +89,11 @@ extern void cron_pam_finish (void); extern void cron_pam_child_close (void); extern char **cron_pam_getenvlist (char **envp); #endif + +#ifdef WITH_SELINUX +char* get_selinux_context(const char *name, int fd, char **err_msg); +int set_job_selinux_context(const user *u, char **envp); +int set_at_selinux_context(const char *name, int fd, char **err_msg); +int selinux_crontab_access_denied(void); +int write_crontab_mls_level(FILE *crontab); +#endif /* WITH_SELINUX */ diff --git a/usr.sbin/cron/macros.h b/usr.sbin/cron/macros.h index e96c342..1b856ab 100644 --- a/usr.sbin/cron/macros.h +++ b/usr.sbin/cron/macros.h @@ -70,6 +70,8 @@ #define MAXHOSTNAMELEN 64 #endif +#define SYSUSERNAME "*system*" + #define Skip_Blanks(c, f) \ while (c == '\t' || c == ' ') \ c = get_char(f); diff --git a/usr.sbin/cron/selinux.c b/usr.sbin/cron/selinux.c new file mode 100644 index 0000000..07dd291 --- /dev/null +++ b/usr.sbin/cron/selinux.c @@ -0,0 +1,275 @@ +#include "cron.h" +#include +#include + +static int +selinux_authorize(const char *scon, const char *tcon, + security_class_t tclass, access_vector_t requested) { + struct av_decision avd; + int rc; + + rc = security_compute_av(scon, tcon, tclass, requested, &avd); + if (rc || ((requested & avd.allowed) != requested)) + return 0; + return 1; +} + +static int +selinux_authorize_context(const char *scontext, + const char *file_context) { + /* Since crontab files are not directly executed, + * crond must ensure that the crontab file has + * a context that is appropriate for the context of + * the user cron job. It performs an entrypoint + * permission check for this purpose. + */ + security_class_t tclass; + access_vector_t bit; + + tclass = string_to_security_class("file"); + if (tclass <= 0) { + log_it("CRON", getpid(), + "Failed to translate security class file", strerror(errno)); + return 0; + } + + bit = string_to_av_perm(tclass, "entrypoint"); + if (bit <= 0) { + log_it("CRON", getpid(), + "Failed to translate av perm entrypoint", strerror(errno)); + return 0; + } + + return selinux_authorize(scontext, file_context, tclass, bit); +} + +static int +selinux_authorize_range(const char *scontext, + const char *ucontext) { + /* Since crontab files are not directly executed, + * so crond must ensure that any user specified range + * falls within the seusers-specified range for that Linux user. + */ + security_class_t tclass; + access_vector_t bit; + + tclass = string_to_security_class("context"); + if (tclass <= 0) { + log_it("CRON", getpid(), + "Failed to translate security class context", strerror(errno)); + return 0; + } + + bit = string_to_av_perm(tclass, "contains"); + if (bit <= 0) { + log_it("CRON", getpid(), + "Failed to translate av perm contains", strerror(errno)); + return 0; + } + + return selinux_authorize(scontext, ucontext, tclass, bit); +} + +static char* +get_job_context(const char *u_name, const char *u_scontext, const char *range) +{ + char *scontext = NULL; + context_t ccon = NULL; + + if (range) { + if (!(ccon = context_new(u_scontext))) { + log_it(u_name, getpid(), + "context_new FAILED for MLS_LEVEL", range); + return NULL; + } + + if (context_range_set(ccon, range)) { + log_it(u_name, getpid(), + "context_range_set FAILED for MLS_LEVEL", range); + goto out; + } + + if (!(scontext = context_str(ccon))) { + log_it(u_name, getpid(), + "context_str FAILED for MLS_LEVEL", range); + goto out; + } + } + + if (!(scontext = strdup(scontext ? scontext : u_scontext))) { + log_it(u_name, getpid(), "strdup FAILED for MLS_LEVEL", range); + } + +out: + context_free(ccon); + return scontext; +} + +char* +get_selinux_context(const char *name, int fd, char **err_msg) { + char *user_context = NULL; + char *file_context = NULL; + char *seuser = NULL; + char *level = NULL; + + if (fgetfilecon(fd, &file_context) < OK) { + *err_msg = strdup("getfilecon FAILED"); + return NULL; + } + + if (name && getseuserbyname(name, &seuser, &level) < OK) { + *err_msg = strdup("NO SEUSER"); + goto out; + } + + if (get_default_context_with_level(name ? seuser : "system_u", + level, NULL, &user_context) < OK) { + *err_msg = strdup("No SELinux security context"); + goto out; + } + + if (!selinux_authorize_context(user_context, file_context)) { + freecon(user_context); + user_context = NULL; + *err_msg = strdup("Unauthorized SELinux context"); + } + +out: + free(seuser); + free(level); + freecon(file_context); + + return user_context; +} + +int +set_job_selinux_context(const user *u, char **envp) +{ + char *scontext = NULL; + int rc = -1; + + if (u->scontext == NULL) { + log_it(u->name, getpid(), "NULL security context for user", ""); + return -1; + } + + scontext = get_job_context(u->name, u->scontext, env_get("MLS_LEVEL", envp)); + if (!scontext) + return -1; + if (strcmp(u->scontext, scontext) && + !selinux_authorize_range(u->scontext, scontext)) { + char *msg = NULL; + if (asprintf(&msg, "Unauthorized range in %s for user range in %s", + (char *) scontext, (const char *)u->scontext) >= 0) { + log_it(u->name, getpid(), "ERROR", msg); + free(msg); + } + goto out; + } + + if (setexeccon(scontext) < 0 || setkeycreatecon(scontext) < 0) { + char *msg = NULL; + if (asprintf(&msg, "Could not set exec or keycreate context to %s for user", + (char *) scontext) >= 0) { + log_it(u->name, getpid(), "ERROR", msg); + free(msg); + } + goto out; + } + + rc = 0; +out: + freecon(scontext); + + return rc; +} + +int +set_at_selinux_context(const char *name, int fd, char **err_msg) +{ + char *scontext = NULL; + int rc = 0; + + scontext = get_selinux_context(name, fd, err_msg); + if (scontext == NULL) + return -1; + + if (setexeccon(scontext) < 0 || setkeycreatecon(scontext) < 0) { + asprintf(err_msg, "Could not set exec or keycreate context to %s", + (char *) scontext); + rc = -1; + } + + freecon(scontext); + + return rc; +} + +int +selinux_crontab_access_denied(void) { + security_class_t passwd_class; + access_vector_t bit; + char *scontext; + int rc = 1; + + if (is_selinux_enabled() <= 0) + return 0; + + if (getprevcon(&scontext)) { + perror("Cannot obtain SELinux process context"); + return security_getenforce() > 0; + } + + passwd_class = string_to_security_class("passwd"); + if (passwd_class <= 0) { + fprintf(stderr, "Security class \"passwd\" is not defined in the SELinux policy.\n"); + goto out; + } + + bit = string_to_av_perm(passwd_class, "crontab"); + if (bit <= 0) { + fprintf(stderr, "Failed to translate av perm \"crontab\" for security class \"passwd\".\n"); + goto out; + } + + rc = !selinux_authorize(scontext, scontext, passwd_class, bit); + +out: + freecon(scontext); + + return rc ? (security_getenforce() > 0) : 0; +} + +int +write_crontab_mls_level(FILE *crontab) { + char *scontext = NULL; + context_t ccon = NULL; + const char *level = NULL; + int rc = -1; + + if (getprevcon(&scontext)) { + perror("Cannot obtain SELinux process context"); + return -1; + } + + if (!(ccon = context_new(scontext))) { + perror("context_new failed"); + goto out; + } + + if (!(level = context_range_get(ccon))) { + perror("context_range failed"); + goto out; + } + + rc = fprintf(crontab, "MLS_LEVEL=%s\n", level); + if (rc < 0) + perror("Cannot write MLS level"); + +out: + context_free(ccon); + freecon(scontext); + + return rc; +} + diff --git a/usr.sbin/cron/structs.h b/usr.sbin/cron/structs.h index 1b72d79..8ecc5e7 100644 --- a/usr.sbin/cron/structs.h +++ b/usr.sbin/cron/structs.h @@ -48,6 +48,9 @@ typedef struct _user { char *name; time_t mtime; /* last modtime of crontab */ entry *crontab; /* this person's crontab */ +#ifdef WITH_SELINUX + char *scontext; /* SELinux security context */ +#endif /* WITH_SELINUX */ } user; typedef struct _cron_db { diff --git a/usr.sbin/cron/user.c b/usr.sbin/cron/user.c index facc0b1..efa7394 100644 --- a/usr.sbin/cron/user.c +++ b/usr.sbin/cron/user.c @@ -39,11 +39,15 @@ free_user(user *u) { ne = e->next; free_entry(e); } +#ifdef WITH_SELINUX + freecon(u->scontext); +#endif /* WITH_SELINUX */ free(u); } user * -load_user(int crontab_fd, struct passwd *pw, const char *name) { +load_user(int crontab_fd, struct passwd *pw, + const char *name, const char *tabname) { char envstr[MAX_ENVSTR]; FILE *file; user *u; @@ -80,6 +84,24 @@ load_user(int crontab_fd, struct passwd *pw, const char *name) { return (NULL); } +#ifdef WITH_SELINUX + u->scontext = NULL; + if (is_selinux_enabled() > 0) { + char *err_msg = NULL; + u->scontext = get_selinux_context(strcmp(name, SYSUSERNAME) ? name : NULL, + crontab_fd, &err_msg); + if (!u->scontext) { + log_it(name, getpid(), err_msg, tabname); + free(err_msg); + if(security_getenforce() > 0) { + free_user(u); + u = NULL; + goto done; + } + } + } +#endif /* WITH_SELINUX */ + /* load the crontab */ while ((status = load_env(envstr, file)) >= OK) {