From: Christian Kastner <ckk@kvr.at>
Date: Thu, 07 Jan 2016 23:17:45 +0100
Subject: SELinux support

Add SELinux support.

Patch contributed by Manoj Srivastava <srivasta@debian.org>, with additional
fixes by Russell Coker <russell@coker.com.au> and Laurent Bigonville
<bigon@debian.org>.
 
Bug-Debian: https://bugs.debian.org/264320
Bug-Debian: https://bugs.debian.org/315509
Bug-Debian: https://bugs.debian.org/324017
Bug-Debian: https://bugs.debian.org/383857
Bug-Debian: https://bugs.debian.org/857662
Bug-Debian: https://bugs.debian.org/924716
Forwarded: no
Last-Update: 2019-09-15
Index: cron/Makefile
===================================================================
--- cron.orig/Makefile
+++ cron/Makefile
@@ -55,7 +55,7 @@ DESTMAN		=	$(DESTROOT)/share/man
 INCLUDE		=	-I.
 #INCLUDE	=
 #<<need getopt()>>
-LIBS		= $(PAM_LIBS)
+LIBS		= $(PAM_LIBS) $(SELINUX_LIBS)
 #<<optimize or debug?>>
 #OPTIM		=	-O
 OPTIM		=	-g
@@ -73,7 +73,7 @@ LINTFLAGS	=	-hbxa $(INCLUDE) $(COMPAT) $
 #<<manifest defines>>
 # Allow override from command line
 DEBUG_DEFS	?= -DDEBUGGING=0
-DEFS		= $(DEBUG_DEFS) $(PAM_DEFS)
+DEFS		= $(DEBUG_DEFS) $(PAM_DEFS) $(SELINUX_DEFS)
 #(SGI IRIX systems need this)
 #DEFS		=	-D_BSD_SIGNALS -Dconst=
 #<<the name of the BSD-like install program>>
Index: cron/cron.h
===================================================================
--- cron.orig/cron.h
+++ cron/cron.h
@@ -40,6 +40,13 @@
 #include "config.h"
 #include "externs.h"
 
+#ifdef WITH_SELINUX
+#include <selinux/selinux.h>
+#endif
+
+#define SYSUSERNAME "root"
+
+
 	/* these are really immutable, and are
 	 *   defined for symbolic convenience only
 	 * TRUE, FALSE, and ERR must be distinct
@@ -178,6 +185,9 @@ typedef	struct _user {
 	char		*name;
 	time_t		mtime;		/* last modtime of crontab */
 	entry		*crontab;	/* this person's crontab */
