Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F102704903
D47633.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
95 KB
Referenced Files
None
Subscribers
None
D47633.diff
View Options
diff --git a/lib/libsys/Symbol.sys.map b/lib/libsys/Symbol.sys.map
--- a/lib/libsys/Symbol.sys.map
+++ b/lib/libsys/Symbol.sys.map
@@ -380,6 +380,7 @@
FBSD_1.8 {
getrlimitusage;
kcmp;
+ setcred;
};
FBSDprivate_1.0 {
diff --git a/lib/libsys/_libsys.h b/lib/libsys/_libsys.h
--- a/lib/libsys/_libsys.h
+++ b/lib/libsys/_libsys.h
@@ -463,6 +463,7 @@
typedef int (__sys_timerfd_settime_t)(int, int, const struct itimerspec *, struct itimerspec *);
typedef int (__sys_kcmp_t)(pid_t, pid_t, int, uintptr_t, uintptr_t);
typedef int (__sys_getrlimitusage_t)(u_int, int, rlim_t *);
+typedef int (__sys_setcred_t)(u_int, const void *, size_t);
void __sys_exit(int rval);
int __sys_fork(void);
@@ -863,6 +864,7 @@
int __sys_timerfd_settime(int fd, int flags, const struct itimerspec * new_value, struct itimerspec * old_value);
int __sys_kcmp(pid_t pid1, pid_t pid2, int type, uintptr_t idx1, uintptr_t idx2);
int __sys_getrlimitusage(u_int which, int flags, rlim_t * res);
+int __sys_setcred(u_int flags, const void * wcred, size_t size);
__END_DECLS
#endif /* __LIBSYS_H_ */
diff --git a/lib/libsys/syscalls.map b/lib/libsys/syscalls.map
--- a/lib/libsys/syscalls.map
+++ b/lib/libsys/syscalls.map
@@ -805,4 +805,6 @@
__sys_kcmp;
_getrlimitusage;
__sys_getrlimitusage;
+ _setcred;
+ __sys_setcred;
};
diff --git a/sys/bsm/audit_kevents.h b/sys/bsm/audit_kevents.h
--- a/sys/bsm/audit_kevents.h
+++ b/sys/bsm/audit_kevents.h
@@ -662,6 +662,7 @@
#define AUE_AIO_READV 43268 /* FreeBSD-specific. */
#define AUE_FSPACECTL 43269 /* FreeBSD-specific. */
#define AUE_TIMERFD 43270 /* FreeBSD/Linux. */
+#define AUE_SETCRED 43271 /* FreeBSD-specific. */
/*
* Darwin BSM uses a number of AUE_O_* definitions, which are aliased to the
diff --git a/sys/compat/freebsd32/freebsd32_syscall.h b/sys/compat/freebsd32/freebsd32_syscall.h
--- a/sys/compat/freebsd32/freebsd32_syscall.h
+++ b/sys/compat/freebsd32/freebsd32_syscall.h
@@ -508,4 +508,5 @@
#define FREEBSD32_SYS_freebsd32_timerfd_settime 587
#define FREEBSD32_SYS_kcmp 588
#define FREEBSD32_SYS_getrlimitusage 589
-#define FREEBSD32_SYS_MAXSYSCALL 590
+#define FREEBSD32_SYS_setcred 590
+#define FREEBSD32_SYS_MAXSYSCALL 591
diff --git a/sys/compat/freebsd32/freebsd32_syscalls.c b/sys/compat/freebsd32/freebsd32_syscalls.c
--- a/sys/compat/freebsd32/freebsd32_syscalls.c
+++ b/sys/compat/freebsd32/freebsd32_syscalls.c
@@ -595,4 +595,5 @@
"freebsd32_timerfd_settime", /* 587 = freebsd32_timerfd_settime */
"kcmp", /* 588 = kcmp */
"getrlimitusage", /* 589 = getrlimitusage */
+ "setcred", /* 590 = setcred */
};
diff --git a/sys/compat/freebsd32/freebsd32_sysent.c b/sys/compat/freebsd32/freebsd32_sysent.c
--- a/sys/compat/freebsd32/freebsd32_sysent.c
+++ b/sys/compat/freebsd32/freebsd32_sysent.c
@@ -657,4 +657,5 @@
{ .sy_narg = AS(freebsd32_timerfd_settime_args), .sy_call = (sy_call_t *)freebsd32_timerfd_settime, .sy_auevent = AUE_TIMERFD, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 587 = freebsd32_timerfd_settime */
{ .sy_narg = AS(kcmp_args), .sy_call = (sy_call_t *)sys_kcmp, .sy_auevent = AUE_NULL, .sy_flags = 0, .sy_thrcnt = SY_THR_STATIC }, /* 588 = kcmp */
{ .sy_narg = AS(getrlimitusage_args), .sy_call = (sy_call_t *)sys_getrlimitusage, .sy_auevent = AUE_NULL, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 589 = getrlimitusage */
+ { .sy_narg = AS(setcred_args), .sy_call = (sy_call_t *)sys_setcred, .sy_auevent = AUE_SETCRED, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 590 = setcred */
};
diff --git a/sys/compat/freebsd32/freebsd32_systrace_args.c b/sys/compat/freebsd32/freebsd32_systrace_args.c
--- a/sys/compat/freebsd32/freebsd32_systrace_args.c
+++ b/sys/compat/freebsd32/freebsd32_systrace_args.c
@@ -3378,6 +3378,15 @@
*n_args = 3;
break;
}
+ /* setcred */
+ case 590: {
+ struct setcred_args *p = params;
+ uarg[a++] = p->flags; /* u_int */
+ uarg[a++] = (intptr_t)p->wcred; /* const void * */
+ uarg[a++] = p->size; /* size_t */
+ *n_args = 3;
+ break;
+ }
default:
*n_args = 0;
break;
@@ -9126,6 +9135,22 @@
break;
};
break;
+ /* setcred */
+ case 590:
+ switch (ndx) {
+ case 0:
+ p = "u_int";
+ break;
+ case 1:
+ p = "userland const void *";
+ break;
+ case 2:
+ p = "size_t";
+ break;
+ default:
+ break;
+ };
+ break;
default:
break;
};
@@ -11014,6 +11039,11 @@
if (ndx == 0 || ndx == 1)
p = "int";
break;
+ /* setcred */
+ case 590:
+ if (ndx == 0 || ndx == 1)
+ p = "int";
+ break;
default:
break;
};
diff --git a/sys/kern/init_sysent.c b/sys/kern/init_sysent.c
--- a/sys/kern/init_sysent.c
+++ b/sys/kern/init_sysent.c
@@ -656,4 +656,5 @@
{ .sy_narg = AS(timerfd_settime_args), .sy_call = (sy_call_t *)sys_timerfd_settime, .sy_auevent = AUE_TIMERFD, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 587 = timerfd_settime */
{ .sy_narg = AS(kcmp_args), .sy_call = (sy_call_t *)sys_kcmp, .sy_auevent = AUE_NULL, .sy_flags = 0, .sy_thrcnt = SY_THR_STATIC }, /* 588 = kcmp */
{ .sy_narg = AS(getrlimitusage_args), .sy_call = (sy_call_t *)sys_getrlimitusage, .sy_auevent = AUE_NULL, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 589 = getrlimitusage */
+ { .sy_narg = AS(setcred_args), .sy_call = (sy_call_t *)sys_setcred, .sy_auevent = AUE_SETCRED, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 590 = setcred */
};
diff --git a/sys/kern/kern_jail.c b/sys/kern/kern_jail.c
--- a/sys/kern/kern_jail.c
+++ b/sys/kern/kern_jail.c
@@ -3956,6 +3956,7 @@
* Allow jailed processes to manipulate process UNIX
* credentials in any way they see fit.
*/
+ case PRIV_CRED_SETCRED:
case PRIV_CRED_SETUID:
case PRIV_CRED_SETEUID:
case PRIV_CRED_SETGID:
diff --git a/sys/kern/kern_prot.c b/sys/kern/kern_prot.c
--- a/sys/kern/kern_prot.c
+++ b/sys/kern/kern_prot.c
@@ -73,6 +73,10 @@
#include <sys/syscallsubr.h>
#include <sys/sysctl.h>
+#ifdef MAC
+#include <security/mac/mac_syscalls.h>
+#endif
+
#include <vm/uma.h>
#ifdef REGRESSION
@@ -484,6 +488,303 @@
return (error);
}
+static int
+gidp_cmp(const void *p1, const void *p2)
+{
+ const gid_t g1 = *(const gid_t *)p1;
+ const gid_t g2 = *(const gid_t *)p2;
+
+ return ((g1 > g2) - (g1 < g2));
+}
+
+#ifndef _SYS_SYSPROTO_H_
+struct setcred_args {
+ u_int flags; /* Flags, including version. */
+ const void *wcred; /* Some setcred_vX structure. */
+ size_t size; /* Length of setcred_vX structure. */
+};
+#endif
+int
+sys_setcred(struct thread *td, struct setcred_args *uap)
+{
+ const u_int flags = uap->flags;
+ const void *const uwcred = uap->wcred;
+ const size_t size = uap->size;
+ struct setcred_v0 wcred;
+#ifdef MAC
+ struct mac mac;
+#endif
+ gid_t smallgroups[CRED_SMALLGROUPS_NB];
+ gid_t *groups = NULL;
+ int error;
+
+ /*
+ * As the only point of this wrapper function is to copyin() from
+ * userland, we only interpret the data pieces we need to perform this
+ * operation and defer further sanity checks to kern_setcred_vX().
+ */
+
+ /* There is only one version for now. */
+ if (SETCREDF_TO_VERSION(flags) != 0 || size != sizeof(wcred))
+ return (EINVAL);
+
+ error = copyin(uwcred, &wcred, sizeof(wcred));
+ if (error != 0)
+ return (error);
+
+ if (flags & SETCREDF_SUPP_GROUPS) {
+ /*
+ * Check for the limit of number of groups right now in order to
+ * limit the amount of bytes to copy.
+ */
+ if (wcred.sc_supp_groups_nb > ngroups_max)
+ return (EINVAL);
+
+ /*
+ * Since we are going to be copying the supplementary groups
+ * from userland, make room also for the effective GID right
+ * now, to avoid having to allocate and copy again the
+ * supplementary groups.
+ */
+ groups = wcred.sc_supp_groups_nb < CRED_SMALLGROUPS_NB ?
+ smallgroups : malloc((wcred.sc_supp_groups_nb + 1) *
+ sizeof(*groups), M_TEMP, M_WAITOK);
+
+ error = copyin(wcred.sc_supp_groups, groups + 1,
+ wcred.sc_supp_groups_nb * sizeof(*groups));
+ if (error != 0)
+ goto finish;
+ wcred.sc_supp_groups = groups + 1;
+ } else {
+ wcred.sc_supp_groups_nb = 0;
+ wcred.sc_supp_groups = NULL;
+ }
+
+#ifdef MAC
+ if (flags & SETCREDF_MAC_LABEL) {
+ error = mac_label_copyin(wcred.sc_label, &mac, NULL);
+ if (error != 0) {
+ wcred.sc_label = NULL;
+ goto finish;
+ }
+ wcred.sc_label = &mac;
+ } else
+ wcred.sc_label = NULL;
+#endif
+
+ error = kern_setcred_v0(td, flags, &wcred, groups);
+
+finish:
+ if (groups != NULL && groups != smallgroups)
+ free(groups, M_TEMP);
+#ifdef MAC
+ if (wcred.sc_label != NULL)
+ free_copied_label(wcred.sc_label);
+#endif
+ return (error);
+}
+
+/*
+ * CAUTION: This function normalizes groups in 'wcred'.
+ *
+ * If 'preallocated_groups' is non-NULL, it must be an already allocated array
+ * of size 'wcred->sc_supp_groups_nb + 1', with the supplementary groups
+ * starting at index 1, and 'wcred->sc_supp_groups' then must point to the first
+ * supplementary group.
+ */
+int
+kern_setcred_v0(struct thread *const td, const u_int flags,
+ struct setcred_v0 *const wcred, gid_t *preallocated_groups)
+{
+ struct proc *const p = td->td_proc;
+ struct ucred *new_cred, *old_cred, *to_free_cred;
+ struct uidinfo *uip = NULL, *ruip = NULL;
+#ifdef MAC
+ void *mac_set_proc_data = NULL;
+ bool proc_label_set = false;
+#endif
+ gid_t *groups = NULL;
+ gid_t smallgroups[CRED_SMALLGROUPS_NB];
+ int error;
+ bool cred_set;
+
+ MPASS(SETCREDF_TO_VERSION(flags) == 0);
+ /* Bail out on unrecognized flags. */
+ if (flags & ~SETCREDF_MASK)
+ return (EINVAL);
+
+ /*
+ * Part 1: We allocate and perform preparatory operations with no locks.
+ */
+
+ if (flags & SETCREDF_SUPP_GROUPS) {
+ if (wcred->sc_supp_groups_nb > ngroups_max)
+ return (EINVAL);
+ if (preallocated_groups != NULL) {
+ groups = preallocated_groups;
+ MPASS(preallocated_groups + 1 == wcred->sc_supp_groups);
+ } else {
+ groups = wcred->sc_supp_groups_nb < CRED_SMALLGROUPS_NB ?
+ smallgroups :
+ malloc((wcred->sc_supp_groups_nb + 1) *
+ sizeof(*groups), M_TEMP, M_WAITOK);
+ memcpy(groups + 1, wcred->sc_supp_groups,
+ wcred->sc_supp_groups_nb * sizeof(*groups));
+ }
+ }
+
+ if (flags & SETCREDF_MAC_LABEL) {
+#ifdef MAC
+ error = mac_set_proc_prepare(td, wcred->sc_label,
+ &mac_set_proc_data);
+ if (error != 0)
+ goto free_groups;
+#else
+ error = ENOTSUP;
+ goto free_groups;
+#endif
+ }
+
+ if (flags & SETCREDF_UID) {
+ AUDIT_ARG_EUID(wcred->sc_uid);
+ uip = uifind(wcred->sc_uid);
+ }
+ if (flags & SETCREDF_RUID) {
+ AUDIT_ARG_RUID(wcred->sc_ruid);
+ ruip = uifind(wcred->sc_ruid);
+ }
+ if (flags & SETCREDF_SVUID)
+ AUDIT_ARG_SUID(wcred->sc_svuid);
+
+ if (flags & SETCREDF_GID)
+ AUDIT_ARG_EGID(wcred->sc_gid);
+ if (flags & SETCREDF_RGID)
+ AUDIT_ARG_RGID(wcred->sc_rgid);
+ if (flags & SETCREDF_SVGID)
+ AUDIT_ARG_SGID(wcred->sc_svgid);
+ if (flags & SETCREDF_SUPP_GROUPS) {
+ int ngrp = wcred->sc_supp_groups_nb;
+
+ /*
+ * Output the raw supplementary groups array for better
+ * traceability.
+ */
+ AUDIT_ARG_GROUPSET(groups + 1, ngrp);
+ ++ngrp;
+ groups_normalize(&ngrp, groups);
+ wcred->sc_supp_groups_nb = ngrp - 1;
+ }
+
+ /*
+ * We first completely build the new credentials and only then pass them
+ * to MAC along with the old ones so that modules can check whether the
+ * requested transition is allowed.
+ */
+ new_cred = crget();
+ to_free_cred = new_cred;
+ if (flags & SETCREDF_SUPP_GROUPS)
+ crextend(new_cred, wcred->sc_supp_groups_nb + 1);
+
+#ifdef MAC
+ mac_cred_setcred_enter();
+#endif
+
+ /*
+ * Part 2: We grab the process lock as to have a stable view of its
+ * current credentials, and prepare a copy of them with the requested
+ * changes applied under that lock.
+ */
+
+ PROC_LOCK(p);
+ old_cred = crcopysafe(p, new_cred);
+
+ /*
+ * Change user IDs.
+ */
+ if (flags & SETCREDF_UID)
+ change_euid(new_cred, uip);
+ if (flags & SETCREDF_RUID)
+ change_ruid(new_cred, ruip);
+ if (flags & SETCREDF_SVUID)
+ change_svuid(new_cred, wcred->sc_svuid);
+
+ /*
+ * Change groups.
+ *
+ * crsetgroups_internal() changes both the effective and supplementary
+ * ones.
+ */
+ if (flags & SETCREDF_SUPP_GROUPS) {
+ groups[0] = flags & SETCREDF_GID ? wcred->sc_gid :
+ new_cred->cr_gid;
+ crsetgroups_internal(new_cred, wcred->sc_supp_groups_nb + 1,
+ groups);
+ } else if (flags & SETCREDF_GID)
+ change_egid(new_cred, wcred->sc_gid);
+ if (flags & SETCREDF_RGID)
+ change_rgid(new_cred, wcred->sc_rgid);
+ if (flags & SETCREDF_SVGID)
+ change_svgid(new_cred, wcred->sc_svgid);
+
+#ifdef MAC
+ /*
+ * Change the MAC label.
+ */
+ if (flags & SETCREDF_MAC_LABEL) {
+ error = mac_set_proc_core(td, new_cred, mac_set_proc_data);
+ if (error != 0)
+ goto unlock_finish;
+ proc_label_set = true;
+ }
+
+ /*
+ * MAC security modules checks.
+ */
+ error = mac_cred_check_setcred(flags, old_cred, new_cred);
+ if (error != 0)
+ goto unlock_finish;
+#endif
+ /*
+ * Privilege check.
+ */
+ error = priv_check_cred(old_cred, PRIV_CRED_SETCRED);
+ if (error != 0)
+ goto unlock_finish;
+
+ /*
+ * Set the new credentials, noting that they have changed.
+ */
+ cred_set = proc_set_cred_enforce_proc_lim(p, new_cred);
+ if (cred_set) {
+ setsugid(p);
+ to_free_cred = old_cred;
+ MPASS(error == 0);
+ } else
+ error = EAGAIN;
+
+unlock_finish:
+ PROC_UNLOCK(p);
+ /*
+ * Part 3: After releasing the process lock, we perform cleanups and
+ * finishing operations.
+ */
+
+#ifdef MAC
+ if (mac_set_proc_data != NULL)
+ mac_set_proc_finish(td, proc_label_set, mac_set_proc_data);
+ mac_cred_setcred_exit();
+#endif
+ crfree(to_free_cred);
+ if (uip != NULL)
+ uifree(uip);
+ if (ruip != NULL)
+ uifree(ruip);
+free_groups:
+ if (groups != preallocated_groups && groups != smallgroups)
+ free(groups, M_TEMP); /* Deals with 'groups' being NULL. */
+ return (error);
+}
+
/*
* Use the clause in B.4.2.2 that allows setuid/setgid to be 4.2/4.3BSD
* compatible. It says that setting the uid/gid to euid/egid is a special
@@ -859,15 +1160,6 @@
return (error);
}
-static int
-gidp_cmp(const void *p1, const void *p2)
-{
- const gid_t g1 = *(const gid_t *)p1;
- const gid_t g2 = *(const gid_t *)p2;
-
- return ((g1 > g2) - (g1 < g2));
-}
-
/*
* CAUTION: This function normalizes 'groups', possibly also changing the value
* of '*ngrpp' as a consequence.
diff --git a/sys/kern/syscalls.c b/sys/kern/syscalls.c
--- a/sys/kern/syscalls.c
+++ b/sys/kern/syscalls.c
@@ -595,4 +595,5 @@
"timerfd_settime", /* 587 = timerfd_settime */
"kcmp", /* 588 = kcmp */
"getrlimitusage", /* 589 = getrlimitusage */
+ "setcred", /* 590 = setcred */
};
diff --git a/sys/kern/syscalls.master b/sys/kern/syscalls.master
--- a/sys/kern/syscalls.master
+++ b/sys/kern/syscalls.master
@@ -3341,5 +3341,12 @@
_Out_ rlim_t *res
);
}
+590 AUE_SETCRED STD|CAPENABLED {
+ int setcred(
+ u_int flags,
+ _In_reads_bytes_(size) const void *wcred,
+ size_t size
+ );
+ }
; vim: syntax=off
diff --git a/sys/kern/systrace_args.c b/sys/kern/systrace_args.c
--- a/sys/kern/systrace_args.c
+++ b/sys/kern/systrace_args.c
@@ -3465,6 +3465,15 @@
*n_args = 3;
break;
}
+ /* setcred */
+ case 590: {
+ struct setcred_args *p = params;
+ uarg[a++] = p->flags; /* u_int */
+ uarg[a++] = (intptr_t)p->wcred; /* const void * */
+ uarg[a++] = p->size; /* size_t */
+ *n_args = 3;
+ break;
+ }
default:
*n_args = 0;
break;
@@ -9271,6 +9280,22 @@
break;
};
break;
+ /* setcred */
+ case 590:
+ switch (ndx) {
+ case 0:
+ p = "u_int";
+ break;
+ case 1:
+ p = "userland const void *";
+ break;
+ case 2:
+ p = "size_t";
+ break;
+ default:
+ break;
+ };
+ break;
default:
break;
};
@@ -11249,6 +11274,11 @@
if (ndx == 0 || ndx == 1)
p = "int";
break;
+ /* setcred */
+ case 590:
+ if (ndx == 0 || ndx == 1)
+ p = "int";
+ break;
default:
break;
};
diff --git a/sys/security/mac/mac_cred.c b/sys/security/mac/mac_cred.c
--- a/sys/security/mac/mac_cred.c
+++ b/sys/security/mac/mac_cred.c
@@ -209,6 +209,55 @@
return (error);
}
+/*
+ * Entry hook for setcred().
+ *
+ * Called with no lock held by setcred() so that MAC modules may allocate memory
+ * in preparation for checking privileges. A call to this hook is always
+ * followed by a matching call to mac_cred_setcred_exit(). Between these two,
+ * setcred() may or may not call mac_cred_check_setcred().
+ */
+void
+mac_cred_setcred_enter(void)
+{
+
+ MAC_POLICY_PERFORM_NOSLEEP(cred_setcred_enter);
+}
+
+MAC_CHECK_PROBE_DEFINE3(cred_check_setcred, "unsigned int", "struct ucred *",
+ "struct ucred *");
+
+/*
+ * Check hook for setcred().
+ *
+ * When called, the current process' lock is held. It thus cannot perform
+ * memory allocations, which must be done in advance in
+ * mac_cred_setcred_enter(). It *MUST NOT* tamper with the process' lock.
+ */
+int
+mac_cred_check_setcred(u_int flags, const struct ucred *old_cred,
+ struct ucred *new_cred)
+{
+ int error;
+
+ MAC_POLICY_CHECK_NOSLEEP(cred_check_setcred, flags, old_cred, new_cred);
+ MAC_CHECK_PROBE3(cred_check_setcred, error, flags, old_cred, new_cred);
+
+ return (error);
+}
+
+/*
+ * Exit hook for setcred().
+ *
+ * Called with no lock held, exactly once per call to mac_cred_setcred_enter().
+ */
+void
+mac_cred_setcred_exit(void)
+{
+
+ MAC_POLICY_PERFORM_NOSLEEP(cred_setcred_exit);
+}
+
MAC_CHECK_PROBE_DEFINE2(cred_check_setuid, "struct ucred *", "uid_t");
int
diff --git a/sys/security/mac/mac_framework.h b/sys/security/mac/mac_framework.h
--- a/sys/security/mac/mac_framework.h
+++ b/sys/security/mac/mac_framework.h
@@ -72,6 +72,7 @@
struct mount;
struct msg;
struct msqid_kernel;
+struct pipepair;
struct proc;
struct semid_kernel;
struct shmfd;
@@ -80,7 +81,6 @@
struct socket;
struct sysctl_oid;
struct sysctl_req;
-struct pipepair;
struct thread;
struct timespec;
struct ucred;
@@ -115,6 +115,10 @@
int mac_cred_check_setaudit_addr(struct ucred *cred,
struct auditinfo_addr *aia);
int mac_cred_check_setauid(struct ucred *cred, uid_t auid);
+void mac_cred_setcred_enter(void);
+int mac_cred_check_setcred(u_int flags, const struct ucred *old_cred,
+ struct ucred *new_cred);
+void mac_cred_setcred_exit(void);
int mac_cred_check_setegid(struct ucred *cred, gid_t egid);
int mac_cred_check_seteuid(struct ucred *cred, uid_t euid);
int mac_cred_check_setgid(struct ucred *cred, gid_t gid);
diff --git a/sys/security/mac/mac_policy.h b/sys/security/mac/mac_policy.h
--- a/sys/security/mac/mac_policy.h
+++ b/sys/security/mac/mac_policy.h
@@ -144,6 +144,10 @@
typedef int (*mpo_cred_check_setaudit_addr_t)(struct ucred *cred,
struct auditinfo_addr *aia);
typedef int (*mpo_cred_check_setauid_t)(struct ucred *cred, uid_t auid);
+typedef void (*mpo_cred_setcred_enter_t)(void);
+typedef int (*mpo_cred_check_setcred_t)(u_int flags,
+ const struct ucred *old_cred, struct ucred *new_cred);
+typedef void (*mpo_cred_setcred_exit_t)(void);
typedef int (*mpo_cred_check_setegid_t)(struct ucred *cred, gid_t egid);
typedef int (*mpo_cred_check_seteuid_t)(struct ucred *cred, uid_t euid);
typedef int (*mpo_cred_check_setgid_t)(struct ucred *cred, gid_t gid);
@@ -720,6 +724,9 @@
mpo_cred_check_setaudit_t mpo_cred_check_setaudit;
mpo_cred_check_setaudit_addr_t mpo_cred_check_setaudit_addr;
mpo_cred_check_setauid_t mpo_cred_check_setauid;
+ mpo_cred_setcred_enter_t mpo_cred_setcred_enter;
+ mpo_cred_check_setcred_t mpo_cred_check_setcred;
+ mpo_cred_setcred_exit_t mpo_cred_setcred_exit;
mpo_cred_check_setuid_t mpo_cred_check_setuid;
mpo_cred_check_seteuid_t mpo_cred_check_seteuid;
mpo_cred_check_setgid_t mpo_cred_check_setgid;
@@ -1033,8 +1040,9 @@
* 3 7.x
* 4 8.x
* 5 14.x
+ * 6 15.x
*/
-#define MAC_VERSION 5
+#define MAC_VERSION 6
#define MAC_POLICY_SET(mpops, mpname, mpfullname, mpflags, privdata_wanted) \
static struct mac_policy_conf mpname##_mac_policy_conf = { \
diff --git a/sys/security/mac_do/mac_do.c b/sys/security/mac_do/mac_do.c
--- a/sys/security/mac_do/mac_do.c
+++ b/sys/security/mac_do/mac_do.c
@@ -5,15 +5,19 @@
*/
#include <sys/param.h>
+#include <sys/ctype.h>
#include <sys/malloc.h>
#include <sys/jail.h>
#include <sys/kernel.h>
+#include <sys/libkern.h>
+#include <sys/limits.h>
#include <sys/lock.h>
#include <sys/module.h>
#include <sys/mount.h>
#include <sys/mutex.h>
#include <sys/priv.h>
#include <sys/proc.h>
+#include <sys/refcount.h>
#include <sys/socket.h>
#include <sys/sx.h>
#include <sys/sysctl.h>
@@ -21,9 +25,9 @@
#include <sys/ucred.h>
#include <sys/vnode.h>
-#include <security/mac/mac_policy.h>
+#include <machine/stdarg.h>
-SYSCTL_DECL(_security_mac);
+#include <security/mac/mac_policy.h>
static SYSCTL_NODE(_security_mac, OID_AUTO, do,
CTLFLAG_RW|CTLFLAG_MPSAFE, 0, "mac_do policy controls");
@@ -32,514 +36,2058 @@
SYSCTL_INT(_security_mac_do, OID_AUTO, enabled, CTLFLAG_RWTUN,
&do_enabled, 0, "Enforce do policy");
+static int print_parse_error = 1;
+SYSCTL_INT(_security_mac_do, OID_AUTO, print_parse_error, CTLFLAG_RWTUN,
+ &print_parse_error, 0, "Print parse errors on setting rules "
+ "(via sysctl(8)).");
+
static MALLOC_DEFINE(M_DO, "do_rule", "Rules for mac_do");
#define MAC_RULE_STRING_LEN 1024
-static unsigned mac_do_osd_jail_slot;
+static unsigned osd_jail_slot;
+static unsigned osd_thread_slot;
-#define RULE_UID 1
-#define RULE_GID 2
-#define RULE_ANY 3
+#define IT_INVALID 0 /* Must stay 0. */
+#define IT_UID 1
+#define IT_GID 2
+#define IT_ANY 3
+#define IT_LAST IT_ANY
+
+static const char *id_type_to_str[] = {
+ [IT_INVALID] = "invalid",
+ [IT_UID] = "uid",
+ [IT_GID] = "gid",
+ /* See also parse_id_type(). */
+ [IT_ANY] = "*",
+};
+
+#define PARSE_ERROR_SIZE 256
+
+struct parse_error {
+ size_t pos;
+ char msg[PARSE_ERROR_SIZE];
+};
+
+/*
+ * We assume that 'uid_t' and 'gid_t' are aliases to 'int' in conversions
+ * required for parsing rules specification strings.
+ */
+_Static_assert(sizeof(uid_t) == sizeof(int) && (int)(uid_t)-1 == -1 &&
+ sizeof(gid_t) == sizeof(int) && (int)(gid_t)-1 == -1,
+ "mac_do(4) currently assumes that 'uid_t' and 'gid_t' are aliases to 'int'");
+
+/*
+ * Internal flags.
+ *
+ * They either apply as per-type (t) or per-ID (i) but are conflated because all
+ * per-ID flags are also valid as per-type ones to qualify the "current" (".")
+ * per-type flag. Also, some of them are in fact exclusive, but we use one-hot
+ * encoding for simplicity.
+ *
+ * There is currently room for "only" 16 bits. As these flags are purely
+ * internal, they can be renumbered and/or their type changed as needed.
+ *
+ * See also the check_*() functions below.
+ */
+typedef uint16_t flags_t;
+
+/* (i,gid) Specification concerns primary groups. */
+#define MDF_PRIMARY 1
+/* (i,gid) Specification concerns supplementary groups. */
+#define MDF_SUPP_ALLOW 2
+/* (i,gid) Group must appear as a supplementary group. */
+#define MDF_SUPP_MUST 4
+/* (i,gid) Group must not appear as a supplementary group. */
+#define MDF_SUPP_DONT 8
+#define MDF_SUPP_MASK (MDF_SUPP_ALLOW | MDF_SUPP_MUST | MDF_SUPP_DONT)
+#define MDF_ID_MASK (MDF_PRIMARY | MDF_SUPP_MASK)
+
+/*
+ * (t) All IDs allowed.
+ *
+ * For GIDs, MDF_ANY only concerns primary groups. The MDF_PRIMARY and
+ * MDF_SUPP_* flags never apply to MDF_ANY, but can be present if MDF_CURRENT is
+ * present also, as usual.
+ */
+#define MDF_ANY (1u << 8)
+/* (t) Current IDs allowed. */
+#define MDF_CURRENT (1u << 9)
+#define MDF_TYPE_COMMON_MASK (MDF_ANY | MDF_CURRENT)
+/* (t,gid) All IDs allowed as supplementary groups. */
+#define MDF_ANY_SUPP (1u << 10)
+/* (t,gid) Some ID has MDF_SUPP_MUST or MDF_SUPP_DONT. */
+#define MDF_MAY_REJ_SUPP (1u << 11)
+/* (t,gid) Whether some target clause concerns primary groups. Used during
+ * parsing only. */
+#define MDF_HAS_PRIMARY_CLAUSE (1u << 12)
+/* (t,gid) Whether some target clause concerns supplementary groups. Used
+ * during parsing only. */
+#define MDF_HAS_SUPP_CLAUSE (1u << 13)
+#define MDF_TYPE_GID_MASK (MDF_ANY_SUPP | MDF_MAY_REJ_SUPP | \
+ MDF_HAS_PRIMARY_CLAUSE | MDF_HAS_SUPP_CLAUSE)
+#define MDF_TYPE_MASK (MDF_TYPE_COMMON_MASK | MDF_TYPE_GID_MASK)
+
+/*
+ * Persistent structures.
+ */
+
+struct id_spec {
+ int id;
+ flags_t flags; /* See MDF_* above. */
+};
+
+/*
+ * This limits the number of target clauses per type to 65535. With the current
+ * value of MAC_RULE_STRING_LEN (1024), this is way more than enough anyway.
+ */
+typedef uint16_t id_nb_t;
+/* We only have a few IT_* types. */
+typedef uint16_t id_type_t;
struct rule {
- int from_type;
- union {
- uid_t f_uid;
- gid_t f_gid;
- };
- int to_type;
- uid_t t_uid;
- TAILQ_ENTRY(rule) r_entries;
+ STAILQ_ENTRY(rule) r_entries;
+ id_type_t from_type;
+ int from_id;
+ flags_t uid_flags; /* See MDF_* above. */
+ id_nb_t uids_nb;
+ flags_t gid_flags; /* See MDF_* above. */
+ id_nb_t gids_nb;
+ struct id_spec *uids;
+ struct id_spec *gids;
};
-struct mac_do_rule {
- char string[MAC_RULE_STRING_LEN];
- TAILQ_HEAD(rulehead, rule) head;
+STAILQ_HEAD(rulehead, rule);
+
+struct rules {
+ char string[MAC_RULE_STRING_LEN];
+ struct rulehead head;
+ volatile u_int use_count __aligned(CACHE_LINE_SIZE);
};
-static struct mac_do_rule rules0;
+/*
+ * Temporary structures used to build a 'struct rule' above.
+ */
+
+struct id_elem {
+ STAILQ_ENTRY(id_elem) ie_entries;
+ struct id_spec spec;
+};
+
+STAILQ_HEAD(id_list, id_elem);
+
+#ifdef INVARIANTS
+static void
+check_type(const id_type_t type)
+{
+ if (type > IT_LAST)
+ panic("Invalid type number %u", type);
+}
static void
-toast_rules(struct rulehead *head)
+panic_for_unexpected_flags(const id_type_t type, const flags_t flags,
+ const char *const str)
{
- struct rule *r;
-
- while ((r = TAILQ_FIRST(head)) != NULL) {
- TAILQ_REMOVE(head, r, r_entries);
- free(r, M_DO);
- }
+ panic("ID type %s: Unexpected flags %u (%s), ", id_type_to_str[type],
+ flags, str);
}
-static int
-parse_rule_element(char *element, struct rule **rule)
+static void
+check_type_and_id_flags(const id_type_t type, const flags_t flags)
{
- int error = 0;
- char *type, *id, *p;
- struct rule *new;
+ const char *str;
- new = malloc(sizeof(*new), M_DO, M_ZERO|M_WAITOK);
-
- type = strsep(&element, "=");
- if (type == NULL) {
- error = EINVAL;
- goto out;
- }
- if (strcmp(type, "uid") == 0) {
- new->from_type = RULE_UID;
- } else if (strcmp(type, "gid") == 0) {
- new->from_type = RULE_GID;
- } else {
- error = EINVAL;
- goto out;
- }
- id = strsep(&element, ":");
- if (id == NULL) {
- error = EINVAL;
- goto out;
- }
- if (new->from_type == RULE_UID)
- new->f_uid = strtol(id, &p, 10);
- if (new->from_type == RULE_GID)
- new->f_gid = strtol(id, &p, 10);
- if (*p != '\0') {
- error = EINVAL;
- goto out;
- }
- if (*element == '\0') {
- error = EINVAL;
- goto out;
- }
- if (strcmp(element, "any") == 0 || strcmp(element, "*") == 0) {
- new->to_type = RULE_ANY;
- } else {
- new->to_type = RULE_UID;
- new->t_uid = strtol(element, &p, 10);
- if (*p != '\0') {
- error = EINVAL;
- goto out;
+ check_type(type);
+ switch (type) {
+ case IT_UID:
+ if (flags != 0) {
+ str = "only 0 allowed";
+ goto unexpected_flags;
}
- }
-out:
- if (error != 0) {
- free(new, M_DO);
- *rule = NULL;
- } else
- *rule = new;
- return (error);
-}
-
-static int
-parse_rules(char *string, struct rulehead *head)
-{
- struct rule *new;
- char *element;
- int error = 0;
-
- while ((element = strsep(&string, ",")) != NULL) {
- if (strlen(element) == 0)
- continue;
- error = parse_rule_element(element, &new);
- if (error)
- goto out;
- TAILQ_INSERT_TAIL(head, new, r_entries);
- }
-out:
- if (error != 0)
- toast_rules(head);
- return (error);
-}
-
-static struct mac_do_rule *
-mac_do_rule_find(struct prison *spr, struct prison **prp)
-{
- struct prison *pr;
- struct mac_do_rule *rules;
-
- for (pr = spr;; pr = pr->pr_parent) {
- mtx_lock(&pr->pr_mtx);
- if (pr == &prison0) {
- rules = &rules0;
- break;
+ break;
+ case IT_GID:
+ if (flags & ~MDF_ID_MASK) {
+ str = "only bits in MDF_ID_MASK allowed";
+ goto unexpected_flags;
}
- rules = osd_jail_get(pr, mac_do_osd_jail_slot);
- if (rules != NULL)
- break;
- mtx_unlock(&pr->pr_mtx);
+ if (!powerof2(flags & MDF_SUPP_MASK)) {
+ str = "only a single flag in MDF_SUPP_MASK allowed";
+ goto unexpected_flags;
+ }
+ break;
+ default:
+ __assert_unreachable();
}
- *prp = pr;
+ return;
+unexpected_flags:
+ panic_for_unexpected_flags(type, flags, str);
+}
+
+static void
+check_type_and_id_spec(const id_type_t type, const struct id_spec *const is)
+{
+ check_type_and_id_flags(type, is->flags);
+ if (is->id < 0)
+ panic("ID type %s: Negative id %d", id_type_to_str[type],
+ is->id);
+}
+
+static void
+check_type_and_type_flags(const id_type_t type, const flags_t flags)
+{
+ const char *str;
+
+ check_type_and_id_flags(type, flags & MDF_ID_MASK);
+ if (flags & ~MDF_ID_MASK & ~MDF_TYPE_MASK) {
+ str = "only MDF_ID_MASK | MDF_TYPE_MASK bits allowed";
+ goto unexpected_flags;
+ }
+ if ((flags & MDF_ANY) && (flags & MDF_CURRENT) &&
+ (type == IT_UID || (flags & MDF_PRIMARY))) {
+ str = "MDF_ANY and MDF_CURRENT are exclusive for UIDs "
+ "or primary group GIDs";
+ goto unexpected_flags;
+ }
+ if ((flags & MDF_ANY_SUPP) && (flags & MDF_CURRENT) &&
+ (flags & MDF_SUPP_MASK)) {
+ str = "MDF_SUPP_ANY and MDF_CURRENT with supplementary "
+ "groups specification are exclusive";
+ goto unexpected_flags;
+ }
+ if (((flags & MDF_PRIMARY) || (flags & MDF_ANY)) &&
+ !(flags & MDF_HAS_PRIMARY_CLAUSE)) {
+ str = "Presence of folded primary clause not reflected "
+ "by presence of MDF_HAS_PRIMARY_CLAUSE";
+ goto unexpected_flags;
+ }
+ if (((flags & MDF_SUPP_MASK) || (flags & MDF_ANY_SUPP)) &&
+ !(flags & MDF_HAS_SUPP_CLAUSE)) {
+ str = "Presence of folded supplementary clause not reflected "
+ "by presence of MDF_HAS_SUPP_CLAUSE";
+ goto unexpected_flags;
+ }
+ return;
+
+unexpected_flags:
+ panic_for_unexpected_flags(type, flags, str);
+}
+#else /* !INVARIANTS */
+#define check_type_and_id_flags(...)
+#define check_type_and_id_spec(...)
+#define check_type_and_type_flags(...)
+#endif /* INVARIANTS */
+
+/*
+ * Returns EALREADY if both flags have some overlap, or EINVAL if flags are
+ * incompatible, else 0 with flags successfully merged into 'dest'.
+ */
+static int
+coalesce_id_flags(const flags_t src, flags_t *const dest)
+{
+ flags_t res;
+
+ if ((src & *dest) != 0)
+ return (EALREADY);
+
+ res = src | *dest;
+
+ /* Check for compatibility of supplementary flags, and coalesce. */
+ if (res & MDF_SUPP_MASK) {
+ /* MDF_SUPP_DONT incompatible with the rest. */
+ if ((res & MDF_SUPP_DONT) && (res & MDF_SUPP_MASK &
+ ~MDF_SUPP_DONT))
+ return (EINVAL);
+ /* Coalesce MDF_SUPP_ALLOW and MDF_SUPP_MUST into MDF_SUPP_MUST. */
+ if ((res & MDF_SUPP_ALLOW) && (res & MDF_SUPP_MUST))
+ res &= ~MDF_SUPP_ALLOW;
+ }
+
+ *dest = res;
+ return (0);
+}
+
+static void
+toast_rules(struct rules *const rules)
+{
+ struct rulehead *const head = &rules->head;
+ struct rule *rule, *rule_next;
+
+ STAILQ_FOREACH_SAFE(rule, head, r_entries, rule_next) {
+ free(rule->uids, M_DO);
+ free(rule->gids, M_DO);
+ free(rule, M_DO);
+ }
+ free(rules, M_DO);
+}
+
+static struct rules *
+alloc_rules(void)
+{
+ struct rules *const rules = malloc(sizeof(*rules), M_DO, M_WAITOK);
+
+ _Static_assert(MAC_RULE_STRING_LEN > 0, "MAC_RULE_STRING_LEN <= 0!");
+ rules->string[0] = 0;
+ STAILQ_INIT(&rules->head);
+ rules->use_count = 0;
return (rules);
}
-static int
-sysctl_rules(SYSCTL_HANDLER_ARGS)
+static bool
+is_null_or_empty(const char *s)
{
- char *copy_string, *new_string;
- struct rulehead head, saved_head;
- struct prison *pr;
- struct mac_do_rule *rules;
+ return (s == NULL || s[0] == 0);
+}
+
+/*
+ * String to non-negative int.
+ *
+ * Returns minus an error (EINVAL or EOVERFLOW) in case of failure. Rejects an
+ * explicit sign at start, as well as whitespaces.
+ */
+static int
+strtonni(const char *const restrict s, const char **const restrict endptr,
+ int base)
+{
+ long l;
+ char *ep;
+
+ if (s[0] != 0 && !isdigit(s[0])) {
+ if (endptr != NULL)
+ *endptr = s;
+ return (-EINVAL);
+ }
+
+ l = strtol(s, &ep, base);
+ if (endptr != NULL)
+ *endptr = ep;
+ if (l < 0)
+ return (-EINVAL);
+ else if (l == LONG_MAX || l > INT_MAX)
+ return (-EOVERFLOW);
+ return ((int)l);
+}
+
+/*
+ * strsep() variant skipping spaces and tabs.
+ *
+ * Skips spaces and tabs at beginning and end of the token before one of the
+ * 'delim' characters, i.e., at start of string and just before one of the
+ * delimiter characters (so it doesn't prevent tokens containing spaces and tabs
+ * in the middle).
+ */
+static char *
+strsep_noblanks(char **const stringp, const char *delim)
+{
+ char *p = *stringp;
+ char *ret, *wsp;
+ size_t idx;
+
+ if (p == NULL)
+ return (NULL);
+
+ idx = strspn(p, " \t");
+ p += idx;
+
+ ret = strsep(&p, delim);
+
+ /* Rewind spaces/tabs at the end. */
+ if (p == NULL)
+ wsp = ret + strlen(ret);
+ else
+ wsp = p - 1;
+ for (; wsp != ret; --wsp) {
+ switch (wsp[-1]) {
+ case ' ':
+ case '\t':
+ continue;
+ }
+ break;
+ }
+ *wsp = '\0';
+
+ *stringp = p;
+ return (ret);
+}
+
+
+static void
+alloc_parse_error(struct parse_error **const parse_error, const size_t pos,
+ const char *const fmt, ...)
+{
+ struct parse_error *const err = malloc(sizeof(*err), M_DO, M_WAITOK);
+ va_list ap;
+
+ err->pos = pos;
+ va_start(ap, fmt);
+ vsnprintf(err->msg, PARSE_ERROR_SIZE, fmt, ap);
+ va_end(ap);
+
+ MPASS(*parse_error == NULL);
+ *parse_error = err;
+}
+
+static void
+free_parse_error(struct parse_error *const parse_error)
+{
+ free(parse_error, M_DO);
+}
+
+static int
+parse_id_type(const char *const string, id_type_t *const type,
+ struct parse_error **const parse_error)
+{
+ /*
+ * Special case for "any", as the canonical form for IT_ANY in
+ * id_type_to_str[] is "*".
+ */
+ if (strcmp(string, "any") == 0) {
+ *type = IT_ANY;
+ return (0);
+ }
+
+ /* Start at 1 to avoid parsing "invalid". */
+ for (size_t i = 1; i <= IT_LAST; ++i) {
+ if (strcmp(string, id_type_to_str[i]) == 0) {
+ *type = i;
+ return (0);
+ }
+ }
+
+ *type = IT_INVALID;
+ alloc_parse_error(parse_error, 0, "No valid type found.");
+ return (EINVAL);
+}
+
+static bool
+parse_any(const char *const string)
+{
+ return (strcmp(string, "*") == 0 || strcmp(string, "any") == 0);
+}
+
+static bool
+has_clauses(const id_nb_t nb, const flags_t type_flags)
+{
+ return ((type_flags & MDF_TYPE_MASK) || nb != 0);
+}
+
+static int
+parse_target_clause(char *to, struct rule *const rule,
+ struct id_list *const uid_list, struct id_list *const gid_list,
+ struct parse_error **const parse_error)
+{
+ const char *const start = to;
+ char *to_type, *to_id;
+ const char *p;
+ struct id_list *list;
+ id_nb_t *nb;
+ flags_t *tflags;
+ struct id_elem *ie;
+ struct id_spec is = {};
+ u_int gid_flags = 0;
+ id_type_t type;
int error;
- rules = mac_do_rule_find(req->td->td_ucred->cr_prison, &pr);
- mtx_unlock(&pr->pr_mtx);
- if (req->newptr == NULL)
- return (sysctl_handle_string(oidp, rules->string, MAC_RULE_STRING_LEN, req));
+ MPASS(*parse_error == NULL);
+ MPASS(to != NULL);
+ to_type = strsep_noblanks(&to, "=");
+ MPASS(to_type != NULL);
+ error = parse_id_type(to_type, &type, parse_error);
+ if (error != 0)
+ goto einval;
- new_string = malloc(MAC_RULE_STRING_LEN, M_DO,
- M_WAITOK|M_ZERO);
- mtx_lock(&pr->pr_mtx);
- strlcpy(new_string, rules->string, MAC_RULE_STRING_LEN);
- mtx_unlock(&pr->pr_mtx);
+ to_id = strsep_noblanks(&to, "");
+ switch (type) {
+ case IT_GID:
+ if (to_id == NULL) {
+ alloc_parse_error(parse_error, to_type - start,
+ "No '=' and ID specification after type '%s'.",
+ to_type);
+ goto einval;
+ }
- error = sysctl_handle_string(oidp, new_string, MAC_RULE_STRING_LEN, req);
- if (error)
- goto out;
+ /*
+ * This is easily wrapped into a loop for when/if we allow
+ * multiple flags to be specified at once.
+ */
+ switch (*to_id) {
+ case '+':
+ is.flags |= MDF_SUPP_ALLOW;
+ goto has_supp_clause;
+ case '!':
+ is.flags |= MDF_SUPP_MUST;
+ gid_flags |= MDF_MAY_REJ_SUPP;
+ goto has_supp_clause;
+ case '-':
+ is.flags |= MDF_SUPP_DONT;
+ gid_flags |= MDF_MAY_REJ_SUPP;
+ goto has_supp_clause;
+ has_supp_clause:
+ gid_flags |= MDF_HAS_SUPP_CLAUSE;
+ ++to_id;
+ break;
+ default:
+ is.flags |= MDF_PRIMARY;
+ gid_flags |= MDF_HAS_PRIMARY_CLAUSE;
+ break;
+ }
- copy_string = strdup(new_string, M_DO);
- TAILQ_INIT(&head);
- error = parse_rules(copy_string, &head);
- free(copy_string, M_DO);
- if (error)
- goto out;
- TAILQ_INIT(&saved_head);
- mtx_lock(&pr->pr_mtx);
- TAILQ_CONCAT(&saved_head, &rules->head, r_entries);
- TAILQ_CONCAT(&rules->head, &head, r_entries);
- strlcpy(rules->string, new_string, MAC_RULE_STRING_LEN);
- mtx_unlock(&pr->pr_mtx);
- toast_rules(&saved_head);
+ list = gid_list;
+ nb = &rule->gids_nb;
+ tflags = &rule->gid_flags;
+ /* "*" or "any"? */
+ if (parse_any(to_id)) {
+ /*
+ * We check that we have not seen any other clause of
+ * the same category (i.e., concerning primary or
+ * supplementary groups).
+ */
+ if (is.flags & MDF_PRIMARY) {
+ if (*tflags & MDF_HAS_PRIMARY_CLAUSE) {
+ alloc_parse_error(parse_error,
+ to_id - start,
+ "'any' specified after another "
+ "(primary) GID.");
+ goto einval;
+ }
+ *tflags |= gid_flags | MDF_ANY;
+ } else {
+ /*
+ * If a supplementary group flag was present, it
+ * must be MDF_SUPP_ALLOW ("+").
+ */
+ if ((is.flags & MDF_SUPP_MASK) != MDF_SUPP_ALLOW) {
+ alloc_parse_error(parse_error,
+ to_id - start,
+ "'any' specified with another "
+ "flag than '+'.");
+ goto einval;
+ }
+ if (*tflags & MDF_HAS_SUPP_CLAUSE) {
+ alloc_parse_error(parse_error,
+ to_id - start,
+ "'+any' specified after another "
+ "(supplementary) GID.");
+ goto einval;
+ }
+ *tflags |= gid_flags | MDF_ANY_SUPP;
+ }
+ goto check_type_and_finish;
+ } else {
+ /*
+ * Check that we haven't already seen "any" for the same
+ * category.
+ */
+ if ((is.flags & MDF_PRIMARY)) {
+ if (*tflags & MDF_ANY) {
+ alloc_parse_error(parse_error,
+ to_id - start,
+ "Some (primary) GID specified after "
+ "'any'.");
+ goto einval;
+ }
+ } else if (*tflags & MDF_ANY_SUPP &&
+ is.flags & MDF_SUPP_ALLOW) {
+ alloc_parse_error(parse_error,
+ to_id - start,
+ "Some (supplementary) GID specified after "
+ "'+any'.");
+ goto einval;
+ }
+ *tflags |= gid_flags;
+ }
+ break;
+
+ case IT_UID:
+ if (to_id == NULL) {
+ alloc_parse_error(parse_error, to_type - start,
+ "No '=' and ID specification after type '%s'.",
+ to_type);
+ goto einval;
+ }
+
+ list = uid_list;
+ nb = &rule->uids_nb;
+ tflags = &rule->uid_flags;
+
+ /* "*" or "any"? */
+ if (parse_any(to_id)) {
+ /* There must not be any other clause. */
+ if (has_clauses(*nb, *tflags)) {
+ alloc_parse_error(parse_error, to_id - start,
+ "'any' specified after another UID.");
+ goto einval;
+ }
+ *tflags |= MDF_ANY;
+ goto check_type_and_finish;
+ } else {
+ /*
+ * Check that we haven't already seen "any" for the same
+ * category.
+ */
+ if (*tflags & MDF_ANY) {
+ alloc_parse_error(parse_error, to_id - start,
+ "Some UID specified after 'any'.");
+ goto einval;
+ }
+ }
+ break;
+
+ case IT_ANY:
+ /* No ID allowed. */
+ if (to_id != NULL) {
+ alloc_parse_error(parse_error, to_type - start,
+ "No '=' and ID allowed after type '%s'.", to_type);
+ goto einval;
+ }
+ /*
+ * We can't have IT_ANY after any other IT_*, it must be the
+ * only one.
+ */
+ if (has_clauses(rule->uids_nb, rule->uid_flags) ||
+ has_clauses(rule->gids_nb, rule->gid_flags)) {
+ alloc_parse_error(parse_error, to_type - start,
+ "Target clause of type '%s' coming after another "
+ "clause (must be alone).", to_type);
+ goto einval;
+ }
+ rule->uid_flags |= MDF_ANY;
+ rule->gid_flags |= MDF_ANY | MDF_ANY_SUPP |
+ MDF_HAS_PRIMARY_CLAUSE | MDF_HAS_SUPP_CLAUSE;
+ goto finish;
+
+ default:
+ /* parse_id_type() returns no other types currently. */
+ __assert_unreachable();
+ }
+
+ /* These cases have been treated above. */
+ MPASS((type == IT_UID || type == IT_GID) && !parse_any(to_id));
+
+ /* "."? */
+ if (strcmp(to_id, ".") == 0) {
+ if (*tflags & MDF_CURRENT) {
+ /* Duplicate "." <id>. Try to coalesce. */
+ error = coalesce_id_flags(is.flags, tflags);
+ if (error != 0) {
+ alloc_parse_error(parse_error, to_id - start,
+ "Incompatible flags with prior clause "
+ "with same target.");
+ goto einval;
+ }
+ } else
+ *tflags |= MDF_CURRENT | is.flags;
+ goto check_type_and_finish;
+ }
+
+ /* Parse an ID. */
+ is.id = strtonni(to_id, &p, 10);
+ if (is.id < 0 || *p != '\0') {
+ alloc_parse_error(parse_error, to_id - start,
+ "Cannot parse a numerical ID (base 10, no sign).");
+ goto einval;
+ }
+
+ /*
+ * We check for duplicate IDs and coalesce their 'struct id_spec' only
+ * at end of parse_single_rule() because it is much more performant then
+ * (using sorted arrays).
+ */
+ ++*nb;
+ if (*nb == 0) {
+ alloc_parse_error(parse_error, 0,
+ "Too many target clauses of type '%s'.", to_type);
+ return (EOVERFLOW);
+ }
+ ie = malloc(sizeof(*ie), M_DO, M_WAITOK);
+ ie->spec = is;
+ STAILQ_INSERT_TAIL(list, ie, ie_entries);
+ check_type_and_id_spec(type, &is);
+check_type_and_finish:
+ check_type_and_type_flags(type, *tflags);
+finish:
+ return (0);
+einval:
+ /* We must have built a parse error on error. */
+ MPASS(*parse_error != NULL);
+ return (EINVAL);
+}
+
+static int
+int_cmp(const int i1, const int i2)
+{
+ return ((i1 > i2) - (i1 < i2));
+}
+
+static int
+id_spec_cmp(const void *const p1, const void *const p2)
+{
+ const struct id_spec *const is1 = p1;
+ const struct id_spec *const is2 = p2;
+
+ return (int_cmp(is1->id, is2->id));
+}
+
+/*
+ * Transfer content of 'list' into 'array', freeing and emptying list.
+ *
+ * 'nb' must be 'list''s length and not be greater than 'array''s size. The
+ * destination array is sorted by ID. Structures 'struct id_spec' with same IDs
+ * are coalesced if that makes sense (not including duplicate clauses), else
+ * EINVAL is returned. On success, 'nb' is updated (lowered) to account for
+ * coalesced specifications. The parameter 'type' is only for testing purposes
+ * (INVARIANTS).
+ */
+static int
+pour_list_into_rule(const id_type_t type, struct id_list *const list,
+ struct id_spec *const array, id_nb_t *const nb,
+ struct parse_error **const parse_error)
+{
+ struct id_elem *ie, *ie_next;
+ size_t idx = 0;
+
+ /* Fill the array. */
+ STAILQ_FOREACH_SAFE(ie, list, ie_entries, ie_next) {
+ MPASS(idx < *nb);
+ array[idx] = ie->spec;
+ free(ie, M_DO);
+ ++idx;
+ }
+ MPASS(idx == *nb);
+ STAILQ_INIT(list);
+
+ /* Sort it (by ID). */
+ qsort(array, *nb, sizeof(*array), id_spec_cmp);
+
+ /* Coalesce same IDs. */
+ if (*nb != 0) {
+ size_t ref_idx = 0;
+
+ for (idx = 1; idx < *nb; ++idx) {
+ const int id = array[idx].id;
+
+ if (id == array[ref_idx].id) {
+ const int error =
+ coalesce_id_flags(array[idx].flags,
+ &array[ref_idx].flags);
+
+ if (error != 0) {
+ alloc_parse_error(parse_error, 0,
+ "Incompatible or duplicate flags "
+ "for ID %d.", id);
+ return (EINVAL);
+ }
+ check_type_and_id_flags(type,
+ array[ref_idx].flags);
+ }
+ else {
+ ++ref_idx;
+ if (ref_idx != idx)
+ array[ref_idx] = array[idx];
+ }
+ }
+ *nb = ref_idx + 1;
+ }
+
+ return (0);
+}
+
+/*
+ * See also first comments for parse_rule() below.
+ *
+ * The second part of a rule, called <target> (or <to>), is a comma-separated
+ * (',') list of '<type>=<flags><id>' clauses similar to that of the <from>
+ * part, with the extensions that <id> may also be "*" or "any" or ".", and that
+ * <flags> may contain at most one of the '+', '-' and '!' characters when
+ * <type> is "gid" (no flags are allowed for "uid"). No two clauses in a single
+ * <to> list may list the same <id>. "*" and "any" both designate any ID for
+ * the <type>, and are aliases to each other. In front of "any" (or "*"; only
+ * for GIDs), only the '+' flags is allowed. "." designates the process'
+ * current IDs for the <type>. The precise meaning of flags and "." is
+ * explained in functions checking privileges below.
+ */
+static int
+parse_single_rule(char *rule, struct rules *const rules,
+ struct parse_error **const parse_error)
+{
+ const char *const start = rule;
+ const char *from_type, *from_id, *p;
+ char *to_list;
+ struct id_list uid_list, gid_list;
+ struct id_elem *ie, *ie_next;
+ struct rule *new;
+ int error;
+
+ MPASS(*parse_error == NULL);
+ STAILQ_INIT(&uid_list);
+ STAILQ_INIT(&gid_list);
+
+ /* Freed when the 'struct rules' container is freed. */
+ new = malloc(sizeof(*new), M_DO, M_WAITOK | M_ZERO);
+
+ from_type = strsep_noblanks(&rule, "=");
+ MPASS(from_type != NULL); /* Because 'rule' was not NULL. */
+ error = parse_id_type(from_type, &new->from_type, parse_error);
+ if (error != 0)
+ goto einval;
+ switch (new->from_type) {
+ case IT_UID:
+ case IT_GID:
+ break;
+ default:
+ alloc_parse_error(parse_error, 0, "Type '%s' not allowed in "
+ "the \"from\" part of rules.");
+ goto einval;
+ }
+
+ from_id = strsep_noblanks(&rule, ":");
+ if (is_null_or_empty(from_id)) {
+ alloc_parse_error(parse_error, 0, "No ID specified.");
+ goto einval;
+ }
+
+ new->from_id = strtonni(from_id, &p, 10);
+ if (new->from_id < 0 || *p != '\0') {
+ alloc_parse_error(parse_error, from_id - start,
+ "Cannot parse a numerical ID (base 10, no sign).");
+ goto einval;
+ }
+
+ /*
+ * We will now parse the "to" list.
+ *
+ * In order to ease parsing, we will begin by building lists of target
+ * UIDs and GIDs in local variables 'uid_list' and 'gid_list'. The
+ * number of each type of IDs will be filled directly in 'new'. At end
+ * of parse, we will allocate both arrays of IDs to be placed into the
+ * 'uids' and 'gids' members, sort them, and discard the tail queues
+ * used to build them. This conversion to sorted arrays at end of parse
+ * allows to minimize memory allocations and enables searching IDs in
+ * O(log(n)) instead of linearly.
+ */
+ to_list = strsep_noblanks(&rule, ",");
+ if (to_list == NULL) {
+ alloc_parse_error(parse_error, 0, "No target list.");
+ goto einval;
+ }
+ do {
+ error = parse_target_clause(to_list, new, &uid_list, &gid_list,
+ parse_error);
+ if (error != 0) {
+ (*parse_error)->pos += to_list - start;
+ goto einval;
+ }
+
+ to_list = strsep_noblanks(&rule, ",");
+ } while (to_list != NULL);
+
+ if (new->uids_nb != 0) {
+ new->uids = malloc(sizeof(*new->uids) * new->uids_nb, M_DO,
+ M_WAITOK);
+ error = pour_list_into_rule(IT_UID, &uid_list, new->uids,
+ &new->uids_nb, parse_error);
+ if (error != 0)
+ goto einval;
+ }
+ MPASS(STAILQ_EMPTY(&uid_list));
+ if (!has_clauses(new->uids_nb, new->uid_flags)) {
+ /* No UID specified, default is "uid=.". */
+ MPASS(new->uid_flags == 0);
+ new->uid_flags = MDF_CURRENT;
+ check_type_and_type_flags(IT_UID, new->uid_flags);
+ }
+
+ if (new->gids_nb != 0) {
+ new->gids = malloc(sizeof(*new->gids) * new->gids_nb, M_DO,
+ M_WAITOK);
+ error = pour_list_into_rule(IT_GID, &gid_list, new->gids,
+ &new->gids_nb, parse_error);
+ if (error != 0)
+ goto einval;
+ }
+ MPASS(STAILQ_EMPTY(&gid_list));
+ if (!has_clauses(new->gids_nb, new->gid_flags)) {
+ /* No GID specified, default is "gid=.,gid=!.". */
+ MPASS(new->gid_flags == 0);
+ new->gid_flags |= MDF_CURRENT | MDF_PRIMARY | MDF_SUPP_MUST |
+ MDF_HAS_PRIMARY_CLAUSE | MDF_HAS_SUPP_CLAUSE;
+ check_type_and_type_flags(IT_GID, new->gid_flags);
+ }
+
+ STAILQ_INSERT_TAIL(&rules->head, new, r_entries);
+ return (0);
+
+einval:
+ free(new->gids, M_DO);
+ free(new->uids, M_DO);
+ free(new, M_DO);
+ STAILQ_FOREACH_SAFE(ie, &gid_list, ie_entries, ie_next)
+ free(ie, M_DO);
+ STAILQ_FOREACH_SAFE(ie, &uid_list, ie_entries, ie_next)
+ free(ie, M_DO);
+ MPASS(*parse_error != NULL);
+ return (EINVAL);
+}
+
+/*
+ * Parse rules specification and produce rule structures out of it.
+ *
+ * Returns 0 on success, with '*rulesp' made to point to a 'struct rule'
+ * representing the rules. On error, the returned value is non-zero and
+ * '*rulesp' is unchanged. If 'string' has length greater or equal to
+ * MAC_RULE_STRING_LEN, ENAMETOOLONG is returned. If it is not in the expected
+ * format, EINVAL is returned. If an error is returned, '*parse_error' is set
+ * to point to a 'struct parse_error' giving an error message for the problem,
+ * else '*parse_error' is set to NULL.
+ *
+ * Expected format: A semi-colon-separated list of rules of the form
+ * "<from>:<target>". The <from> part is of the form "<type>=<id>" where <type>
+ * is "uid" or "gid", <id> an UID or GID (depending on <type>) and <target> is
+ * "*", "any" or a comma-separated list of '<type>=<flags><id>' clauses (see the
+ * comment for parse_single_rule() for more details). For convenience, empty
+ * rules are allowed (and do nothing), and spaces and tabs are allowed (and
+ * removed) around each token (tokens are natural ones, except that
+ * '<flags><id>' as a whole is considered a single token, so no blanks are
+ * allowed between '<flags>' and '<id>').
+ *
+ * Examples:
+ * - "uid=1001:uid=1010,gid=1010;uid=1002:any"
+ * - "gid=1010:gid=1011,gid=1012,gid=1013"
+ */
+static int
+parse_rules(const char *const string, struct rules **const rulesp,
+ struct parse_error **const parse_error)
+{
+ const size_t len = strlen(string);
+ char *copy, *p, *rule;
+ struct rules *rules;
+ int error = 0;
+
+ *parse_error = NULL;
+
+ if (len >= MAC_RULE_STRING_LEN) {
+ alloc_parse_error(parse_error, 0,
+ "Rule specification string is too long (%zu, max %zu)",
+ len, MAC_RULE_STRING_LEN - 1);
+ return (ENAMETOOLONG);
+ }
+
+ rules = alloc_rules();
+ bcopy(string, rules->string, len + 1);
+ MPASS(rules->string[len] == '\0'); /* Catch some races. */
+
+ copy = malloc(len + 1, M_DO, M_WAITOK);
+ bcopy(string, copy, len + 1);
+ MPASS(copy[len] == '\0'); /* Catch some races. */
+
+ p = copy;
+ while ((rule = strsep_noblanks(&p, ";")) != NULL) {
+ if (rule[0] == '\0')
+ continue;
+ error = parse_single_rule(rule, rules, parse_error);
+ if (error != 0) {
+ (*parse_error)->pos += rule - copy;
+ toast_rules(rules);
+ goto out;
+ }
+ }
+
+ *rulesp = rules;
out:
- free(new_string, M_DO);
+ free(copy, M_DO);
+ return (error);
+}
+
+/*
+ * Find rules applicable to the passed prison.
+ *
+ * Returns the applicable rules (and never NULL). 'pr' must be unlocked.
+ * 'aprp' is set to the (ancestor) prison holding these, and it must be unlocked
+ * once the caller is done accessing the rules. '*aprp' is equal to 'pr' if and
+ * only if the current jail has its own set of rules.
+ */
+static struct rules *
+find_rules(struct prison *const pr, struct prison **const aprp)
+{
+ struct prison *cpr, *ppr;
+ struct rules *rules;
+
+ cpr = pr;
+ for (;;) {
+ prison_lock(cpr);
+ rules = osd_jail_get(cpr, osd_jail_slot);
+ if (rules != NULL)
+ break;
+ prison_unlock(cpr);
+
+ ppr = cpr->pr_parent;
+ MPASS(ppr != NULL); /* prison0 always has rules. */
+ cpr = ppr;
+ }
+
+ *aprp = cpr;
+ return (rules);
+}
+
+static void
+hold_rules(struct rules *const rules)
+{
+ refcount_acquire(&rules->use_count);
+}
+
+static void
+drop_rules(struct rules *const rules)
+{
+ if (refcount_release(&rules->use_count))
+ toast_rules(rules);
+}
+
+#ifdef INVARIANTS
+static void
+check_rules_use_count(const struct rules *const rules, u_int expected)
+{
+ const u_int use_count = refcount_load(&rules->use_count);
+
+ if (use_count != expected)
+ panic("MAC/do: Rules at %p: Use count is %u, expected %u",
+ rules, use_count, expected);
+}
+#else
+#define check_rules_use_count(...)
+#endif /* INVARIANTS */
+
+/*
+ * OSD destructor for slot 'osd_jail_slot'.
+ *
+ * Called with 'value' not NULL. We have arranged that it is only ever called
+ * when the corresponding jail goes down or at module unload.
+ */
+static void
+dealloc_jail_osd(void *const value)
+{
+ struct rules *const rules = value;
+
+ /*
+ * If called because the "holding" jail goes down, no one should be
+ * using the rules but us at this point because no threads of that jail
+ * (or its sub-jails) should currently be executing (in particular,
+ * currently executing setcred()). The case of module unload is more
+ * complex. Although the MAC framework takes care that no hook is
+ * called while a module is unloading, the unload could happen between
+ * two calls to MAC hooks in the course of, e.g., executing setcred(),
+ * where the rules' reference count has been bumped to keep them alive
+ * even if the rules on the "holding" jail has been concurrently
+ * changed. These other references are held in our thread OSD slot, so
+ * we ensure that all thread's slots are freed first in mac_do_destroy()
+ * to be able to check that only one reference remains.
+ */
+ check_rules_use_count(rules, 1);
+ toast_rules(rules);
+}
+
+/*
+ * Remove the rules specifically associated to a prison.
+ *
+ * In practice, this means that the rules become inherited (from the closest
+ * ascendant that has some).
+ *
+ * Destroys the 'osd_jail_slot' slot of the passed jail.
+ */
+static void
+remove_rules(struct prison *const pr)
+{
+ struct rules *old_rules;
+ int error __unused;
+
+ prison_lock(pr);
+ /*
+ * We go to the burden of extracting rules first instead of just letting
+ * osd_jail_del() calling dealloc_jail_osd() as we want to decrement
+ * their use count, and possibly free them, outside of the prison lock.
+ */
+ old_rules = osd_jail_get(pr, osd_jail_slot);
+ error = osd_jail_set(pr, osd_jail_slot, NULL);
+ /* osd_set() never fails nor allocate memory when 'value' is NULL. */
+ MPASS(error == 0);
+ /*
+ * This completely frees the OSD slot, but doesn't call the destructor
+ * since we've just put NULL in the slot.
+ */
+ osd_jail_del(pr, osd_jail_slot);
+ prison_unlock(pr);
+
+ if (old_rules != NULL)
+ drop_rules(old_rules);
+}
+
+/*
+ * Assign already built rules to a jail.
+ */
+static void
+set_rules(struct prison *const pr, struct rules *const rules)
+{
+ struct rules *old_rules;
+ void **rsv;
+
+ check_rules_use_count(rules, 0);
+ hold_rules(rules);
+ rsv = osd_reserve(osd_jail_slot);
+
+ prison_lock(pr);
+ old_rules = osd_jail_get(pr, osd_jail_slot);
+ osd_jail_set_reserved(pr, osd_jail_slot, rsv, rules);
+ prison_unlock(pr);
+ if (old_rules != NULL)
+ drop_rules(old_rules);
+}
+
+/*
+ * Assigns empty rules to a jail.
+ */
+static void
+set_empty_rules(struct prison *const pr)
+{
+ struct rules *const rules = alloc_rules();
+
+ set_rules(pr, rules);
+}
+
+/*
+ * Parse a rules specification and assign them to a jail.
+ *
+ * Returns the same error code as parse_rules() (which see).
+ */
+static int
+parse_and_set_rules(struct prison *const pr, const char *rules_string,
+ struct parse_error **const parse_error)
+{
+ struct rules *rules;
+ int error;
+
+ error = parse_rules(rules_string, &rules, parse_error);
+ if (error != 0)
+ return (error);
+ set_rules(pr, rules);
+ return (0);
+}
+
+static int
+mac_do_sysctl_rules(SYSCTL_HANDLER_ARGS)
+{
+ char *const buf = malloc(MAC_RULE_STRING_LEN, M_DO, M_WAITOK);
+ struct prison *const td_pr = req->td->td_ucred->cr_prison;
+ struct prison *pr;
+ struct rules *rules;
+ struct parse_error *parse_error;
+ int error;
+
+ rules = find_rules(td_pr, &pr);
+ strlcpy(buf, rules->string, MAC_RULE_STRING_LEN);
+ prison_unlock(pr);
+
+ error = sysctl_handle_string(oidp, buf, MAC_RULE_STRING_LEN, req);
+ if (error != 0 || req->newptr == NULL)
+ goto out;
+
+ /* Set our prison's rules, not that of the jail we inherited from. */
+ error = parse_and_set_rules(td_pr, buf, &parse_error);
+ if (error != 0) {
+ if (print_parse_error)
+ printf("MAC/do: Parse error at index %zu: %s\n",
+ parse_error->pos, parse_error->msg);
+ free_parse_error(parse_error);
+ }
+out:
+ free(buf, M_DO);
return (error);
}
SYSCTL_PROC(_security_mac_do, OID_AUTO, rules,
- CTLTYPE_STRING|CTLFLAG_RW|CTLFLAG_MPSAFE,
- 0, 0, sysctl_rules, "A",
+ CTLTYPE_STRING|CTLFLAG_RW|CTLFLAG_PRISON|CTLFLAG_MPSAFE,
+ 0, 0, mac_do_sysctl_rules, "A",
"Rules");
-static void
-destroy(struct mac_policy_conf *mpc)
-{
- osd_jail_deregister(mac_do_osd_jail_slot);
- toast_rules(&rules0.head);
-}
-static void
-mac_do_alloc_prison(struct prison *pr, struct mac_do_rule **lrp)
-{
- struct prison *ppr;
- struct mac_do_rule *rules, *new_rules;
- void **rsv;
-
- rules = mac_do_rule_find(pr, &ppr);
- if (ppr == pr)
- goto done;
-
- mtx_unlock(&ppr->pr_mtx);
- new_rules = malloc(sizeof(*new_rules), M_PRISON, M_WAITOK|M_ZERO);
- rsv = osd_reserve(mac_do_osd_jail_slot);
- rules = mac_do_rule_find(pr, &ppr);
- if (ppr == pr) {
- free(new_rules, M_PRISON);
- osd_free_reserved(rsv);
- goto done;
- }
- mtx_lock(&pr->pr_mtx);
- osd_jail_set_reserved(pr, mac_do_osd_jail_slot, rsv, new_rules);
- TAILQ_INIT(&new_rules->head);
-done:
- if (lrp != NULL)
- *lrp = rules;
- mtx_unlock(&pr->pr_mtx);
- mtx_unlock(&ppr->pr_mtx);
-}
-
-static void
-mac_do_dealloc_prison(void *data)
-{
- struct mac_do_rule *r = data;
-
- toast_rules(&r->head);
-}
-
-static int
-mac_do_prison_set(void *obj, void *data)
-{
- struct prison *pr = obj;
- struct vfsoptlist *opts = data;
- struct rulehead head, saved_head;
- struct mac_do_rule *rules;
- char *rules_string, *copy_string;
- int error, jsys, len;
-
- error = vfs_copyopt(opts, "mdo", &jsys, sizeof(jsys));
- if (error == ENOENT)
- jsys = -1;
- error = vfs_getopt(opts, "mdo.rules", (void **)&rules_string, &len);
- if (error == ENOENT)
- rules = NULL;
- else
- jsys = JAIL_SYS_NEW;
- switch (jsys) {
- case JAIL_SYS_INHERIT:
- mtx_lock(&pr->pr_mtx);
- osd_jail_del(pr, mac_do_osd_jail_slot);
- mtx_unlock(&pr->pr_mtx);
- break;
- case JAIL_SYS_NEW:
- mac_do_alloc_prison(pr, &rules);
- if (rules_string == NULL)
- break;
- copy_string = strdup(rules_string, M_DO);
- TAILQ_INIT(&head);
- error = parse_rules(copy_string, &head);
- free(copy_string, M_DO);
- if (error)
- return (1);
- TAILQ_INIT(&saved_head);
- mtx_lock(&pr->pr_mtx);
- TAILQ_CONCAT(&saved_head, &rules->head, r_entries);
- TAILQ_CONCAT(&rules->head, &head, r_entries);
- strlcpy(rules->string, rules_string, MAC_RULE_STRING_LEN);
- mtx_unlock(&pr->pr_mtx);
- toast_rules(&saved_head);
- break;
- }
- return (0);
-}
-
-SYSCTL_JAIL_PARAM_SYS_NODE(mdo, CTLFLAG_RW, "Jail MAC/do parameters");
-SYSCTL_JAIL_PARAM_STRING(_mdo, rules, CTLFLAG_RW, MAC_RULE_STRING_LEN,
+SYSCTL_JAIL_PARAM_SYS_SUBNODE(mac, do, CTLFLAG_RW, "Jail MAC/do parameters");
+SYSCTL_JAIL_PARAM_STRING(_mac_do, rules, CTLFLAG_RW, MAC_RULE_STRING_LEN,
"Jail MAC/do rules");
+
static int
-mac_do_prison_get(void *obj, void *data)
+mac_do_jail_create(void *obj, void *data __unused)
{
- struct prison *ppr, *pr = obj;
- struct vfsoptlist *opts = data;
- struct mac_do_rule *rules;
+ struct prison *const pr = obj;
+
+ set_empty_rules(pr);
+ return (0);
+}
+
+static int
+mac_do_jail_get(void *obj, void *data)
+{
+ struct prison *ppr, *const pr = obj;
+ struct vfsoptlist *const opts = data;
+ struct rules *rules;
int jsys, error;
- rules = mac_do_rule_find(pr, &ppr);
- error = vfs_setopt(opts, "mdo", &jsys, sizeof(jsys));
+ rules = find_rules(pr, &ppr);
+
+ jsys = pr == ppr ?
+ (STAILQ_EMPTY(&rules->head) ? JAIL_SYS_DISABLE : JAIL_SYS_NEW) :
+ JAIL_SYS_INHERIT;
+ error = vfs_setopt(opts, "mac.do", &jsys, sizeof(jsys));
if (error != 0 && error != ENOENT)
goto done;
- error = vfs_setopts(opts, "mdo.rules", rules->string);
+
+ error = vfs_setopts(opts, "mac.do.rules", rules->string);
if (error != 0 && error != ENOENT)
goto done;
- mtx_unlock(&ppr->pr_mtx);
+
error = 0;
done:
- return (0);
-}
-
-static int
-mac_do_prison_create(void *obj, void *data __unused)
-{
- struct prison *pr = obj;
-
- mac_do_alloc_prison(pr, NULL);
- return (0);
-}
-
-static int
-mac_do_prison_remove(void *obj, void *data __unused)
-{
- struct prison *pr = obj;
- struct mac_do_rule *r;
-
- mtx_lock(&pr->pr_mtx);
- r = osd_jail_get(pr, mac_do_osd_jail_slot);
- mtx_unlock(&pr->pr_mtx);
- toast_rules(&r->head);
- return (0);
-}
-
-static int
-mac_do_prison_check(void *obj, void *data)
-{
- struct vfsoptlist *opts = data;
- char *rules_string;
- int error, jsys, len;
-
- error = vfs_copyopt(opts, "mdo", &jsys, sizeof(jsys));
- if (error != ENOENT) {
- if (error != 0)
- return (error);
- if (jsys != JAIL_SYS_NEW && jsys != JAIL_SYS_INHERIT)
- return (EINVAL);
- }
- error = vfs_getopt(opts, "mdo.rules", (void **)&rules_string, &len);
- if (error != ENOENT) {
- if (error != 0)
- return (error);
- if (len > MAC_RULE_STRING_LEN) {
- vfs_opterror(opts, "mdo.rules too long");
- return (ENAMETOOLONG);
- }
- }
- if (error == ENOENT)
- error = 0;
+ prison_unlock(ppr);
return (error);
}
-static void
-init(struct mac_policy_conf *mpc)
+/*
+ * We perform only cheap checks here, i.e., we do not really parse the rules
+ * specification string, if any.
+ */
+static int
+mac_do_jail_check(void *obj, void *data)
{
- static osd_method_t methods[PR_MAXMETHOD] = {
- [PR_METHOD_CREATE] = mac_do_prison_create,
- [PR_METHOD_GET] = mac_do_prison_get,
- [PR_METHOD_SET] = mac_do_prison_set,
- [PR_METHOD_CHECK] = mac_do_prison_check,
- [PR_METHOD_REMOVE] = mac_do_prison_remove,
- };
- struct prison *pr;
+ struct vfsoptlist *opts = data;
+ char *rules_string;
+ int error, jsys, size;
- mac_do_osd_jail_slot = osd_jail_register(mac_do_dealloc_prison, methods);
- TAILQ_INIT(&rules0.head);
- sx_slock(&allprison_lock);
- TAILQ_FOREACH(pr, &allprison, pr_list)
- mac_do_alloc_prison(pr, NULL);
- sx_sunlock(&allprison_lock);
+ error = vfs_copyopt(opts, "mac.do", &jsys, sizeof(jsys));
+ if (error == ENOENT)
+ jsys = -1;
+ else {
+ if (error != 0)
+ return (error);
+ if (jsys != JAIL_SYS_DISABLE && jsys != JAIL_SYS_NEW &&
+ jsys != JAIL_SYS_INHERIT)
+ return (EINVAL);
+ }
+
+ /*
+ * We use vfs_getopt() here instead of vfs_getopts() to get the length.
+ * We perform the additional checks done by the latter here, even if
+ * jail_set() calls vfs_getopts() itself later (they becoming
+ * inconsistent wouldn't cause any security problem).
+ */
+ error = vfs_getopt(opts, "mac.do.rules", (void**)&rules_string, &size);
+ if (error == ENOENT) {
+ /*
+ * Default (in absence of "mac.do.rules") is to disable (and, in
+ * particular, not inherit).
+ */
+ if (jsys == -1)
+ jsys = JAIL_SYS_DISABLE;
+
+ if (jsys == JAIL_SYS_NEW) {
+ vfs_opterror(opts, "'mac.do.rules' must be specified "
+ "given 'mac.do''s value");
+ return (EINVAL);
+ }
+
+ /* Absence of "mac.do.rules" at this point is OK. */
+ error = 0;
+ } else {
+ if (error != 0)
+ return (error);
+
+ /* Not a proper string. */
+ if (size == 0 || rules_string[size - 1] != '\0') {
+ vfs_opterror(opts, "'mac.do.rules' not a proper string");
+ return (EINVAL);
+ }
+
+ if (size > MAC_RULE_STRING_LEN) {
+ vfs_opterror(opts, "'mdo.rules' too long");
+ return (ENAMETOOLONG);
+ }
+
+ if (jsys == -1)
+ /* Default (if "mac.do.rules" is present). */
+ jsys = rules_string[0] == '\0' ? JAIL_SYS_DISABLE :
+ JAIL_SYS_NEW;
+
+ /*
+ * Be liberal and accept JAIL_SYS_DISABLE and JAIL_SYS_INHERIT
+ * with an explicit empty rules specification.
+ */
+ switch (jsys) {
+ case JAIL_SYS_DISABLE:
+ case JAIL_SYS_INHERIT:
+ if (rules_string[0] != '\0') {
+ vfs_opterror(opts, "'mac.do.rules' specified "
+ "but should not given 'mac.do''s value");
+ return (EINVAL);
+ }
+ break;
+ }
+ }
+
+ return (error);
+}
+
+static int
+mac_do_jail_set(void *obj, void *data)
+{
+ struct prison *pr = obj;
+ struct vfsoptlist *opts = data;
+ char *rules_string;
+ struct parse_error *parse_error;
+ int error, jsys;
+
+ /*
+ * The invariants checks used below correspond to what has already been
+ * checked in jail_check() above.
+ */
+
+ error = vfs_copyopt(opts, "mac.do", &jsys, sizeof(jsys));
+ MPASS(error == 0 || error == ENOENT);
+ if (error != 0)
+ jsys = -1; /* Mark unfilled. */
+
+ rules_string = vfs_getopts(opts, "mac.do.rules", &error);
+ MPASS(error == 0 || error == ENOENT);
+ if (error == 0) {
+ MPASS(strlen(rules_string) < MAC_RULE_STRING_LEN);
+ if (jsys == -1)
+ /* Default (if "mac.do.rules" is present). */
+ jsys = rules_string[0] == '\0' ? JAIL_SYS_DISABLE :
+ JAIL_SYS_NEW;
+ else
+ MPASS(jsys == JAIL_SYS_NEW ||
+ ((jsys == JAIL_SYS_DISABLE ||
+ jsys == JAIL_SYS_INHERIT) &&
+ rules_string[0] == '\0'));
+ } else {
+ MPASS(jsys != JAIL_SYS_NEW);
+ if (jsys == -1)
+ /*
+ * Default (in absence of "mac.do.rules") is to disable
+ * (and, in particular, not inherit).
+ */
+ jsys = JAIL_SYS_DISABLE;
+ /* If disabled, we'll store an empty rule specification. */
+ if (jsys == JAIL_SYS_DISABLE)
+ rules_string = "";
+ }
+
+ switch (jsys) {
+ case JAIL_SYS_INHERIT:
+ remove_rules(pr);
+ error = 0;
+ break;
+ case JAIL_SYS_DISABLE:
+ case JAIL_SYS_NEW:
+ error = parse_and_set_rules(pr, rules_string, &parse_error);
+ if (error != 0) {
+ vfs_opterror(opts,
+ "MAC/do: Parse error at index %zu: %s\n",
+ parse_error->pos, parse_error->msg);
+ free_parse_error(parse_error);
+ }
+ break;
+ default:
+ __assert_unreachable();
+ }
+ return (error);
+}
+
+/*
+ * OSD jail methods.
+ *
+ * There is no PR_METHOD_REMOVE, as OSD storage is destroyed by the common jail
+ * code (see prison_cleanup()), which triggers a run of our dealloc_jail_osd()
+ * destructor.
+ */
+static const osd_method_t osd_methods[PR_MAXMETHOD] = {
+ [PR_METHOD_CREATE] = mac_do_jail_create,
+ [PR_METHOD_GET] = mac_do_jail_get,
+ [PR_METHOD_CHECK] = mac_do_jail_check,
+ [PR_METHOD_SET] = mac_do_jail_set,
+};
+
+
+/*
+ * Common header structure.
+ *
+ * Each structure that is used to pass information between some MAC check
+ * function and priv_grant() must start with this header.
+ */
+struct mac_do_data_header {
+ /* Size of the allocated buffer holding the containing structure. */
+ size_t allocated_size;
+ /* Full size of the containing structure. */
+ size_t size;
+ /*
+ * For convenience, we use privilege numbers as an identifier for the
+ * containing structure's type, since there is one distinct privilege
+ * for each privilege changing function we are supporting. 0 in 'priv'
+ * indicates this header is uninitialized.
+ */
+ int priv;
+ /* Rules to apply. */
+ struct rules *rules;
+};
+
+/*
+ * The case of unusable or absent per-thread data can actually happen as nothing
+ * prevents, e.g., priv_check*() with privilege 'priv' to be called standalone,
+ * as it is currently by, e.g., the Linux emulator for PRIV_CRED_SETUID. We
+ * interpret such calls to priv_check*() as full, unrestricted requests for
+ * 'priv', contrary to what we're doing here for selected operations, and
+ * consequently will not grant the requested privilege.
+ *
+ * Also, we protect ourselves from a concurrent change of 'do_enabled' while
+ * a call to setcred() is in progress by storing the rules per-thread
+ * which is then consulted by each successive hook so that they all have
+ * a coherent view of the specifications, and we empty the slot (actually, mark
+ * it as empty) when MAC/do is disabled.
+ */
+static int
+check_data_usable(const void *const data, const size_t size, const int priv)
+{
+ const struct mac_do_data_header *const hdr = data;
+
+ if (hdr == NULL || hdr->priv == 0)
+ return (ENOENT);
+ /*
+ * Impacting changes in the protocols we are based on... Don't crash in
+ * production.
+ */
+ if (hdr->priv != priv) {
+ MPASS(hdr->priv == priv);
+ return (EBUSY);
+ }
+ MPASS(hdr->size == size);
+ MPASS(hdr->size <= hdr->allocated_size);
+ return (0);
+}
+
+static void
+clear_data(void *const data)
+{
+ struct mac_do_data_header *const hdr = data;
+
+ if (hdr != NULL) {
+ drop_rules(hdr->rules);
+ /* We don't deallocate so as to save time on next access. */
+ hdr->priv = 0;
+ }
+}
+
+static void *
+fetch_data(void)
+{
+ return (osd_thread_get_unlocked(curthread, osd_thread_slot));
}
static bool
-rule_is_valid(struct ucred *cred, struct rule *r)
+is_data_reusable(const void *const data, const size_t size)
{
- if (r->from_type == RULE_UID && r->f_uid == cred->cr_uid)
+ const struct mac_do_data_header *const hdr = data;
+
+ return (hdr != NULL && size <= hdr->allocated_size);
+}
+
+static void
+set_data_header(void *const data, const size_t size, const int priv,
+ struct rules *const rules)
+{
+ struct mac_do_data_header *const hdr = data;
+
+ MPASS(hdr->priv == 0);
+ MPASS(priv != 0);
+ MPASS(size <= hdr->allocated_size);
+ hdr->size = size;
+ hdr->priv = priv;
+ hdr->rules = rules;
+}
+
+/* The proc lock (and any other non-sleepable lock) must not be held. */
+static void *
+alloc_data(void *const data, const size_t size)
+{
+ struct mac_do_data_header *const hdr = realloc(data, size, M_DO,
+ M_WAITOK);
+
+ MPASS(size >= sizeof(struct mac_do_data_header));
+ hdr->allocated_size = size;
+ hdr->priv = 0;
+ if (hdr != data) {
+ /*
+ * This call either reuses the existing memory allocated for the
+ * slot or tries to allocate some without blocking.
+ */
+ int error = osd_thread_set(curthread, osd_thread_slot, hdr);
+
+ if (error != 0) {
+ /* Going to make a M_WAITOK allocation. */
+ void **const rsv = osd_reserve(osd_thread_slot);
+
+ error = osd_thread_set_reserved(curthread,
+ osd_thread_slot, rsv, hdr);
+ MPASS(error == 0);
+ }
+ }
+ return (hdr);
+}
+
+/* Destructor for 'osd_thread_slot'. */
+static void
+dealloc_thread_osd(void *const value)
+{
+ free(value, M_DO);
+}
+
+/*
+ * Whether to grant access to some primary group according to flags.
+ *
+ * The passed 'flags' must be those of a rule's matching GID, or the IT_GID type
+ * flags when MDF_CURRENT has been matched.
+ *
+ * Return values:
+ * - 0: Access granted.
+ * - EJUSTRETURN: Flags are agnostic.
+ */
+static int
+grant_primary_group_from_flags(const flags_t flags)
+{
+ return (flags & MDF_PRIMARY ? 0 : EJUSTRETURN);
+}
+
+/*
+ * Same as grant_primary_group_from_flags(), but for supplementary groups.
+ *
+ * Return values:
+ * - 0: Access granted.
+ * - EJUSTRETURN: Flags are agnostic.
+ * - EPERM: Access denied.
+ */
+static int
+grant_supplementary_group_from_flags(const flags_t flags)
+{
+ if (flags & MDF_SUPP_MASK)
+ return (flags & MDF_SUPP_DONT ? EPERM : 0);
+
+ return (EJUSTRETURN);
+}
+
+/*
+ * Check whether to grant access to supplementary groups.
+ */
+static int
+rule_grant_supplementary_groups(const struct rule *const rule,
+ const struct ucred *const old_cred, const struct ucred *const new_cred)
+{
+ const int nb_groups = new_cred->cr_ngroups - 1;
+ const gid_t *const groups = new_cred->cr_groups + 1;
+ int error;
+ id_nb_t r_idx = 0;
+
+ if (rule->gid_flags & MDF_ANY_SUPP)
+ /*
+ * All supplementary groups will be accepted, no need to loop
+ * over them.
+ */
+ return (0);
+
+ for (int i = 0; i < nb_groups; ++i) {
+ const int gid = groups[i];
+
+ /* Was MDF_CURRENT specified, and is 'gid' a current GID? */
+ if ((rule->gid_flags & MDF_CURRENT) &&
+ group_is_supplementary(gid, old_cred)) {
+ error = grant_supplementary_group_from_flags
+ (rule->gid_flags);
+ if (error == 0)
+ continue;
+ /*
+ * MDF_SUPP_DONT must have been handled in the relevant
+ * check_*() function (or these functions weren't
+ * executed at all (e.g., Linux emulation), and we
+ * shouldn't get this deep).
+ */
+ MPASS(error == EJUSTRETURN);
+ }
+
+ /*
+ * Search by GID for a corresponding 'struct id_spec'.
+ *
+ * As both the new credentials and the groups in the rule are
+ * sorted, we just need to browse the rules' groups only once
+ * for each one.
+ */
+ for (; r_idx < rule->gids_nb; ++r_idx) {
+ const int r_gid = rule->gids[r_idx].id;
+
+ if (gid == r_gid) {
+ error = grant_supplementary_group_from_flags
+ (rule->gids[r_idx].flags);
+ if (error == 0)
+ goto next_group;
+ /* Same as above. */
+ MPASS(error == EJUSTRETURN);
+ break;
+ } else if (gid < r_gid)
+ break;
+ }
+
+ /* 'gid' wasn't accepted. */
+ return (EPERM);
+next_group:
+ ;
+ }
+
+ return (0);
+}
+
+static int
+rule_grant_primary_group(const struct rule *const rule,
+ const struct ucred *const old_cred, const gid_t gid)
+{
+ struct id_spec gid_is = {};
+ const struct id_spec *found_is;
+ int error;
+
+ if (rule->gid_flags & MDF_ANY)
+ return (0);
+
+ /* Was MDF_CURRENT specified, and is 'gid' a current GID? */
+ if ((rule->gid_flags & MDF_CURRENT) &&
+ group_is_primary(gid, old_cred)) {
+ error = grant_primary_group_from_flags(rule->gid_flags);
+ if (error == 0)
+ return (0);
+ }
+
+ /* Search by GID for a corresponding 'struct id_spec'. */
+ gid_is.id = gid;
+ found_is = bsearch(&gid_is, rule->gids, rule->gids_nb,
+ sizeof(*rule->gids), id_spec_cmp);
+
+ if (found_is != NULL) {
+ error = grant_primary_group_from_flags(found_is->flags);
+ if (error == 0)
+ return (0);
+ }
+
+ return (EPERM);
+}
+
+static int
+rule_grant_primary_groups(const struct rule *const rule,
+ const struct ucred *const old_cred, const struct ucred *const new_cred)
+{
+ int error;
+
+ /* Shortcut. */
+ if (rule->gid_flags & MDF_ANY)
+ return (0);
+
+ error = rule_grant_primary_group(rule, old_cred, new_cred->cr_gid);
+ if (error != 0)
+ return (error);
+ error = rule_grant_primary_group(rule, old_cred, new_cred->cr_rgid);
+ if (error != 0)
+ return (error);
+ error = rule_grant_primary_group(rule, old_cred, new_cred->cr_svgid);
+ if (error != 0)
+ return (error);
+ return (0);
+}
+
+static bool
+user_is_current(const uid_t uid, const struct ucred *const old_cred)
+{
+ return (uid == old_cred->cr_uid || uid == old_cred->cr_ruid ||
+ uid == old_cred->cr_svuid);
+}
+
+static int
+rule_grant_user(const struct rule *const rule,
+ const struct ucred *const old_cred, const uid_t uid)
+{
+ struct id_spec uid_is = {};
+ const struct id_spec *found_is;
+
+ if (rule->uid_flags & MDF_ANY)
+ return (0);
+
+ /* Was MDF_CURRENT specified, and is 'uid' a current UID? */
+ if ((rule->uid_flags & MDF_CURRENT) &&
+ user_is_current(uid, old_cred))
+ return (0);
+
+ /* Search by UID for a corresponding 'struct id_spec'. */
+ uid_is.id = uid;
+ found_is = bsearch(&uid_is, rule->uids, rule->uids_nb,
+ sizeof(*rule->uids), id_spec_cmp);
+
+ if (found_is != NULL)
+ return (0);
+
+ return (EPERM);
+}
+
+static int
+rule_grant_users(const struct rule *const rule,
+ const struct ucred *const old_cred, const struct ucred *const new_cred)
+{
+ int error;
+
+ /* Shortcut. */
+ if (rule->uid_flags & MDF_ANY)
+ return (0);
+
+ error = rule_grant_user(rule, old_cred, new_cred->cr_uid);
+ if (error != 0)
+ return (error);
+ error = rule_grant_user(rule, old_cred, new_cred->cr_ruid);
+ if (error != 0)
+ return (error);
+ error = rule_grant_user(rule, old_cred, new_cred->cr_svuid);
+ if (error != 0)
+ return (error);
+
+ return (0);
+}
+
+static int
+rule_grant_setcred(const struct rule *const rule,
+ const struct ucred *const old_cred, const struct ucred *const new_cred)
+{
+ int error;
+
+ error = rule_grant_users(rule, old_cred, new_cred);
+ if (error != 0)
+ return (error);
+ error = rule_grant_primary_groups(rule, old_cred, new_cred);
+ if (error != 0)
+ return (error);
+ error = rule_grant_supplementary_groups(rule, old_cred, new_cred);
+ if (error != 0)
+ return (error);
+
+ return (0);
+}
+
+static bool
+rule_applies(const struct rule *const rule, const struct ucred *const cred)
+{
+ if (rule->from_type == IT_UID && rule->from_id == cred->cr_uid)
return (true);
- if (r->from_type == RULE_GID && groupmember(r->f_gid, cred))
+ if (rule->from_type == IT_GID && groupmember(rule->from_id, cred))
return (true);
return (false);
}
-static int
-priv_grant(struct ucred *cred, int priv)
-{
- struct rule *r;
- struct prison *pr;
- struct mac_do_rule *rule;
-
- if (do_enabled == 0)
- return (EPERM);
-
- rule = mac_do_rule_find(cred->cr_prison, &pr);
- TAILQ_FOREACH(r, &rule->head, r_entries) {
- if (rule_is_valid(cred, r)) {
- switch (priv) {
- case PRIV_CRED_SETGROUPS:
- case PRIV_CRED_SETUID:
- mtx_unlock(&pr->pr_mtx);
- return (0);
- default:
- break;
- }
- }
- }
- mtx_unlock(&pr->pr_mtx);
- return (EPERM);
-}
+/*
+ * To pass data between check_setcred() and priv_grant() (on PRIV_CRED_SETCRED).
+ */
+struct mac_do_setcred_data {
+ struct mac_do_data_header hdr;
+ const struct ucred *new_cred;
+ u_int setcred_flags;
+};
static int
-check_setgroups(struct ucred *cred, int ngrp, gid_t *groups)
+mac_do_priv_grant(struct ucred *cred, int priv)
{
- struct rule *r;
- char *fullpath = NULL;
- char *freebuf = NULL;
- struct prison *pr;
- struct mac_do_rule *rule;
-
- if (do_enabled == 0)
- return (0);
- if (cred->cr_uid == 0)
- return (0);
-
- if (vn_fullpath(curproc->p_textvp, &fullpath, &freebuf) != 0)
- return (EPERM);
- if (strcmp(fullpath, "/usr/bin/mdo") != 0) {
- free(freebuf, M_TEMP);
- return (EPERM);
- }
- free(freebuf, M_TEMP);
-
- rule = mac_do_rule_find(cred->cr_prison, &pr);
- TAILQ_FOREACH(r, &rule->head, r_entries) {
- if (rule_is_valid(cred, r)) {
- mtx_unlock(&pr->pr_mtx);
- return (0);
- }
- }
- mtx_unlock(&pr->pr_mtx);
-
- return (EPERM);
-}
-
-static int
-check_setuid(struct ucred *cred, uid_t uid)
-{
- struct rule *r;
+ struct mac_do_setcred_data *const data = fetch_data();
+ const struct rules *rules;
+ const struct ucred *new_cred;
+ const struct rule *rule;
+ u_int setcred_flags;
int error;
- char *fullpath = NULL;
- char *freebuf = NULL;
- struct prison *pr;
- struct mac_do_rule *rule;
- if (do_enabled == 0)
- return (0);
- if (cred->cr_uid == uid || cred->cr_uid == 0 || cred->cr_ruid == 0)
- return (0);
-
- if (vn_fullpath(curproc->p_textvp, &fullpath, &freebuf) != 0)
+ /* Bail out fast if we aren't concerned. */
+ if (priv != PRIV_CRED_SETCRED)
return (EPERM);
- if (strcmp(fullpath, "/usr/bin/mdo") != 0) {
- free(freebuf, M_TEMP);
- return (EPERM);
- }
- free(freebuf, M_TEMP);
+ /*
+ * Do we have to do something?
+ */
+ if (check_data_usable(data, sizeof(*data), priv) != 0)
+ /* No. */
+ return (EPERM);
+
+ rules = data->hdr.rules;
+ new_cred = data->new_cred;
+ KASSERT(new_cred != NULL,
+ ("priv_check*() called before mac_cred_check_setcred()"));
+ setcred_flags = data->setcred_flags;
+
+ /*
+ * Rule out new versions of setcred() (in case, e.g., they introduce new
+ * flags), as well as MAC label changes as we do not (currently) support
+ * validating such changes. This may be improved by actually checking
+ * whether the requested label and the current one would differ. For
+ * now, we just check for SETCREDF_MAC_LABEL's presence.
+ */
+ if (SETCREDF_TO_VERSION(setcred_flags) != 0 ||
+ (setcred_flags & SETCREDF_MAC_LABEL) != 0)
+ return (EPERM);
+
+ /*
+ * Browse rules, and for those that match the requestor, call specific
+ * privilege granting functions interpreting the "to"/"target" part.
+ */
error = EPERM;
- rule = mac_do_rule_find(cred->cr_prison, &pr);
- TAILQ_FOREACH(r, &rule->head, r_entries) {
- if (r->from_type == RULE_UID) {
- if (cred->cr_uid != r->f_uid)
- continue;
- if (r->to_type == RULE_ANY) {
- error = 0;
- break;
- }
- if (r->to_type == RULE_UID && uid == r->t_uid) {
- error = 0;
- break;
- }
- }
- if (r->from_type == RULE_GID) {
- if (!groupmember(r->f_gid, cred))
- continue;
- if (r->to_type == RULE_ANY) {
- error = 0;
- break;
- }
- if (r->to_type == RULE_UID && uid == r->t_uid) {
- error = 0;
- break;
- }
- }
- }
- mtx_unlock(&pr->pr_mtx);
+ STAILQ_FOREACH(rule, &rules->head, r_entries)
+ if (rule_applies(rule, cred)) {
+ error = rule_grant_setcred(rule, cred, new_cred);
+ if (error != EPERM)
+ break;
+ }
+
return (error);
}
+static int
+rule_check_set_supplementary_groups(const struct rule *const rule,
+ const struct ucred *const old_cred, const struct ucred *const new_cred)
+{
+ const flags_t gid_flags = rule->gid_flags;
+
+ if (gid_flags & MDF_MAY_REJ_SUPP) {
+ const bool has_current_rej = (gid_flags & MDF_CURRENT) &&
+ ((gid_flags & MDF_SUPP_MUST) || (gid_flags & MDF_SUPP_DONT));
+ int o_idx, n_idx, r_idx;
+
+ o_idx = r_idx = 1;
+ for (n_idx = 1; n_idx < new_cred->cr_ngroups; ++n_idx) {
+ const int gid = new_cred->cr_groups[n_idx];
+
+ if (has_current_rej) {
+ /*
+ * Linear search, as both supplementary groups
+ * arrays are sorted.
+ */
+ for (; o_idx < old_cred->cr_ngroups; ++o_idx) {
+ const gid_t o_gid =
+ old_cred->cr_groups[o_idx];
+
+ if (o_gid < gid) {
+ if (gid_flags & MDF_SUPP_MUST)
+ return (EPERM);
+ } else if (o_gid == gid) {
+ if (gid_flags & MDF_SUPP_DONT)
+ return (EPERM);
+ }
+ else
+ break;
+ }
+ }
+
+ /*
+ * Linear search, as both the supplementary groups
+ * array and the GIDs in the rule are sorted.
+ */
+ for (; r_idx < rule->gids_nb; ++r_idx) {
+ const struct id_spec *const is =
+ &rule->gids[r_idx];
+
+ if (is->id < gid) {
+ if (is->flags & MDF_SUPP_MUST)
+ /* Not present but mandatory. */
+ return (EPERM);
+ } else if (is->id == gid) {
+ if (is->flags & MDF_SUPP_DONT)
+ /* Present but forbidden. */
+ return (EPERM);
+ } else
+ break;
+ }
+ }
+
+ /*
+ * If we must have all current groups and we didn't browse all
+ * of them at this point (because the remaining ones have GIDs
+ * greater than the last requested group), we are simply missing
+ * them.
+ */
+ if ((gid_flags & MDF_CURRENT) && (gid_flags & MDF_SUPP_MUST) &&
+ o_idx < old_cred->cr_ngroups)
+ return (EPERM);
+ /*
+ * Similarly, we have to finish browsing all GIDs from the rule
+ * in case some are marked mandatory.
+ */
+ for (; r_idx < rule->gids_nb; ++r_idx) {
+ const struct id_spec *const is = &rule->gids[r_idx];
+
+ if (is->flags & MDF_SUPP_MUST)
+ return (EPERM);
+ }
+ }
+
+ return (0);
+}
+
+static int
+check_proc(void)
+{
+ char *path, *to_free;
+ int error;
+
+ /*
+ * Only grant privileges if requested by the right executable.
+ *
+ * XXXOC: We may want to base this check on a tunable path and/or
+ * a specific MAC label. Going even further, e.g., envisioning to
+ * completely replace the path check with the latter, we would need to
+ * install FreeBSD on a FS with multilabel enabled by default, which in
+ * practice entails adding an option to ZFS to set MNT_MULTILABEL
+ * automatically on mounts, ensuring that root (and more if using
+ * different partitions) ZFS or UFS filesystems are created with
+ * multilabel turned on, and having the installation procedure support
+ * setting a MAC label per file (perhaps via additions to mtree(1)). So
+ * this probably isn't going to happen overnight, if ever.
+ */
+ if (vn_fullpath(curproc->p_textvp, &path, &to_free) != 0)
+ return (EPERM);
+ error = strcmp(path, "/usr/bin/mdo") == 0 ? 0 : EPERM;
+ free(to_free, M_TEMP);
+ return (error);
+}
+
+static void
+mac_do_setcred_enter(void)
+{
+ struct rules *rules;
+ struct prison *pr;
+ struct mac_do_setcred_data * data;
+ int error;
+
+ /*
+ * If not enabled, don't prepare data. Other hooks will check for that
+ * to know if they have to do something.
+ */
+ if (do_enabled == 0)
+ return;
+
+ /*
+ * MAC/do only applies to a process launched from a given executable.
+ * For other processes, we just won't intervene (we don't deny requests,
+ * nor do we grant privileges to them).
+ */
+ error = check_proc();
+ if (error != 0)
+ return;
+
+ /*
+ * Find the currently applicable rules.
+ */
+ rules = find_rules(curproc->p_ucred->cr_prison, &pr);
+ hold_rules(rules);
+ prison_unlock(pr);
+
+ /*
+ * Setup thread data to be used by other hooks.
+ */
+ data = fetch_data();
+ if (!is_data_reusable(data, sizeof(*data)))
+ data = alloc_data(data, sizeof(*data));
+ set_data_header(data, sizeof(*data), PRIV_CRED_SETCRED, rules);
+ /* Not really necessary, but helps to catch programming errors. */
+ data->new_cred = NULL;
+ data->setcred_flags = 0;
+}
+
+static int
+mac_do_check_setcred(u_int flags, const struct ucred *const old_cred,
+ struct ucred *const new_cred)
+{
+ struct mac_do_setcred_data *const data = fetch_data();
+ const struct rule *rule;
+
+ /*
+ * Do we have to do something?
+ */
+ if (check_data_usable(data, sizeof(*data), PRIV_CRED_SETCRED) != 0)
+ /* No. */
+ return (0);
+
+ /*
+ * Check for (supplementary) mandatory and forbidden groups.
+ */
+ STAILQ_FOREACH(rule, &data->hdr.rules->head, r_entries) {
+ if (rule_applies(rule, old_cred)) {
+ int error = rule_check_set_supplementary_groups(rule,
+ old_cred, new_cred);
+
+ if (error != 0)
+ return (error);
+ }
+ }
+
+ /*
+ * Keep track of the setcred() flags and the new credentials for
+ * priv_check*().
+ */
+ data->new_cred = new_cred;
+ data->setcred_flags = flags;
+
+ return (0);
+}
+
+static void
+mac_do_setcred_exit(void)
+{
+ struct mac_do_setcred_data *const data = fetch_data();
+
+ if (check_data_usable(data, sizeof(*data), PRIV_CRED_SETCRED) == 0)
+ /*
+ * This doesn't deallocate the small per-thread data storage,
+ * which can be reused on subsequent calls. (That data is of
+ * course deallocated as the current thread dies or this module
+ * is unloaded.)
+ */
+ clear_data(data);
+}
+
+static void
+mac_do_init(struct mac_policy_conf *mpc)
+{
+ struct prison *pr;
+
+ osd_jail_slot = osd_jail_register(dealloc_jail_osd, osd_methods);
+ set_empty_rules(&prison0);
+ sx_slock(&allprison_lock);
+ TAILQ_FOREACH(pr, &allprison, pr_list)
+ set_empty_rules(pr);
+ sx_sunlock(&allprison_lock);
+
+ osd_thread_slot = osd_thread_register(dealloc_thread_osd);
+}
+
+static void
+mac_do_destroy(struct mac_policy_conf *mpc)
+{
+ /*
+ * osd_thread_deregister() must be called before osd_jail_deregister(),
+ * for the reason explained in dealloc_jail_osd().
+ */
+ osd_thread_deregister(osd_thread_slot);
+ osd_jail_deregister(osd_jail_slot);
+}
+
static struct mac_policy_ops do_ops = {
- .mpo_destroy = destroy,
- .mpo_init = init,
- .mpo_cred_check_setuid = check_setuid,
- .mpo_cred_check_setgroups = check_setgroups,
- .mpo_priv_grant = priv_grant,
+ .mpo_init = mac_do_init,
+ .mpo_destroy = mac_do_destroy,
+ .mpo_cred_setcred_enter = mac_do_setcred_enter,
+ .mpo_cred_check_setcred = mac_do_check_setcred,
+ .mpo_cred_setcred_exit = mac_do_setcred_exit,
+ .mpo_priv_grant = mac_do_priv_grant,
};
-MAC_POLICY_SET(&do_ops, mac_do, "MAC/do",
- MPC_LOADTIME_FLAG_UNLOADOK, NULL);
+MAC_POLICY_SET(&do_ops, mac_do, "MAC/do", MPC_LOADTIME_FLAG_UNLOADOK, NULL);
MODULE_VERSION(mac_do, 1);
diff --git a/sys/security/mac_stub/mac_stub.c b/sys/security/mac_stub/mac_stub.c
--- a/sys/security/mac_stub/mac_stub.c
+++ b/sys/security/mac_stub/mac_stub.c
@@ -222,6 +222,26 @@
return (0);
}
+static void
+stub_cred_setcred_enter(void)
+{
+
+}
+
+static int
+stub_cred_check_setcred(u_int flags, const struct ucred *old_cred,
+ struct ucred *new_cred)
+{
+
+ return (0);
+}
+
+static void
+stub_cred_setcred_exit(void)
+{
+
+}
+
static int
stub_cred_check_setegid(struct ucred *cred, gid_t egid)
{
@@ -1688,6 +1708,9 @@
.mpo_cred_check_setaudit = stub_cred_check_setaudit,
.mpo_cred_check_setaudit_addr = stub_cred_check_setaudit_addr,
.mpo_cred_check_setauid = stub_cred_check_setauid,
+ .mpo_cred_setcred_enter = stub_cred_setcred_enter,
+ .mpo_cred_check_setcred = stub_cred_check_setcred,
+ .mpo_cred_setcred_exit = stub_cred_setcred_exit,
.mpo_cred_check_setegid = stub_cred_check_setegid,
.mpo_cred_check_seteuid = stub_cred_check_seteuid,
.mpo_cred_check_setgid = stub_cred_check_setgid,
diff --git a/sys/security/mac_test/mac_test.c b/sys/security/mac_test/mac_test.c
--- a/sys/security/mac_test/mac_test.c
+++ b/sys/security/mac_test/mac_test.c
@@ -257,6 +257,35 @@
return (0);
}
+COUNTER_DECL(cred_setcred_enter);
+static void
+test_cred_setcred_enter(void)
+{
+
+ COUNTER_INC(cred_setcred_enter);
+}
+
+COUNTER_DECL(cred_check_setcred);
+static int
+test_cred_check_setcred(u_int flags, const struct ucred *old_cred,
+ struct ucred *new_cred)
+{
+
+ LABEL_CHECK(old_cred->cr_label, MAGIC_CRED);
+ LABEL_CHECK(new_cred->cr_label, MAGIC_CRED);
+ COUNTER_INC(cred_check_setcred);
+
+ return (0);
+}
+
+COUNTER_DECL(cred_setcred_exit);
+static void
+test_cred_setcred_exit(void)
+{
+
+ COUNTER_INC(cred_setcred_exit);
+}
+
COUNTER_DECL(cred_check_setegid);
static int
test_cred_check_setegid(struct ucred *cred, gid_t egid)
@@ -3033,6 +3062,9 @@
.mpo_cred_check_setaudit = test_cred_check_setaudit,
.mpo_cred_check_setaudit_addr = test_cred_check_setaudit_addr,
.mpo_cred_check_setauid = test_cred_check_setauid,
+ .mpo_cred_setcred_enter = test_cred_setcred_enter,
+ .mpo_cred_check_setcred = test_cred_check_setcred,
+ .mpo_cred_setcred_exit = test_cred_setcred_exit,
.mpo_cred_check_seteuid = test_cred_check_seteuid,
.mpo_cred_check_setegid = test_cred_check_setegid,
.mpo_cred_check_setgid = test_cred_check_setgid,
diff --git a/sys/sys/priv.h b/sys/sys/priv.h
--- a/sys/sys/priv.h
+++ b/sys/sys/priv.h
@@ -105,7 +105,8 @@
#define PRIV_CRED_SETRESGID 58 /* setresgid. */
#define PRIV_SEEOTHERGIDS 59 /* Exempt bsd.seeothergids. */
#define PRIV_SEEOTHERUIDS 60 /* Exempt bsd.seeotheruids. */
-#define PRIV_SEEJAILPROC 61 /* Exempt from bsd.see_jail_proc. */
+#define PRIV_SEEJAILPROC 61 /* Exempt from bsd.see_jail_proc. */
+#define PRIV_CRED_SETCRED 62 /* setcred. */
/*
* Debugging privileges.
diff --git a/sys/sys/syscall.h b/sys/sys/syscall.h
--- a/sys/sys/syscall.h
+++ b/sys/sys/syscall.h
@@ -526,4 +526,5 @@
#define SYS_timerfd_settime 587
#define SYS_kcmp 588
#define SYS_getrlimitusage 589
-#define SYS_MAXSYSCALL 590
+#define SYS_setcred 590
+#define SYS_MAXSYSCALL 591
diff --git a/sys/sys/syscall.mk b/sys/sys/syscall.mk
--- a/sys/sys/syscall.mk
+++ b/sys/sys/syscall.mk
@@ -432,4 +432,5 @@
timerfd_gettime.o \
timerfd_settime.o \
kcmp.o \
- getrlimitusage.o
+ getrlimitusage.o \
+ setcred.o
diff --git a/sys/sys/syscallsubr.h b/sys/sys/syscallsubr.h
--- a/sys/sys/syscallsubr.h
+++ b/sys/sys/syscallsubr.h
@@ -320,6 +320,8 @@
fd_set *fd_ex, struct timeval *tvp, int abi_nfdbits);
int kern_sendit(struct thread *td, int s, struct msghdr *mp, int flags,
struct mbuf *control, enum uio_seg segflg);
+int kern_setcred_v0(struct thread *const td, const u_int flags,
+ struct setcred_v0 *const wcred, gid_t *preallocated_groups);
int kern_setgroups(struct thread *td, int *ngrpp, gid_t *groups);
int kern_setitimer(struct thread *, u_int, struct itimerval *,
struct itimerval *);
diff --git a/sys/sys/sysproto.h b/sys/sys/sysproto.h
--- a/sys/sys/sysproto.h
+++ b/sys/sys/sysproto.h
@@ -1882,6 +1882,11 @@
char flags_l_[PADL_(int)]; int flags; char flags_r_[PADR_(int)];
char res_l_[PADL_(rlim_t *)]; rlim_t * res; char res_r_[PADR_(rlim_t *)];
};
+struct setcred_args {
+ char flags_l_[PADL_(u_int)]; u_int flags; char flags_r_[PADR_(u_int)];
+ char wcred_l_[PADL_(const void *)]; const void * wcred; char wcred_r_[PADR_(const void *)];
+ char size_l_[PADL_(size_t)]; size_t size; char size_r_[PADR_(size_t)];
+};
int sys_exit(struct thread *, struct exit_args *);
int sys_fork(struct thread *, struct fork_args *);
int sys_read(struct thread *, struct read_args *);
@@ -2282,6 +2287,7 @@
int sys_timerfd_settime(struct thread *, struct timerfd_settime_args *);
int sys_kcmp(struct thread *, struct kcmp_args *);
int sys_getrlimitusage(struct thread *, struct getrlimitusage_args *);
+int sys_setcred(struct thread *, struct setcred_args *);
#ifdef COMPAT_43
@@ -3262,6 +3268,7 @@
#define SYS_AUE_timerfd_settime AUE_TIMERFD
#define SYS_AUE_kcmp AUE_NULL
#define SYS_AUE_getrlimitusage AUE_NULL
+#define SYS_AUE_setcred AUE_SETCRED
#undef PAD_
#undef PADL_
diff --git a/sys/sys/ucred.h b/sys/sys/ucred.h
--- a/sys/sys/ucred.h
+++ b/sys/sys/ucred.h
@@ -32,6 +32,7 @@
#ifndef _SYS_UCRED_H_
#define _SYS_UCRED_H_
+#include <sys/types.h>
#if defined(_KERNEL) || defined(_WANT_UCRED)
#include <sys/_lock.h>
#include <sys/_mutex.h>
@@ -39,8 +40,6 @@
#include <bsm/audit.h>
#if defined(_KERNEL) || defined(_WANT_UCRED)
-struct loginclass;
-
/*
* Flags for cr_flags.
*/
@@ -53,6 +52,11 @@
*/
#define CRED_SMALLGROUPS_NB 16
+struct label;
+struct loginclass;
+struct prison;
+struct uidinfo;
+
/*
* Credentials.
*
@@ -119,7 +123,50 @@
/* This can be used for both ucred and xucred structures. */
#define cr_gid cr_groups[0]
+struct mac;
+/*
+ * Structure to pass as an argument to the setcred() system call.
+ */
+struct setcred_v0 {
+ uid_t sc_uid; /* effective user id */
+ uid_t sc_ruid; /* real user id */
+ uid_t sc_svuid; /* saved user id */
+ gid_t sc_gid; /* effective group id */
+ gid_t sc_rgid; /* real group id */
+ gid_t sc_svgid; /* saved group id */
+ int sc_supp_groups_nb; /* number of supplementary groups */
+ gid_t *sc_supp_groups; /* supplementary groups */
+ struct mac *sc_label; /* MAC label */
+};
+
+/*
+ * Flags to setcred().
+ *
+ * Descending order to leave room for more version bits (if ever needed).
+ */
+#define SETCREDF_UID (1u << 31)
+#define SETCREDF_RUID (1u << 30)
+#define SETCREDF_SVUID (1u << 29)
+#define SETCREDF_GID (1u << 28)
+#define SETCREDF_RGID (1u << 27)
+#define SETCREDF_SVGID (1u << 26)
+#define SETCREDF_SUPP_GROUPS (1u << 25)
+#define SETCREDF_MAC_LABEL (1u << 24)
+
+#define SETCREDF_FROM_VERSION(version) (((u_int)version) & 0xFF)
+#define SETCREDF_TO_VERSION(flags) ((flags) & 0xFF)
+
#ifdef _KERNEL
+/*
+ * Masks of the currently valid flags to setcred() (v0). As new versions are
+ * added, they may or may not use the same flags.
+ */
+#define SETCREDF_VERSION_BITS (0xFF)
+#define SETCREDF_SET_MASK (SETCREDF_UID | SETCREDF_RUID | SETCREDF_SVUID | \
+ SETCREDF_GID | SETCREDF_RGID | SETCREDF_SVGID | SETCREDF_SUPP_GROUPS | \
+ SETCREDF_MAC_LABEL)
+#define SETCREDF_MASK (SETCREDF_SET_MASK | SETCREDF_VERSION_BITS)
+
struct proc;
struct thread;
@@ -183,6 +230,13 @@
bool group_is_supplementary(const gid_t gid, const struct ucred *const cred);
bool groupmember(gid_t gid, const struct ucred *cred);
bool realgroupmember(gid_t gid, const struct ucred *cred);
+
+#else /* !_KERNEL */
+
+__BEGIN_DECLS
+int setcred(u_int flags, const void *wcred, size_t size);
+__END_DECLS
+
#endif /* _KERNEL */
#endif /* !_SYS_UCRED_H_ */
diff --git a/usr.bin/mdo/mdo.c b/usr.bin/mdo/mdo.c
--- a/usr.bin/mdo/mdo.c
+++ b/usr.bin/mdo/mdo.c
@@ -5,6 +5,7 @@
*/
#include <sys/limits.h>
+#include <sys/ucred.h>
#include <err.h>
#include <paths.h>
@@ -27,6 +28,8 @@
{
struct passwd *pw;
const char *username = "root";
+ struct setcred_v0 wcred = {};
+ u_int setcred_flags = SETCREDF_FROM_VERSION(0);
bool uidonly = false;
int ch;
@@ -50,20 +53,45 @@
const char *errp = NULL;
uid_t uid = strtonum(username, 0, UID_MAX, &errp);
if (errp != NULL)
- err(EXIT_FAILURE, "%s", errp);
+ err(EXIT_FAILURE, "invalid user ID '%s'",
+ username);
pw = getpwuid(uid);
}
if (pw == NULL)
err(EXIT_FAILURE, "invalid username '%s'", username);
}
+
+ wcred.sc_uid = wcred.sc_ruid = wcred.sc_svuid = pw->pw_uid;
+ setcred_flags |= SETCREDF_UID | SETCREDF_RUID | SETCREDF_SVUID;
+
if (!uidonly) {
- if (initgroups(pw->pw_name, pw->pw_gid) == -1)
- err(EXIT_FAILURE, "failed to call initgroups");
- if (setgid(pw->pw_gid) == -1)
- err(EXIT_FAILURE, "failed to call setgid");
+ /*
+ * If there are too many groups specified for some UID, setting
+ * the groups will fail. We preserve this condition by
+ * allocating one more group slot than allowed, as
+ * getgrouplist() itself is just some getter function and thus
+ * doesn't (and shouldn't) check the limit, and to allow
+ * setcred() to actually check for overflow.
+ */
+ const long ngroups_alloc = sysconf(_SC_NGROUPS_MAX) + 2;
+ gid_t *const groups = malloc(sizeof(*groups) * ngroups_alloc);
+ int ngroups = ngroups_alloc;
+
+ if (groups == NULL)
+ err(EXIT_FAILURE, "cannot allocate memory for groups");
+
+ getgrouplist(pw->pw_name, pw->pw_gid, groups, &ngroups);
+
+ wcred.sc_gid = wcred.sc_rgid = wcred.sc_svgid = pw->pw_gid;
+ wcred.sc_supp_groups = groups + 1;
+ wcred.sc_supp_groups_nb = ngroups - 1;
+ setcred_flags |= SETCREDF_GID | SETCREDF_RGID | SETCREDF_SVGID |
+ SETCREDF_SUPP_GROUPS;
}
- if (setuid(pw->pw_uid) == -1)
- err(EXIT_FAILURE, "failed to call setuid");
+
+ if (setcred(setcred_flags, &wcred, sizeof(wcred)) != 0)
+ err(EXIT_FAILURE, "calling setcred() failed");
+
if (*argv == NULL) {
const char *sh = getenv("SHELL");
if (sh == NULL)
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Nov 17, 2:52 AM (21 h, 19 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
14669370
Default Alt Text
D47633.diff (95 KB)
Attached To
Mode
D47633: MAC/do 2.0
Attached
Detach File
Event Timeline
Log In to Comment