#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/kernel_types.h>
#include <sys/sysproto.h>
#include <sys/kauth.h>
#include <sys/malloc.h>
#include <sys/persona.h>
#include <sys/proc.h>
#include <libkern/libkern.h>
static int kpersona_copyin(user_addr_t infop, struct kpersona_info *kinfo)
{
uint32_t info_v = 0;
int error;
error = copyin(infop, &info_v, sizeof(info_v));
if (error)
return error;
if (info_v != PERSONA_INFO_V1)
return EINVAL;
error = copyin(infop, kinfo, sizeof(*kinfo));
kinfo->persona_name[MAXLOGNAME] = 0;
return error;
}
static int kpersona_copyout(struct kpersona_info *kinfo, user_addr_t infop)
{
uint32_t info_v;
int error;
error = copyin(infop, &info_v, sizeof(info_v));
if (error)
return error;
if (info_v != PERSONA_INFO_V1)
return EINVAL;
error = copyout(kinfo, infop, sizeof(*kinfo));
return error;
}
static int kpersona_alloc_syscall(user_addr_t infop, user_addr_t idp)
{
int error;
struct kpersona_info kinfo;
struct persona *persona;
uid_t id = PERSONA_ID_NONE;
const char *login;
if (!kauth_cred_issuser(kauth_cred_get()))
return EPERM;
error = kpersona_copyin(infop, &kinfo);
if (error)
return error;
login = kinfo.persona_name[0] ? kinfo.persona_name : NULL;
if (kinfo.persona_id != PERSONA_ID_NONE && kinfo.persona_id != (uid_t)0)
id = kinfo.persona_id;
error = 0;
persona = persona_alloc(id, login, kinfo.persona_type, &error);
if (!persona)
return error;
if (kinfo.persona_gid) {
error = persona_set_gid(persona, kinfo.persona_gid);
if (error)
goto out_error;
}
if (kinfo.persona_ngroups > 0) {
if (kinfo.persona_gmuid == 0)
kinfo.persona_gmuid = KAUTH_UID_NONE;
error = persona_set_groups(persona, kinfo.persona_groups,
kinfo.persona_ngroups,
kinfo.persona_gmuid);
if (error)
goto out_error;
}
error = copyout(&persona->pna_id, idp, sizeof(persona->pna_id));
if (error)
goto out_error;
error = kpersona_copyout(&kinfo, infop);
return error;
out_error:
printf("%s: ERROR:%d\n", __func__, error);
if (persona)
persona_put(persona);
return error;
}
static int kpersona_dealloc_syscall(user_addr_t idp)
{
int error;
uid_t persona_id;
struct persona *persona;
if (!kauth_cred_issuser(kauth_cred_get()))
return EPERM;
error = copyin(idp, &persona_id, sizeof(persona_id));
if (error)
return error;
persona = persona_lookup(persona_id);
if (!persona)
return ESRCH;
error = persona_invalidate(persona);
persona_put(persona);
if (!error)
persona_put(persona);
return error;
}
static int kpersona_get_syscall(user_addr_t idp)
{
int error;
struct persona *persona = current_persona_get();
if (!persona)
return ESRCH;
error = copyout(&persona->pna_id, idp, sizeof(persona->pna_id));
persona_put(persona);
return error;
}
static int kpersona_info_syscall(user_addr_t idp, user_addr_t infop)
{
int error;
uid_t persona_id;
struct persona *persona;
struct kpersona_info kinfo;
error = copyin(idp, &persona_id, sizeof(persona_id));
if (error)
return error;
persona = persona_lookup(persona_id);
if (!persona)
return ESRCH;
persona_dbg("FOUND: persona:%p, id:%d, gid:%d, login:\"%s\"",
persona, persona->pna_id, persona_get_gid(persona),
persona->pna_login);
memset(&kinfo, 0, sizeof(kinfo));
kinfo.persona_info_version = PERSONA_INFO_V1;
kinfo.persona_id = persona->pna_id;
kinfo.persona_type = persona->pna_type;
kinfo.persona_gid = persona_get_gid(persona);
int ngroups = 0;
persona_get_groups(persona, &ngroups, kinfo.persona_groups, NGROUPS);
kinfo.persona_ngroups = ngroups;
kinfo.persona_gmuid = persona_get_gmuid(persona);
strncpy(kinfo.persona_name, persona->pna_login, MAXLOGNAME);
persona_put(persona);
error = kpersona_copyout(&kinfo, infop);
return error;
}
static int kpersona_pidinfo_syscall(user_addr_t idp, user_addr_t infop)
{
int error;
pid_t pid;
struct persona *persona;
struct kpersona_info kinfo;
error = copyin(idp, &pid, sizeof(pid));
if (error)
return error;
if (!kauth_cred_issuser(kauth_cred_get())
&& (pid != current_proc()->p_pid))
return EPERM;
persona = persona_proc_get(pid);
if (!persona)
return ESRCH;
memset(&kinfo, 0, sizeof(kinfo));
kinfo.persona_info_version = PERSONA_INFO_V1;
kinfo.persona_id = persona->pna_id;
kinfo.persona_type = persona->pna_type;
kinfo.persona_gid = persona_get_gid(persona);
int ngroups = 0;
persona_get_groups(persona, &ngroups, kinfo.persona_groups, NGROUPS);
kinfo.persona_ngroups = ngroups;
kinfo.persona_gmuid = persona_get_gmuid(persona);
strncpy(kinfo.persona_name, persona->pna_login, MAXLOGNAME);
persona_put(persona);
error = kpersona_copyout(&kinfo, infop);
return error;
}
static int kpersona_find_syscall(user_addr_t infop, user_addr_t idp, user_addr_t idlenp)
{
int error;
struct kpersona_info kinfo;
const char *login;
size_t u_idlen, k_idlen = 0;
struct persona **persona = NULL;
error = copyin(idlenp, &u_idlen, sizeof(u_idlen));
if (error)
return error;
if (u_idlen > g_max_personas)
u_idlen = g_max_personas;
error = kpersona_copyin(infop, &kinfo);
if (error)
goto out;
login = kinfo.persona_name[0] ? kinfo.persona_name : NULL;
if (u_idlen > 0) {
MALLOC(persona, struct persona **, sizeof(*persona) * u_idlen,
M_TEMP, M_WAITOK|M_ZERO);
if (!persona) {
error = ENOMEM;
goto out;
}
}
k_idlen = u_idlen;
error = persona_find(login, kinfo.persona_id, persona, &k_idlen);
if (error)
goto out;
for (size_t i = 0; i < k_idlen; i++) {
if (i >= u_idlen)
break;
error = copyout(&persona[i]->pna_id,
idp + (i * sizeof(persona[i]->pna_id)),
sizeof(persona[i]->pna_id));
if (error)
goto out;
}
out:
if (persona) {
for (size_t i = 0; i < u_idlen; i++)
persona_put(persona[i]);
FREE(persona, M_TEMP);
}
(void)copyout(&k_idlen, idlenp, sizeof(u_idlen));
return error;
}
int persona(__unused proc_t p, struct persona_args *pargs, __unused int32_t *retval)
{
int error;
uint32_t op = pargs->operation;
user_addr_t infop = pargs->info;
user_addr_t idp = pargs->id;
switch (op) {
case PERSONA_OP_ALLOC:
error = kpersona_alloc_syscall(infop, idp);
break;
case PERSONA_OP_DEALLOC:
error = kpersona_dealloc_syscall(idp);
break;
case PERSONA_OP_GET:
error = kpersona_get_syscall(idp);
break;
case PERSONA_OP_INFO:
error = kpersona_info_syscall(idp, infop);
break;
case PERSONA_OP_PIDINFO:
error = kpersona_pidinfo_syscall(idp, infop);
break;
case PERSONA_OP_FIND:
error = kpersona_find_syscall(infop, idp, pargs->idlen);
break;
default:
error = ENOSYS;
break;
}
return error;
}