+#ifdef WITH_SELINUX
+	security_context_t scontext;    /* SELinux security context */
+#endif
 } user;
 
 typedef	struct _cron_db {
@@ -224,7 +234,7 @@ char		*env_get __P((char *, char **)),
 		**env_copy __P((char **)),
 		**env_set __P((char **, char *));
 
-user		*load_user __P((int, struct passwd *, char *)),
+user		*load_user __P((int, struct passwd *, char *, char *, char *)),
 		*find_user __P((cron_db *, const char *));
 
 entry		*load_entry __P((FILE *, void (*)(),
Index: cron/database.c
===================================================================
--- cron.orig/database.c
+++ cron/database.c
@@ -98,7 +98,7 @@ load_database(old_db)
 	new_db.head = new_db.tail = NULL;
 
 	if (syscron_stat.st_mtime) {
-		process_crontab("root", "*system*",
+		process_crontab(SYSUSERNAME, "*system*",
 				SYSCRONTAB, &syscron_stat,
 				&new_db, old_db);
 	}
@@ -285,7 +285,8 @@ process_crontab(uname, fname, tabname, s
 		free_user(u);
 		log_it(fname, getpid(), "RELOAD", tabname);
 	}
-	u = load_user(crontab_fd, pw, fname);
+
+	u = load_user(crontab_fd, pw, uname, fname, tabname);
 	if (u != NULL) {
 		u->mtime = statbuf->st_mtime;
 		link_user(new_db, u);
Index: cron/do_command.c
===================================================================
--- cron.orig/do_command.c
+++ cron/do_command.c
@@ -42,6 +42,11 @@ static const struct pam_conv conv = {
 	}
 #endif
 
+#ifdef WITH_SELINUX
+#include <selinux/selinux.h>
+/* #include <selinux/get_context_list.h> */
+#endif
+
 
 static void		child_process __P((entry *, user *)),
 			do_univ __P((user *));
@@ -313,6 +318,24 @@ child_process(e, u)
 				_exit(OK_EXIT);
 			}
 # endif /*DEBUGGING*/
+#ifdef WITH_SELINUX
+			if (is_selinux_enabled() > 0) {
+				if (u->scontext != 0L) {
+					if (setexeccon(u->scontext) < 0 &&
+							security_getenforce() > 0) {
+						fprintf(stderr, "Could not set exec context "
+								"to %s for user  %s\n",
+								u->scontext,u->name);
+						_exit(ERROR_EXIT);
+					}
+				} else if (security_getenforce() > 0) {
+					fprintf(stderr, "Error, must have a security context "
+							"for the cron job when in enforcing "
+							"mode.\nUser %s.\n", u->name);
+					_exit(ERROR_EXIT);
+				}
+			}
+#endif
 			execle(shell, shell, "-c", e->cmd, (char *)0, jobenv);
 			fprintf(stderr, "%s: execle: %s\n", shell, strerror(errno));
 			_exit(ERROR_EXIT);
Index: cron/user.c
===================================================================
--- cron.orig/user.c
+++ cron/user.c
@@ -25,6 +25,133 @@ static char rcsid[] = "$Id: user.c,v 2.8
 
 #include "cron.h"
 
+#ifdef WITH_SELINUX
+#include <selinux/context.h>
+#include <selinux/selinux.h>
+#include <selinux/get_context_list.h>
+
+static int get_security_context(char *name, int crontab_fd, security_context_t
+				*rcontext, char *tabname) {
+	security_context_t *context_list = NULL;
+	security_context_t current_con;
+	int list_count = 0;
+	security_context_t  file_context=NULL;
+	struct av_decision avd;
+	int retval=0;
+	char *seuser = NULL;
+	char *level = NULL;
+	int i;
+
+	if (getcon(&current_con)) {
+		log_it(name, getpid(), "Can't get current context", tabname);
+		return -1;
+	}
+
+	if (name != NULL) {
+		if (getseuserbyname(name, &seuser, &level)) {
+			log_it(name, getpid(), "getseuserbyname FAILED", tabname);
+			freecon(current_con);
+			return (security_getenforce() > 0);
+		}
+	} else {
+		context_t temp_con = context_new(current_con);
+		if (temp_con == NULL) {
+			log_it(name, getpid(), "context_new FAILED", tabname);
+			freecon(current_con);
+			return (security_getenforce() > 0);
+		}
+		seuser = strdup(context_user_get(temp_con));
+		context_free(temp_con);
+	}
+
+	*rcontext = NULL;
+	list_count = get_ordered_context_list_with_level(seuser, level, current_con, &context_list);
+	freecon(current_con);
+	free(seuser);
+	free(level);
+	if (list_count == -1) {
+		if (security_getenforce() > 0) {
+			log_it(name, getpid(), "No SELinux security context", tabname);
+			return -1;
+		} else {
+			log_it(name, getpid(),
+				"No security context but SELinux in permissive mode,"
+				" continuing", tabname);
+			return 0;
+		}
+	}
+
+	if (fgetfilecon(crontab_fd, &file_context) < OK) {
+		if (security_getenforce() > 0) {
+			log_it(name, getpid(), "getfilecon FAILED", tabname);
+			freeconary(context_list);
+			return -1;
+		} else {
+			log_it(name, getpid(), "getfilecon FAILED but SELinux in "
+				"permissive mode, continuing", tabname);
+			*rcontext = strdup(context_list[0]);
+			freeconary(context_list);
+			return 0;
+		}
+	}
+
+	/*
+	* 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 = string_to_security_class("file");
+	if (!tclass) {
+		log_it(name, getpid(), "Failed to translate security class file", tabname);
+		freeconary(context_list);
+		if (security_deny_unknown() == 0) {
+			return 0;
+		} else {
+			return -1;
+		}
+	}
+
+	access_vector_t bit = string_to_av_perm(tclass, "entrypoint");
+	if (!bit) {
+		log_it(name, getpid(), "Failed to translate av perm entrypoint", tabname);
+		freeconary(context_list);
+		if (security_deny_unknown() == 0) {
+			return 0;
+		} else {
+			return -1;
+		}
+	}
+
+	for (i = 0; i < list_count; i++) {
+		retval = security_compute_av(context_list[i],
+						 file_context,
+						 tclass,
+						 bit,
+						 &avd);
+		if(!retval && ((bit & avd.allowed) == bit)) {
+			*rcontext = strdup(context_list[i]);
+			freecon(file_context);
+			freeconary(context_list);
+			return 0;
+		}
+	}
+	freecon(file_context);
+	if (security_getenforce() > 0) {
+		log_it(name, getpid(), "ENTRYPOINT FAILED", tabname);
+		freeconary(context_list);
+		return -1;
+	} else {
+		log_it(name, getpid(), "ENTRYPOINT FAILED but SELinux in permissive mode, continuing", tabname);
+		*rcontext = strdup(context_list[0]);
+		freeconary(context_list);
+	}
+	return 0;
+}
+#endif
+
 
 void
 free_user(u)
@@ -37,22 +164,28 @@ free_user(u)
 		ne = e->next;
 		free_entry(e);
 	}
+#ifdef WITH_SELINUX
+	if (u->scontext)
+		freecon(u->scontext);
+#endif
 	free(u);
 }
 
 
 user *
-load_user(crontab_fd, pw, name)
+load_user(crontab_fd, pw, uname, fname, tabname)
 	int		crontab_fd;
 	struct passwd	*pw;		/* NULL implies syscrontab */
-	char		*name;
+	char		*uname;
+	char		*fname;
+	char		*tabname;
 {
 	char	envstr[MAX_ENVSTR];
 	FILE	*file;
 	user	*u;
 	entry	*e;
 	int	status;
-	char	**envp, **tenvp;
+	char	**envp = NULL, **tenvp;
 
 	if (!(file = fdopen(crontab_fd, "r"))) {
 		perror("fdopen on crontab_fd in load_user");
@@ -67,13 +200,30 @@ load_user(crontab_fd, pw, name)
 		errno = ENOMEM;
 		return NULL;
 	}
-	if ((u->name = strdup(name)) == NULL) {
+	if ((u->name = strdup(fname)) == NULL) {
 		free(u);
 		errno = ENOMEM;
 		return NULL;
 	}
 	u->crontab = NULL;
 
+#ifdef WITH_SELINUX
+	u->scontext = NULL;
+	if (is_selinux_enabled() > 0) {
+		char *sname=uname;
+		if (pw==NULL) {
+			sname=NULL;
+		}
+		if (get_security_context(sname, crontab_fd,
+					&u->scontext, tabname) != 0 ) {
+			u->scontext = NULL;
+			free_user(u);
+			u = NULL;
+			goto done;
+		}
+	}
+#endif
+
 	/* 
 	 * init environment.  this will be copied/augmented for each entry.
 	 */
Index: cron/cron.8
===================================================================
--- cron.orig/cron.8
+++ cron/cron.8
@@ -118,6 +118,8 @@ Support for /etc/cron.{hourly,daily,week
 .IP \(em
 PAM support,
 .IP \(em
+SELinux support,
+.IP \(em
 Debian-specific file locations and commands,
 .IP \(em
 Debian-specific configuration (/etc/default/cron),
