2017-05-08 15:30:03 +02:00
|
|
|
/* security.c
|
|
|
|
*
|
|
|
|
* Implement Red Hat crond security context transitions
|
|
|
|
*
|
|
|
|
* Jason Vas Dias <jvdias@redhat.com> January 2006
|
|
|
|
*
|
|
|
|
* Copyright(C) Red Hat Inc., 2006
|
|
|
|
*
|
|
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
|
|
* copyright notice and this permission notice appear in all copies.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
|
|
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
|
|
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
|
|
|
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
*/
|
|
|
|
|
2019-08-06 18:08:05 +02:00
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
#include <errno.h>
|
|
|
|
#include <grp.h>
|
|
|
|
#include <pwd.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include "cronie_common.h"
|
|
|
|
#include "funcs.h"
|
|
|
|
#include "globals.h"
|
|
|
|
#include "macros.h"
|
|
|
|
|
|
|
|
#ifdef WITH_PAM
|
|
|
|
# include <security/pam_appl.h>
|
|
|
|
#endif
|
2017-05-08 15:30:03 +02:00
|
|
|
|
|
|
|
#ifdef WITH_SELINUX
|
|
|
|
# include <selinux/selinux.h>
|
|
|
|
# include <selinux/context.h>
|
|
|
|
# include <selinux/get_context_list.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef WITH_AUDIT
|
|
|
|
# include <libaudit.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef WITH_PAM
|
|
|
|
static pam_handle_t *pamh = NULL;
|
|
|
|
static int pam_session_opened = 0; //global for open session
|
|
|
|
|
|
|
|
static int
|
|
|
|
cron_conv(int num_msg, const struct pam_message **msgm,
|
2019-08-06 18:08:05 +02:00
|
|
|
struct pam_response **response ATTRIBUTE_UNUSED,
|
|
|
|
void *appdata_ptr ATTRIBUTE_UNUSED)
|
2017-05-08 15:30:03 +02:00
|
|
|
{
|
2019-08-06 18:08:05 +02:00
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < num_msg; i++) {
|
|
|
|
switch (msgm[i]->msg_style) {
|
|
|
|
case PAM_ERROR_MSG:
|
|
|
|
case PAM_TEXT_INFO:
|
|
|
|
if (msgm[i]->msg != NULL) {
|
|
|
|
log_it("CRON", getpid(), "pam_message", msgm[i]->msg, 0);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return (0);
|
2017-05-08 15:30:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static const struct pam_conv conv = {
|
|
|
|
cron_conv, NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
static int cron_open_pam_session(struct passwd *pw);
|
|
|
|
|
|
|
|
# define PAM_FAIL_CHECK if (retcode != PAM_SUCCESS) { \
|
|
|
|
log_it(pw->pw_name, getpid(), "PAM ERROR", pam_strerror(pamh, retcode), 0); \
|
|
|
|
if (pamh != NULL) { \
|
|
|
|
if (pam_session_opened != 0) \
|
|
|
|
pam_close_session(pamh, PAM_SILENT); \
|
|
|
|
pam_end(pamh, retcode); \
|
2019-08-06 18:08:05 +02:00
|
|
|
pamh = NULL; \
|
2017-05-08 15:30:03 +02:00
|
|
|
} \
|
|
|
|
return(retcode); }
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static char **build_env(char **cronenv);
|
|
|
|
|
|
|
|
#ifdef WITH_SELINUX
|
|
|
|
static int cron_change_selinux_range(user * u, security_context_t ucontext);
|
|
|
|
static int cron_get_job_range(user * u, security_context_t * ucontextp,
|
|
|
|
char **jobenv);
|
|
|
|
#endif
|
|
|
|
|
2019-08-06 18:08:05 +02:00
|
|
|
void cron_restore_default_security_context(void) {
|
2017-05-08 15:30:03 +02:00
|
|
|
#ifdef WITH_SELINUX
|
2019-08-06 18:08:05 +02:00
|
|
|
if (is_selinux_enabled() <= 0)
|
|
|
|
return;
|
|
|
|
if (setexeccon(NULL) < 0)
|
|
|
|
log_it("CRON", getpid(), "ERROR",
|
|
|
|
"failed to restore SELinux context", 0);
|
2017-05-08 15:30:03 +02:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2019-08-06 18:08:05 +02:00
|
|
|
int cron_set_job_security_context(entry *e, user *u ATTRIBUTE_UNUSED,
|
|
|
|
char ***jobenv) {
|
2017-05-08 15:30:03 +02:00
|
|
|
time_t minutely_time = 0;
|
|
|
|
#ifdef WITH_PAM
|
|
|
|
int ret;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if ((e->flags & MIN_STAR) == MIN_STAR) {
|
|
|
|
/* "minute-ly" job: Every minute for given hour/dow/month/dom.
|
|
|
|
* Ensure that these jobs never run in the same minute:
|
|
|
|
*/
|
2021-08-09 15:08:54 +02:00
|
|
|
minutely_time = time(NULL);
|
2023-07-01 12:15:55 +02:00
|
|
|
Debug(DSCH, ("Minute-ly job. Recording time %lld\n", (long long)minutely_time));
|
2017-05-08 15:30:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef WITH_PAM
|
2019-08-06 18:08:05 +02:00
|
|
|
/* PAM is called only for non-root users or non-system crontab */
|
|
|
|
if ((!u->system || e->pwd->pw_uid != 0) && (ret = cron_start_pam(e->pwd)) != 0) {
|
2017-05-08 15:30:03 +02:00
|
|
|
log_it(e->pwd->pw_name, getpid(), "FAILED to authorize user with PAM",
|
|
|
|
pam_strerror(pamh, ret), 0);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef WITH_SELINUX
|
|
|
|
/* we must get the crontab context BEFORE changing user, else
|
|
|
|
* we'll not be permitted to read the cron spool directory :-)
|
|
|
|
*/
|
|
|
|
security_context_t ucontext = 0;
|
|
|
|
|
2019-08-06 18:08:05 +02:00
|
|
|
if (cron_get_job_range(u, &ucontext, e->envp) < OK) {
|
2017-05-08 15:30:03 +02:00
|
|
|
log_it(e->pwd->pw_name, getpid(), "ERROR",
|
|
|
|
"failed to get SELinux context", 0);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cron_change_selinux_range(u, ucontext) != 0) {
|
|
|
|
log_it(e->pwd->pw_name, getpid(), "ERROR",
|
|
|
|
"failed to change SELinux context", 0);
|
|
|
|
if (ucontext)
|
|
|
|
freecon(ucontext);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (ucontext)
|
|
|
|
freecon(ucontext);
|
|
|
|
#endif
|
|
|
|
#ifdef WITH_PAM
|
2019-08-06 18:08:05 +02:00
|
|
|
if (pamh != NULL && (ret = cron_open_pam_session(e->pwd)) != 0) {
|
2017-05-08 15:30:03 +02:00
|
|
|
log_it(e->pwd->pw_name, getpid(),
|
|
|
|
"FAILED to open PAM security session", pam_strerror(pamh, ret), 0);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (cron_change_groups(e->pwd) != 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2019-08-06 18:08:05 +02:00
|
|
|
*jobenv = build_env(e->envp);
|
|
|
|
|
2021-08-09 15:08:54 +02:00
|
|
|
time_t job_run_time = time(NULL);
|
2017-05-08 15:30:03 +02:00
|
|
|
|
|
|
|
if ((minutely_time > 0) && ((job_run_time / 60) != (minutely_time / 60))) {
|
|
|
|
/* if a per-minute job is delayed into the next minute
|
|
|
|
* (eg. by network authentication method timeouts), skip it.
|
|
|
|
*/
|
|
|
|
struct tm tmS, tmN;
|
|
|
|
char buf[256];
|
|
|
|
|
|
|
|
localtime_r(&job_run_time, &tmN);
|
|
|
|
localtime_r(&minutely_time, &tmS);
|
|
|
|
|
|
|
|
snprintf(buf, sizeof (buf),
|
|
|
|
"Job execution of per-minute job scheduled for "
|
|
|
|
"%.2u:%.2u delayed into subsequent minute %.2u:%.2u. Skipping job run.",
|
|
|
|
tmS.tm_hour, tmS.tm_min, tmN.tm_hour, tmN.tm_min);
|
|
|
|
log_it(e->pwd->pw_name, getpid(), "INFO", buf, 0);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-08-06 18:08:05 +02:00
|
|
|
#if defined(WITH_PAM)
|
2017-05-08 15:30:03 +02:00
|
|
|
int cron_start_pam(struct passwd *pw) {
|
|
|
|
int retcode = 0;
|
|
|
|
|
|
|
|
retcode = pam_start("crond", pw->pw_name, &conv, &pamh);
|
|
|
|
PAM_FAIL_CHECK;
|
|
|
|
retcode = pam_set_item(pamh, PAM_TTY, "cron");
|
|
|
|
PAM_FAIL_CHECK;
|
|
|
|
retcode = pam_acct_mgmt(pamh, PAM_SILENT);
|
|
|
|
PAM_FAIL_CHECK;
|
|
|
|
retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED | PAM_SILENT);
|
|
|
|
PAM_FAIL_CHECK;
|
|
|
|
|
|
|
|
return retcode;
|
|
|
|
}
|
2019-08-06 18:08:05 +02:00
|
|
|
#endif
|
2017-05-08 15:30:03 +02:00
|
|
|
|
2019-08-06 18:08:05 +02:00
|
|
|
#if defined(WITH_PAM)
|
2017-05-08 15:30:03 +02:00
|
|
|
static int cron_open_pam_session(struct passwd *pw) {
|
2019-08-06 18:08:05 +02:00
|
|
|
int retcode;
|
2017-05-08 15:30:03 +02:00
|
|
|
|
|
|
|
retcode = pam_open_session(pamh, PAM_SILENT);
|
|
|
|
PAM_FAIL_CHECK;
|
|
|
|
if (retcode == PAM_SUCCESS)
|
|
|
|
pam_session_opened = 1;
|
|
|
|
|
|
|
|
return retcode;
|
|
|
|
}
|
2019-08-06 18:08:05 +02:00
|
|
|
#endif
|
2017-05-08 15:30:03 +02:00
|
|
|
|
|
|
|
void cron_close_pam(void) {
|
|
|
|
#if defined(WITH_PAM)
|
|
|
|
if (pam_session_opened != 0) {
|
|
|
|
pam_setcred(pamh, PAM_DELETE_CRED | PAM_SILENT);
|
|
|
|
pam_close_session(pamh, PAM_SILENT);
|
|
|
|
}
|
2019-08-06 18:08:05 +02:00
|
|
|
if (pamh != NULL) {
|
|
|
|
pam_end(pamh, PAM_SUCCESS);
|
|
|
|
pamh = NULL;
|
|
|
|
}
|
2017-05-08 15:30:03 +02:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
int cron_change_groups(struct passwd *pw) {
|
|
|
|
pid_t pid = getpid();
|
|
|
|
|
|
|
|
if (setgid(pw->pw_gid) != 0) {
|
|
|
|
log_it("CRON", pid, "ERROR", "setgid failed", errno);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (initgroups(pw->pw_name, pw->pw_gid) != 0) {
|
|
|
|
log_it("CRON", pid, "ERROR", "initgroups failed", errno);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if defined(WITH_PAM)
|
|
|
|
/* credentials may take form of supplementary groups so reinitialize
|
|
|
|
* them here */
|
2019-08-06 18:08:05 +02:00
|
|
|
if (pamh != NULL) {
|
|
|
|
pam_setcred(pamh, PAM_REINITIALIZE_CRED | PAM_SILENT);
|
|
|
|
}
|
2017-05-08 15:30:03 +02:00
|
|
|
#endif
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int cron_change_user_permanently(struct passwd *pw, char *homedir) {
|
|
|
|
if (setreuid(pw->pw_uid, pw->pw_uid) != 0) {
|
|
|
|
log_it("CRON", getpid(), "ERROR", "setreuid failed", errno);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (chdir(homedir) == -1) {
|
|
|
|
log_it("CRON", getpid(), "ERROR chdir failed", homedir, errno);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
log_close();
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-08-06 18:08:05 +02:00
|
|
|
#ifdef WITH_SELINUX
|
2017-05-08 15:30:03 +02:00
|
|
|
static int cron_authorize_context(security_context_t scontext,
|
|
|
|
security_context_t file_context) {
|
|
|
|
struct av_decision avd;
|
|
|
|
int retval;
|
|
|
|
security_class_t tclass;
|
|
|
|
access_vector_t bit;
|
|
|
|
|
|
|
|
tclass = string_to_security_class("file");
|
|
|
|
if (!tclass) {
|
|
|
|
log_it("CRON", getpid(), "ERROR", "Failed to translate security class file", errno);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
bit = string_to_av_perm(tclass, "entrypoint");
|
|
|
|
if (!bit) {
|
|
|
|
log_it("CRON", getpid(), "ERROR", "Failed to translate av perm entrypoint", errno);
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
retval = security_compute_av(scontext, file_context,
|
|
|
|
tclass, bit, &avd);
|
|
|
|
if (retval || ((bit & avd.allowed) != bit))
|
|
|
|
return 0;
|
|
|
|
return 1;
|
|
|
|
}
|
2019-08-06 18:08:05 +02:00
|
|
|
#endif
|
2017-05-08 15:30:03 +02:00
|
|
|
|
2019-08-06 18:08:05 +02:00
|
|
|
#ifdef WITH_SELINUX
|
2017-05-08 15:30:03 +02:00
|
|
|
static int cron_authorize_range(security_context_t scontext,
|
|
|
|
security_context_t ucontext) {
|
|
|
|
struct av_decision avd;
|
|
|
|
int retval;
|
|
|
|
security_class_t tclass;
|
|
|
|
access_vector_t bit;
|
|
|
|
|
|
|
|
tclass = string_to_security_class("context");
|
|
|
|
if (!tclass) {
|
|
|
|
log_it("CRON", getpid(), "ERROR", "Failed to translate security class context", errno);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
bit = string_to_av_perm(tclass, "contains");
|
|
|
|
if (!bit) {
|
|
|
|
log_it("CRON", getpid(), "ERROR", "Failed to translate av perm contains", errno);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
retval = security_compute_av(scontext, ucontext,
|
|
|
|
tclass, bit, &avd);
|
|
|
|
|
|
|
|
if (retval || ((bit & avd.allowed) != bit))
|
|
|
|
return 0;
|
|
|
|
return 1;
|
|
|
|
}
|
2019-08-06 18:08:05 +02:00
|
|
|
#endif
|
2017-05-08 15:30:03 +02:00
|
|
|
|
|
|
|
#if WITH_SELINUX
|
|
|
|
/* always uses u->scontext as the default process context, then changes the
|
|
|
|
level, and retuns it in ucontextp (or NULL otherwise) */
|
|
|
|
static int
|
|
|
|
cron_get_job_range(user * u, security_context_t * ucontextp, char **jobenv) {
|
|
|
|
char *range;
|
|
|
|
|
|
|
|
if (is_selinux_enabled() <= 0)
|
|
|
|
return 0;
|
2021-08-09 15:08:54 +02:00
|
|
|
if (ucontextp == NULL)
|
2017-05-08 15:30:03 +02:00
|
|
|
return -1;
|
|
|
|
|
2021-08-09 15:08:54 +02:00
|
|
|
*ucontextp = NULL;
|
2017-05-08 15:30:03 +02:00
|
|
|
|
2021-08-09 15:08:54 +02:00
|
|
|
if ((range = env_get("MLS_LEVEL", jobenv)) != NULL) {
|
2017-05-08 15:30:03 +02:00
|
|
|
context_t ccon;
|
|
|
|
if (!(ccon = context_new(u->scontext))) {
|
|
|
|
log_it(u->name, getpid(), "context_new FAILED for MLS_LEVEL",
|
|
|
|
range, 0);
|
|
|
|
context_free(ccon);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (context_range_set(ccon, range)) {
|
|
|
|
log_it(u->name, getpid(),
|
|
|
|
"context_range_set FAILED for MLS_LEVEL", range, 0);
|
|
|
|
context_free(ccon);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(*ucontextp = context_str(ccon))) {
|
|
|
|
log_it(u->name, getpid(), "context_str FAILED for MLS_LEVEL",
|
|
|
|
range, 0);
|
|
|
|
context_free(ccon);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(*ucontextp = strdup(*ucontextp))) {
|
|
|
|
log_it(u->name, getpid(), "strdup FAILED for MLS_LEVEL", range, 0);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
context_free(ccon);
|
|
|
|
}
|
|
|
|
else if (!u->scontext) {
|
|
|
|
/* cron_change_selinux_range() deals with this */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else if (!(*ucontextp = strdup(u->scontext))) {
|
|
|
|
log_it(u->name, getpid(), "strdup FAILED for MLS_LEVEL", range, 0);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef WITH_SELINUX
|
|
|
|
static int cron_change_selinux_range(user * u, security_context_t ucontext) {
|
|
|
|
char *msg = NULL;
|
|
|
|
|
|
|
|
if (is_selinux_enabled() <= 0)
|
|
|
|
return 0;
|
|
|
|
|
2021-08-09 15:08:54 +02:00
|
|
|
if (u->scontext == NULL) {
|
2017-05-08 15:30:03 +02:00
|
|
|
if (security_getenforce() > 0) {
|
|
|
|
log_it(u->name, getpid(), "NULL security context for user", "", 0);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
log_it(u->name, getpid(),
|
|
|
|
"NULL security context for user, "
|
|
|
|
"but SELinux in permissive mode, continuing", "", 0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-06 18:08:05 +02:00
|
|
|
if (!ucontext || strcmp(u->scontext, ucontext)) {
|
2017-05-08 15:30:03 +02:00
|
|
|
if (!cron_authorize_range(u->scontext, ucontext)) {
|
|
|
|
if (security_getenforce() > 0) {
|
|
|
|
# ifdef WITH_AUDIT
|
|
|
|
if (asprintf(&msg,
|
|
|
|
"cron: Unauthorized MLS range acct=%s new_scontext=%s old_scontext=%s",
|
|
|
|
u->name, (char *) ucontext, u->scontext) >= 0) {
|
|
|
|
int audit_fd = audit_open();
|
|
|
|
audit_log_user_message(audit_fd, AUDIT_USER_ROLE_CHANGE,
|
|
|
|
msg, NULL, NULL, NULL, 0);
|
|
|
|
close(audit_fd);
|
|
|
|
free(msg);
|
|
|
|
}
|
|
|
|
# endif
|
|
|
|
if (asprintf
|
|
|
|
(&msg, "Unauthorized range in %s for user range in %s",
|
|
|
|
(char *) ucontext, u->scontext) >= 0) {
|
|
|
|
log_it(u->name, getpid(), "ERROR", msg, 0);
|
|
|
|
free(msg);
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (asprintf
|
|
|
|
(&msg,
|
|
|
|
"Unauthorized range in %s for user range in %s,"
|
|
|
|
" but SELinux in permissive mod, continuing",
|
|
|
|
(char *) ucontext, u->scontext) >= 0) {
|
|
|
|
log_it(u->name, getpid(), "WARNING", msg, 0);
|
|
|
|
free(msg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-06 18:08:05 +02:00
|
|
|
if (setexeccon(ucontext) < 0) {
|
2017-05-08 15:30:03 +02:00
|
|
|
if (security_getenforce() > 0) {
|
|
|
|
if (asprintf
|
|
|
|
(&msg, "Could not set exec or keycreate context to %s for user",
|
|
|
|
(char *) ucontext) >= 0) {
|
|
|
|
log_it(u->name, getpid(), "ERROR", msg, 0);
|
|
|
|
free(msg);
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (asprintf
|
|
|
|
(&msg,
|
2019-08-06 18:08:05 +02:00
|
|
|
"Could not set exec context to %s for user,"
|
2017-05-08 15:30:03 +02:00
|
|
|
" but SELinux in permissive mode, continuing",
|
|
|
|
(char *) ucontext) >= 0) {
|
|
|
|
log_it(u->name, getpid(), "WARNING", msg, 0);
|
|
|
|
free(msg);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2019-08-06 18:08:05 +02:00
|
|
|
#ifdef WITH_SELINUX
|
2017-05-08 15:30:03 +02:00
|
|
|
int
|
|
|
|
get_security_context(const char *name, int crontab_fd,
|
|
|
|
security_context_t * rcontext, const char *tabname) {
|
|
|
|
security_context_t scontext = NULL;
|
|
|
|
security_context_t file_context = NULL;
|
2019-08-06 18:08:05 +02:00
|
|
|
security_context_t rawcontext=NULL;
|
|
|
|
context_t current_context = NULL;
|
|
|
|
int retval;
|
|
|
|
char *current_context_str = NULL;
|
2017-05-08 15:30:03 +02:00
|
|
|
char *seuser = NULL;
|
|
|
|
char *level = NULL;
|
|
|
|
|
|
|
|
*rcontext = NULL;
|
|
|
|
|
|
|
|
if (is_selinux_enabled() <= 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (name != NULL) {
|
|
|
|
if (getseuserbyname(name, &seuser, &level) < 0) {
|
|
|
|
log_it(name, getpid(), "getseuserbyname FAILED", name, 0);
|
2019-08-06 18:08:05 +02:00
|
|
|
return security_getenforce() > 0 ? -1 : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
retval = get_default_context_with_level(seuser, level, NULL, &scontext);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
const char *current_user, *current_role;
|
|
|
|
if (getcon(¤t_context_str) < 0) {
|
|
|
|
log_it(name, getpid(), "getcon FAILED", "", 0);
|
2017-05-08 15:30:03 +02:00
|
|
|
return (security_getenforce() > 0);
|
|
|
|
}
|
2019-08-06 18:08:05 +02:00
|
|
|
|
|
|
|
current_context = context_new(current_context_str);
|
|
|
|
if (current_context == NULL) {
|
|
|
|
log_it(name, getpid(), "context_new FAILED", current_context_str, 0);
|
|
|
|
freecon(current_context_str);
|
|
|
|
return (security_getenforce() > 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
current_user = context_user_get(current_context);
|
|
|
|
current_role = context_role_get(current_context);
|
|
|
|
retval = get_default_context_with_rolelevel(current_user, current_role, level, NULL, &scontext);
|
|
|
|
|
|
|
|
freecon(current_context_str);
|
|
|
|
context_free(current_context);
|
2017-05-08 15:30:03 +02:00
|
|
|
}
|
|
|
|
|
2019-08-06 18:08:05 +02:00
|
|
|
if (selinux_trans_to_raw_context(scontext, &rawcontext) == 0) {
|
|
|
|
freecon(scontext);
|
|
|
|
scontext = rawcontext;
|
|
|
|
}
|
2017-05-08 15:30:03 +02:00
|
|
|
free(seuser);
|
|
|
|
free(level);
|
|
|
|
if (retval) {
|
|
|
|
if (security_getenforce() > 0) {
|
|
|
|
log_it(name, getpid(), "No SELinux security context", tabname, 0);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
log_it(name, getpid(),
|
|
|
|
"No security context but SELinux in permissive mode, continuing",
|
|
|
|
tabname, 0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fgetfilecon(crontab_fd, &file_context) < OK) {
|
|
|
|
if (security_getenforce() > 0) {
|
|
|
|
log_it(name, getpid(), "getfilecon FAILED", tabname, 0);
|
|
|
|
freecon(scontext);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
log_it(name, getpid(),
|
|
|
|
"getfilecon FAILED but SELinux in permissive mode, continuing",
|
|
|
|
tabname, 0);
|
|
|
|
*rcontext = scontext;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!cron_authorize_context(scontext, file_context)) {
|
|
|
|
char *msg=NULL;
|
|
|
|
if (asprintf(&msg,
|
|
|
|
"Unauthorized SELinux context=%s file_context=%s", (char *) scontext, file_context) >= 0) {
|
|
|
|
log_it(name, getpid(), msg, tabname, 0);
|
|
|
|
free(msg);
|
|
|
|
} else {
|
|
|
|
log_it(name, getpid(), "Unauthorized SELinux context", tabname, 0);
|
|
|
|
}
|
|
|
|
freecon(scontext);
|
|
|
|
freecon(file_context);
|
|
|
|
if (security_getenforce() > 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
log_it(name, getpid(),
|
|
|
|
"SELinux in permissive mode, continuing",
|
|
|
|
tabname, 0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
freecon(file_context);
|
|
|
|
|
|
|
|
*rcontext = scontext;
|
|
|
|
return 0;
|
|
|
|
}
|
2019-08-06 18:08:05 +02:00
|
|
|
#endif
|
2017-05-08 15:30:03 +02:00
|
|
|
|
|
|
|
#ifdef WITH_SELINUX
|
2019-08-06 18:08:05 +02:00
|
|
|
void free_security_context(security_context_t * scontext) {
|
2017-05-08 15:30:03 +02:00
|
|
|
if (*scontext != NULL) {
|
|
|
|
freecon(*scontext);
|
2021-08-09 15:08:54 +02:00
|
|
|
*scontext = NULL;
|
2017-05-08 15:30:03 +02:00
|
|
|
}
|
|
|
|
}
|
2019-08-06 18:08:05 +02:00
|
|
|
#endif
|
2017-05-08 15:30:03 +02:00
|
|
|
|
|
|
|
#ifdef WITH_SELINUX
|
2019-08-06 18:08:05 +02:00
|
|
|
int crontab_security_access(void) {
|
2017-05-08 15:30:03 +02:00
|
|
|
int selinux_check_passwd_access = -1;
|
|
|
|
if (is_selinux_enabled() > 0) {
|
|
|
|
security_context_t user_context;
|
|
|
|
if (getprevcon_raw(&user_context) == 0) {
|
|
|
|
security_class_t passwd_class;
|
|
|
|
access_vector_t crontab_bit;
|
|
|
|
struct av_decision avd;
|
|
|
|
int retval = 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");
|
|
|
|
retval = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (retval == 0) {
|
|
|
|
crontab_bit = string_to_av_perm(passwd_class, "crontab");
|
|
|
|
if (crontab_bit == 0) {
|
|
|
|
fprintf(stderr, "Security av permission \"crontab\" is not defined in the SELinux policy.\n");
|
|
|
|
retval = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (retval == 0)
|
|
|
|
retval = security_compute_av_raw(user_context,
|
|
|
|
user_context, passwd_class,
|
|
|
|
crontab_bit, &avd);
|
|
|
|
|
|
|
|
if ((retval == 0) && ((crontab_bit & avd.allowed) == crontab_bit)) {
|
|
|
|
selinux_check_passwd_access = 0;
|
|
|
|
}
|
|
|
|
freecon(user_context);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (selinux_check_passwd_access != 0 && security_getenforce() == 0)
|
|
|
|
selinux_check_passwd_access = 0;
|
|
|
|
|
|
|
|
return selinux_check_passwd_access;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2019-08-06 18:08:05 +02:00
|
|
|
#endif
|
2017-05-08 15:30:03 +02:00
|
|
|
|
|
|
|
/* Build up the job environment from the PAM environment plus the
|
|
|
|
* crontab environment
|
|
|
|
*/
|
|
|
|
static char **build_env(char **cronenv) {
|
2019-08-06 18:08:05 +02:00
|
|
|
char **jobenv;
|
2017-05-08 15:30:03 +02:00
|
|
|
#ifdef WITH_PAM
|
|
|
|
char *cronvar;
|
|
|
|
int count = 0;
|
2019-08-06 18:08:05 +02:00
|
|
|
|
|
|
|
if (pamh == NULL || (jobenv=pam_getenvlist(pamh)) == NULL) {
|
|
|
|
#endif
|
|
|
|
jobenv = env_copy(cronenv);
|
|
|
|
if (jobenv == NULL)
|
|
|
|
log_it("CRON", getpid(),
|
|
|
|
"ERROR", "Initialization of cron environment variables failed", 0);
|
|
|
|
return jobenv;
|
|
|
|
#ifdef WITH_PAM
|
|
|
|
}
|
2017-05-08 15:30:03 +02:00
|
|
|
|
|
|
|
/* Now add the cron environment variables. Since env_set()
|
|
|
|
* overwrites existing variables, this will let cron's
|
|
|
|
* environment settings override pam's */
|
|
|
|
|
|
|
|
while ((cronvar = cronenv[count++])) {
|
|
|
|
if (!(jobenv = env_set(jobenv, cronvar))) {
|
|
|
|
log_it("CRON", getpid(),
|
|
|
|
"Setting Cron environment variable failed", cronvar, 0);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return jobenv;
|
|
|
|
#endif
|
|
|
|
}
|
2021-08-09 15:08:54 +02:00
|
|
|
|
|
|
|
/* int in_file(const char *string, FILE *file, int error)
|
|
|
|
* return TRUE if one of the lines in file matches string exactly,
|
|
|
|
* FALSE if no lines match, and error on error.
|
|
|
|
*/
|
|
|
|
static int in_file(const char *string, FILE * file, int error) {
|
|
|
|
char line[MAX_TEMPSTR];
|
|
|
|
char *endp;
|
|
|
|
|
|
|
|
if (fseek(file, 0L, SEEK_SET))
|
|
|
|
return (error);
|
|
|
|
while (fgets(line, MAX_TEMPSTR, file)) {
|
|
|
|
if (line[0] != '\0') {
|
|
|
|
endp = &line[strlen(line) - 1];
|
|
|
|
if (*endp != '\n')
|
|
|
|
return (error);
|
|
|
|
*endp = '\0';
|
|
|
|
if (0 == strcmp(line, string))
|
|
|
|
return (TRUE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (ferror(file))
|
|
|
|
return (error);
|
|
|
|
return (FALSE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* int allowed(const char *username, const char *allow_file, const char *deny_file)
|
|
|
|
* returns TRUE if (allow_file exists and user is listed)
|
|
|
|
* or (deny_file exists and user is NOT listed).
|
|
|
|
* root is always allowed.
|
|
|
|
*/
|
|
|
|
int allowed(const char *username, const char *allow_file,
|
|
|
|
const char *deny_file) {
|
|
|
|
FILE *fp;
|
|
|
|
int isallowed;
|
|
|
|
char buf[128];
|
|
|
|
|
|
|
|
if (getuid() == 0)
|
|
|
|
return TRUE;
|
|
|
|
isallowed = FALSE;
|
|
|
|
if ((fp = fopen(allow_file, "r")) != NULL) {
|
|
|
|
isallowed = in_file(username, fp, FALSE);
|
|
|
|
fclose(fp);
|
|
|
|
if ((getuid() == 0) && (!isallowed)) {
|
|
|
|
snprintf(buf, sizeof (buf),
|
|
|
|
"root used -u for user %s not in cron.allow", username);
|
|
|
|
log_it("crontab", getpid(), "warning", buf, 0);
|
|
|
|
isallowed = TRUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if ((fp = fopen(deny_file, "r")) != NULL) {
|
|
|
|
isallowed = !in_file(username, fp, FALSE);
|
|
|
|
fclose(fp);
|
|
|
|
if ((getuid() == 0) && (!isallowed)) {
|
|
|
|
snprintf(buf, sizeof (buf),
|
|
|
|
"root used -u for user %s in cron.deny", username);
|
|
|
|
log_it("crontab", getpid(), "warning", buf, 0);
|
|
|
|
isallowed = TRUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#ifdef WITH_AUDIT
|
|
|
|
if (isallowed == FALSE) {
|
|
|
|
int audit_fd = audit_open();
|
|
|
|
audit_log_user_message(audit_fd, AUDIT_USER_START, "cron deny",
|
|
|
|
NULL, NULL, NULL, 0);
|
|
|
|
close(audit_fd);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
return (isallowed);
|
|
|
|
}
|
|
|
|
|