#include <sys/param.h>
#include <sys/jail.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/sx.h>
#include <sys/priv.h>
#include <sys/proc.h>
#include <sys/sdt.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <security/mac/mac_framework.h>
static bool
suser_enabled(struct ucred *cred)
{
return (prison_allow(cred, PR_ALLOW_SUSER));
}
static int
sysctl_kern_suser_enabled(SYSCTL_HANDLER_ARGS)
{
struct ucred *cred;
int error, enabled;
cred = req->td->td_ucred;
enabled = suser_enabled(cred);
error = sysctl_handle_int(oidp, &enabled, 0, req);
if (error || !req->newptr)
return (error);
prison_set_allow(cred, PR_ALLOW_SUSER, enabled);
return (0);
}
SYSCTL_PROC(_security_bsd, OID_AUTO, suser_enabled, CTLTYPE_INT |
CTLFLAG_RWTUN | CTLFLAG_PRISON | CTLFLAG_MPSAFE, 0, 0,
&sysctl_kern_suser_enabled, "I", "Processes with uid 0 have privilege");
static int unprivileged_mlock = 1;
SYSCTL_INT(_security_bsd, OID_AUTO, unprivileged_mlock, CTLFLAG_RWTUN,
&unprivileged_mlock, 0, "Allow non-root users to call mlock(2)");
static int unprivileged_read_msgbuf = 1;
SYSCTL_INT(_security_bsd, OID_AUTO, unprivileged_read_msgbuf,
CTLFLAG_RW, &unprivileged_read_msgbuf, 0,
"Unprivileged processes may read the kernel message buffer");
SDT_PROVIDER_DEFINE(priv);
SDT_PROBE_DEFINE1(priv, kernel, priv_check, priv__ok, "int");
SDT_PROBE_DEFINE1(priv, kernel, priv_check, priv__err, "int");
static __always_inline int
priv_check_cred_pre(struct ucred *cred, int priv)
{
int error;
#ifdef MAC
error = mac_priv_check(cred, priv);
#else
error = 0;
#endif
return (error);
}
static __always_inline int
priv_check_cred_post(struct ucred *cred, int priv, int error, bool handled)
{
if (__predict_true(handled))
goto out;
#ifdef MAC
if (mac_priv_grant(cred, priv) == 0) {
error = 0;
goto out;
}
#endif
error = EPERM;
out:
if (SDT_PROBES_ENABLED()) {
if (error)
SDT_PROBE1(priv, kernel, priv_check, priv__err, priv);
else
SDT_PROBE1(priv, kernel, priv_check, priv__ok, priv);
}
return (error);
}
int
priv_check_cred(struct ucred *cred, int priv)
{
int error;
KASSERT(PRIV_VALID(priv), ("priv_check_cred: invalid privilege %d",
priv));
switch (priv) {
case PRIV_VFS_LOOKUP:
return (priv_check_cred_vfs_lookup(cred));
case PRIV_VFS_GENERATION:
return (priv_check_cred_vfs_generation(cred));
}
error = priv_check_cred_pre(cred, priv);
if (error)
goto out;
error = prison_priv_check(cred, priv);
if (error)
goto out;
if (unprivileged_mlock) {
switch (priv) {
case PRIV_VM_MLOCK:
case PRIV_VM_MUNLOCK:
error = 0;
goto out;
}
}
if (unprivileged_read_msgbuf) {
if (priv == PRIV_MSGBUF) {
error = 0;
goto out;
}
}
if (suser_enabled(cred)) {
switch (priv) {
case PRIV_MAXFILES:
case PRIV_MAXPROC:
case PRIV_PROC_LIMIT:
if (cred->cr_ruid == 0) {
error = 0;
goto out;
}
break;
case PRIV_VFS_READ_DIR:
if (jailed(cred))
break;
default:
if (cred->cr_uid == 0) {
error = 0;
goto out;
}
break;
}
}
switch (priv) {
case PRIV_KMEM_READ:
case PRIV_PROC_MEM_WRITE:
error = 0;
goto out;
}
if (priv == PRIV_DEBUG_UNPRIV) {
if (prison_allow(cred, PR_ALLOW_UNPRIV_DEBUG)) {
error = 0;
goto out;
}
}
return (priv_check_cred_post(cred, priv, error, false));
out:
return (priv_check_cred_post(cred, priv, error, true));
}
int
priv_check(struct thread *td, int priv)
{
KASSERT(td == curthread, ("priv_check: td != curthread"));
return (priv_check_cred(td->td_ucred, priv));
}
static int __noinline
priv_check_cred_vfs_lookup_slow(struct ucred *cred)
{
int error;
error = priv_check_cred_pre(cred, PRIV_VFS_LOOKUP);
if (error)
goto out;
if (cred->cr_uid == 0 && suser_enabled(cred)) {
error = 0;
goto out;
}
return (priv_check_cred_post(cred, PRIV_VFS_LOOKUP, error, false));
out:
return (priv_check_cred_post(cred, PRIV_VFS_LOOKUP, error, true));
}
int
priv_check_cred_vfs_lookup(struct ucred *cred)
{
int error;
if (__predict_false(mac_priv_check_fp_flag ||
mac_priv_grant_fp_flag || SDT_PROBES_ENABLED()))
return (priv_check_cred_vfs_lookup_slow(cred));
error = EPERM;
if (cred->cr_uid == 0 && suser_enabled(cred))
error = 0;
return (error);
}
int
priv_check_cred_vfs_lookup_nomac(struct ucred *cred)
{
int error;
if (__predict_false(mac_priv_check_fp_flag ||
mac_priv_grant_fp_flag || SDT_PROBES_ENABLED()))
return (EAGAIN);
error = EPERM;
if (cred->cr_uid == 0 && suser_enabled(cred))
error = 0;
return (error);
}
static int __noinline
priv_check_cred_vfs_generation_slow(struct ucred *cred)
{
int error;
error = priv_check_cred_pre(cred, PRIV_VFS_GENERATION);
if (error)
goto out;
if (jailed(cred)) {
error = EPERM;
goto out;
}
if (cred->cr_uid == 0 && suser_enabled(cred)) {
error = 0;
goto out;
}
return (priv_check_cred_post(cred, PRIV_VFS_GENERATION, error, false));
out:
return (priv_check_cred_post(cred, PRIV_VFS_GENERATION, error, true));
}
int
priv_check_cred_vfs_generation(struct ucred *cred)
{
int error;
if (__predict_false(mac_priv_check_fp_flag ||
mac_priv_grant_fp_flag || SDT_PROBES_ENABLED()))
return (priv_check_cred_vfs_generation_slow(cred));
error = EPERM;
if (!jailed(cred) && cred->cr_uid == 0 && suser_enabled(cred))
error = 0;
return (error);
}