--- //depot/projects/trustedbsd/base/sys/kern/uipc_sem.c 2003/06/24 12:28:09 +++ //depot/projects/trustedbsd/mac/sys/kern/uipc_sem.c 2003/10/29 21:23:38 @@ -1,7 +1,13 @@ /* * Copyright (c) 2002 Alfred Perlstein + * Copyright (c) 2003 Networks Associates Technology, Inc. * All rights reserved. * + * This software was developed for the FreeBSD Project in part by Network + * Associates Laboratories, the Security Research Division of Network + * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), + * as part of the DARPA CHATS research program. + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: @@ -28,6 +34,7 @@ __FBSDID("$FreeBSD: src/sys/kern/uipc_sem.c,v 1.9 2003/06/11 00:56:58 obrien Exp $"); #include "opt_posix.h" +#include "opt_mac.h" #include #include @@ -47,6 +54,11 @@ #include #include #include +#ifdef MAC +#include +#include +#include +#endif #include #include @@ -54,8 +66,8 @@ static struct ksem *sem_lookup_byname(const char *name); static int sem_create(struct thread *td, const char *name, - struct ksem **ksret, mode_t mode, unsigned int value); -static void sem_free(struct ksem *ksnew); +struct ksem **ksret, mode_t mode, unsigned int value); +static void sem_free(struct ksem *ksnew, int checkrefs); static int sem_perm(struct thread *td, struct ksem *ks); static void sem_enter(struct proc *p, struct ksem *ks); static int sem_leave(struct proc *p, struct ksem *ks); @@ -69,6 +81,8 @@ semid_t *idp); static int kern_sem_open(struct thread *td, int dir, const char *name, int oflag, mode_t mode, unsigned int value, semid_t *idp); +static int ksem_open_existing(struct thread *td, struct ksem *ks, int dir, + semid_t *idpu, semid_t *idpk); static int kern_sem_unlink(struct thread *td, const char *name); #ifndef SEM_MAX @@ -79,7 +93,18 @@ #define SEM_TO_ID(x) ((intptr_t)(x)) #define ID_TO_SEM(x) id_to_sem(x) +#define SEM_FREE(ks) sem_free(ks, 1) +#define SEM_DROP(ks) sem_free(ks, 0) +#define REF_UP(ks) sem_ref(ks) +#define REF_DOWN(ksem) \ + do { \ + sem_rel((ksem)); /* Pump down */ \ + if((ksem)->ks_unlinked && \ + LIST_EMPTY(&(ksem)->ks_users)) \ + SEM_FREE((ksem)); \ + } while (0) +#ifndef MAC struct kuser { pid_t ku_pid; LIST_ENTRY(kuser) ku_next; @@ -97,17 +122,19 @@ struct cv ks_cv; /* waiters sleep here */ int ks_waiters; /* number of waiters */ LIST_HEAD(, kuser) ks_users; /* pids using this sem */ + struct mtx ks_mtx; /* mutex protecting this semaphore */ + int ks_unlinked; /* Whether the named sem is unlinked */ }; +#else +struct kuser; +struct ksem; +#endif /* * available semaphores go here, this includes sem_init and any semaphores * created via sem_open that have not yet been unlinked. */ LIST_HEAD(, ksem) ksem_head = LIST_HEAD_INITIALIZER(&ksem_head); -/* - * semaphores still in use but have been sem_unlink()'d go here. - */ -LIST_HEAD(, ksem) ksem_deadhead = LIST_HEAD_INITIALIZER(&ksem_deadhead); static struct mtx sem_lock; static MALLOC_DEFINE(M_SEM, "sems", "semaphore data"); @@ -118,6 +145,9 @@ static eventhandler_tag sem_exit_tag, sem_exec_tag; +#ifndef SEM_DEBUG +#define SEM_DEBUG +#endif #ifdef SEM_DEBUG #define DP(x) printf x #else @@ -128,7 +158,7 @@ void sem_ref(struct ksem *ks) { - + mtx_assert(&sem_lock, MA_OWNED); ks->ks_ref++; DP(("sem_ref: ks = %p, ref = %d\n", ks, ks->ks_ref)); } @@ -138,9 +168,9 @@ sem_rel(struct ksem *ks) { + mtx_assert(&sem_lock, MA_OWNED); + ks->ks_ref--; DP(("sem_rel: ks = %p, ref = %d\n", ks, ks->ks_ref - 1)); - if (--ks->ks_ref == 0) - sem_free(ks); } static __inline struct ksem *id_to_sem(semid_t id); @@ -152,6 +182,7 @@ { struct ksem *ks; + mtx_assert(&sem_lock,MA_OWNED); DP(("id_to_sem: id = %0x,%p\n", id, (struct ksem *)id)); LIST_FOREACH(ks, &ksem_head, ks_entry) { DP(("id_to_sem: ks = %p\n", ks)); @@ -167,12 +198,15 @@ { struct ksem *ks; + mtx_assert(&sem_lock, MA_OWNED); LIST_FOREACH(ks, &ksem_head, ks_entry) if (ks->ks_name != NULL && strcmp(ks->ks_name, name) == 0) return (ks); return (NULL); } +/* Used by both sem_init and sem_open to create a new semaphore. */ + static int sem_create(td, name, ksret, mode, value) struct thread *td; @@ -185,11 +219,11 @@ struct proc *p; struct ucred *uc; size_t len; - int error; DP(("sem_create\n")); p = td->td_proc; uc = td->td_ucred; + /* XXX Use p31b_getcfg(CTL_P1003_1B_SEM_VALUE_MAX) instead? */ if (value > SEM_VALUE_MAX) return (EINVAL); ret = malloc(sizeof(*ret), M_SEM, M_WAITOK | M_ZERO); @@ -211,27 +245,34 @@ } ret->ks_mode = mode; ret->ks_value = value; - ret->ks_ref = 1; + ret->ks_ref = 0; ret->ks_waiters = 0; ret->ks_uid = uc->cr_uid; ret->ks_gid = uc->cr_gid; ret->ks_onlist = 0; cv_init(&ret->ks_cv, "sem"); LIST_INIT(&ret->ks_users); + mtx_init(&ret->ks_mtx, "ks_mtx", "ks_mtx", MTX_DEF); if (name != NULL) - sem_enter(td->td_proc, ret); - *ksret = ret; + sem_enter(td->td_proc, ret); /* This invokes sem_ref */ + else + ret->ks_ref = 1; +#ifdef MAC + mac_init_posix_ksem(ret); + mac_create_posix_ksem(uc, ret); +#endif mtx_lock(&sem_lock); + nsems++; if (nsems >= p31b_getcfg(CTL_P1003_1B_SEM_NSEMS_MAX)) { - sem_leave(td->td_proc, ret); - sem_free(ret); - error = ENFILE; - } else { - nsems++; - error = 0; - } + if (name != NULL) + sem_leave(td->td_proc, ret); /* This invokes sem_rel */ + SEM_DROP(ret); /* sem_free does a nsem-- */ + mtx_unlock(&sem_lock); + return (ENFILE); + } mtx_unlock(&sem_lock); - return (error); + *ksret = ret; + return (0); } #ifndef _SYS_SYSPROTO_H_ @@ -263,15 +304,14 @@ semid_t id; int error; - error = sem_create(td, NULL, &ks, S_IRWXU | S_IRWXG, value); - if (error) + if((error = sem_create(td, NULL, &ks, S_IRWXU | S_IRWXG, value))) return (error); id = SEM_TO_ID(ks); if (dir == UIO_USERSPACE) { error = copyout(&id, idp, sizeof(id)); if (error) { mtx_lock(&sem_lock); - sem_rel(ks); + SEM_DROP(ks); mtx_unlock(&sem_lock); return (error); } @@ -302,10 +342,9 @@ { char name[SEM_MAX_NAMELEN + 1]; size_t done; - int error; + int error = 0; - error = copyinstr(uap->name, name, SEM_MAX_NAMELEN + 1, &done); - if (error) + if ((error = copyinstr(uap->name, name, SEM_MAX_NAMELEN + 1, &done))) return (error); DP((">>> sem_open start\n")); error = kern_sem_open(td, UIO_USERSPACE, @@ -315,6 +354,52 @@ } static int +ksem_open_existing(struct thread *td, struct ksem *ks, int dir, semid_t *idpu, + semid_t *idpk) +{ + int error = 0; + mtx_assert(&sem_lock,MA_OWNED); + mtx_assert(&ks->ks_mtx,MA_NOTOWNED); + if((error = sem_perm(td, ks))) { + mtx_unlock(&sem_lock); + return (error); + } + /* + * If already queued up for unlinking. + * Then according to spec cant let reconnect to this semaphore. + */ + if(ks->ks_unlinked) { + mtx_unlock(&sem_lock); + return (EPERM); + } + *idpk = SEM_TO_ID(ks); + REF_UP(ks); /* Pump up the refs to avoid the race with SEM_FREE */ + mtx_unlock(&sem_lock); +#ifdef MAC + mtx_lock(&ks->ks_mtx); + if((error = mac_check_posix_sem_openexisting(td->td_ucred, ks))) { + DP(("MAC Framework: mac_check_posix_sem_openexisting access denied\n")); + mtx_unlock(&ks->ks_mtx); + goto err_open_existing; + } + mtx_unlock(&ks->ks_mtx); +#endif + if (dir == UIO_USERSPACE) { + if ((error = copyout(idpk, idpu, sizeof(*idpk)))) { + goto err_open_existing; + } + } else { + *idpu = *idpk; + } + sem_enter(td->td_proc, ks); +err_open_existing: + mtx_lock(&sem_lock); + REF_DOWN(ks); + mtx_unlock(&sem_lock); + return(error); +} + +static int kern_sem_open(td, dir, name, oflag, mode, value, idp) struct thread *td; int dir; @@ -325,7 +410,7 @@ semid_t *idp; { struct ksem *ksnew, *ks; - int error; + int error = 0; semid_t id; ksnew = NULL; @@ -353,8 +438,7 @@ * We may block during creation, so drop the lock. */ mtx_unlock(&sem_lock); - error = sem_create(td, name, &ksnew, mode, value); - if (error != 0) + if((error = sem_create(td, name, &ksnew, mode, value))) return (error); id = SEM_TO_ID(ksnew); if (dir == UIO_USERSPACE) { @@ -363,7 +447,7 @@ if (error) { mtx_lock(&sem_lock); sem_leave(td->td_proc, ksnew); - sem_rel(ksnew); + SEM_DROP(ksnew); mtx_unlock(&sem_lock); return (error); } @@ -380,46 +464,28 @@ if (ks != NULL) { /* we lost... */ sem_leave(td->td_proc, ksnew); - sem_rel(ksnew); + SEM_DROP(ksnew); /* we lost and we can't loose... */ if ((oflag & O_EXCL) != 0) { mtx_unlock(&sem_lock); return (EEXIST); } + /* Use the sem created by the winner */ + else { + /* ksem_open_existing unlocks sem_lock */ + error = ksem_open_existing(td, ks, dir, idp, &id); + } } else { DP(("sem_create: about to add to list...\n")); LIST_INSERT_HEAD(&ksem_head, ksnew, ks_entry); DP(("sem_create: setting list bit...\n")); ksnew->ks_onlist = 1; DP(("sem_create: done, about to unlock...\n")); + mtx_unlock(&sem_lock); } - mtx_unlock(&sem_lock); } else { - /* - * if we aren't the creator, then enforce permissions. - */ - error = sem_perm(td, ks); - if (!error) - sem_ref(ks); - mtx_unlock(&sem_lock); - if (error) - return (error); - id = SEM_TO_ID(ks); - if (dir == UIO_USERSPACE) { - error = copyout(&id, idp, sizeof(id)); - if (error) { - mtx_lock(&sem_lock); - sem_rel(ks); - mtx_unlock(&sem_lock); - return (error); - } - } else { - *idp = id; - } - sem_enter(td->td_proc, ks); - mtx_lock(&sem_lock); - sem_rel(ks); - mtx_unlock(&sem_lock); + /* ksem_open_existing unlocks sem_lock */ + error = ksem_open_existing(td, ks, dir, idp, &id); } return (error); } @@ -443,18 +509,27 @@ } static void -sem_free(struct ksem *ks) +sem_free(struct ksem *ks, int checkrefs) { - + mtx_assert(&sem_lock, MA_OWNED); + mtx_assert(&ks->ks_mtx, MA_NOTOWNED); + if(checkrefs && (ks->ks_ref > 0)) + return; nsems--; if (ks->ks_onlist) LIST_REMOVE(ks, ks_entry); + if (ks->ks_name != NULL) free(ks->ks_name, M_SEM); cv_destroy(&ks->ks_cv); +#ifdef MAC + mac_destroy_posix_ksem(ks); +#endif + mtx_destroy(&ks->ks_mtx); free(ks, M_SEM); } + static __inline struct kuser *sem_getuser(struct proc *p, struct ksem *ks); static __inline struct kuser * @@ -463,7 +538,8 @@ struct ksem *ks; { struct kuser *k; - + + mtx_assert(&sem_lock, MA_OWNED); LIST_FOREACH(k, &ks->ks_users, ku_next) if (k->ku_pid == p->p_pid) return (k); @@ -475,9 +551,15 @@ struct thread *td; struct ksem *ks; { + struct kuser *k; + int ret = 0; + + mtx_assert(&sem_lock, MA_OWNED); + k = sem_getuser(td->td_proc, ks); + if ((ks->ks_name == NULL && sem_perm(td, ks)) || k != NULL) + ret = 1; + return ret; - return ((ks->ks_name == NULL && sem_perm(td, ks)) - || sem_getuser(td->td_proc, ks) != NULL); } static int @@ -485,20 +567,21 @@ struct proc *p; struct ksem *ks; { - struct kuser *k; + struct kuser *k=NULL; DP(("sem_leave: ks = %p\n", ks)); + mtx_assert(&sem_lock, MA_OWNED); + DP(("sem_leave: ks = %p, k = %p\n", ks, k)); k = sem_getuser(p, ks); - DP(("sem_leave: ks = %p, k = %p\n", ks, k)); - if (k != NULL) { - LIST_REMOVE(k, ku_next); - sem_rel(ks); - DP(("sem_leave: about to free k\n")); - free(k, M_SEM); - DP(("sem_leave: returning\n")); - return (0); + if (k == NULL) { + return (EINVAL); } - return (EINVAL); + LIST_REMOVE(k, ku_next); + sem_rel(ks); + DP(("sem_leave: about to free k\n")); + free(k, M_SEM); + DP(("sem_leave: returning\n")); + return (0); } static void @@ -508,7 +591,9 @@ { struct kuser *ku, *k; - ku = malloc(sizeof(*ku), M_SEM, M_WAITOK); + mtx_assert(&sem_lock, MA_NOTOWNED); + mtx_assert(&ks->ks_mtx, MA_NOTOWNED); + ku = malloc(sizeof(*ku), M_SEM, M_WAITOK | M_ZERO); ku->ku_pid = p->p_pid; mtx_lock(&sem_lock); k = sem_getuser(p, ks); @@ -549,20 +634,28 @@ const char *name; { struct ksem *ks; - int error; + int error = 0; mtx_lock(&sem_lock); ks = sem_lookup_byname(name); - if (ks == NULL) + if (ks == NULL) { error = ENOENT; - else - error = sem_perm(td, ks); + goto err_unlink; + } + if ((error = sem_perm(td, ks))) + goto err_unlink; +#ifdef MAC + if((error = mac_check_posix_sem_unlink(td->td_ucred, ks))) { + DP(("MAC Framework: mac_check_posix_sem_unlink access \ + denied\n")); + goto err_unlink; + } +#endif DP(("sem_unlink: '%s' ks = %p, error = %d\n", name, ks, error)); - if (error == 0) { - LIST_REMOVE(ks, ks_entry); - LIST_INSERT_HEAD(&ksem_deadhead, ks, ks_entry); - sem_rel(ks); - } + ks->ks_unlinked = 1; + if(LIST_EMPTY(&ks->ks_users)) + SEM_FREE(ks); +err_unlink: mtx_unlock(&sem_lock); return (error); } @@ -589,12 +682,24 @@ struct ksem *ks; int error; - error = EINVAL; mtx_lock(&sem_lock); ks = ID_TO_SEM(id); /* this is not a valid operation for unnamed sems */ - if (ks != NULL && ks->ks_name != NULL) - error = sem_leave(td->td_proc, ks); + error = EINVAL; + if (ks != NULL && ks->ks_name != NULL) { +#ifdef MAC + if ((error = mac_check_posix_sem_close(td->td_ucred, ks))) { + DP(("MAC Framework: mac_check_posix_sem_close access \ + denied\n")); + goto err_close; + } +#endif + if ((error = sem_leave(td->td_proc, ks))) + goto err_close; + if (ks->ks_unlinked && LIST_EMPTY(&ks->ks_users)) + SEM_FREE(ks); + } +err_close: mtx_unlock(&sem_lock); return (error); } @@ -620,23 +725,35 @@ semid_t id; { struct ksem *ks; - int error; + int error = 0; mtx_lock(&sem_lock); ks = ID_TO_SEM(id); if (ks == NULL || !sem_hasopen(td, ks)) { - error = EINVAL; - goto err; + mtx_unlock(&sem_lock); + return (EINVAL); } + REF_UP(ks);/* Pump up the refs to avoid the race with SEM_FREE */ + mtx_unlock(&sem_lock); + + mtx_lock(&ks->ks_mtx); if (ks->ks_value == SEM_VALUE_MAX) { error = EOVERFLOW; - goto err; + goto err_post; + } +#ifdef MAC + if ((error = mac_check_posix_sem_post(td->td_ucred, ks))) { + DP(("MAC Framework: mac_check_posix_sem_post access denied\n")); + goto err_post; } +#endif ++ks->ks_value; if (ks->ks_waiters > 0) cv_signal(&ks->ks_cv); - error = 0; -err: +err_post: + mtx_unlock(&ks->ks_mtx); + mtx_lock(&sem_lock); + REF_DOWN(ks); mtx_unlock(&sem_lock); return (error); } @@ -679,37 +796,46 @@ int tryflag; { struct ksem *ks; - int error; + int error = 0; DP((">>> kern_sem_wait entered!\n")); mtx_lock(&sem_lock); ks = ID_TO_SEM(id); if (ks == NULL) { DP(("kern_sem_wait ks == NULL\n")); - error = EINVAL; - goto err; + mtx_unlock(&sem_lock); + return (EINVAL); } - sem_ref(ks); if (!sem_hasopen(td, ks)) { DP(("kern_sem_wait hasopen failed\n")); - error = EINVAL; - goto err; + mtx_unlock(&sem_lock); + return (EINVAL); + } + REF_UP(ks);/* Pump up the refs to avoid the race with SEM_FREE */ + mtx_unlock(&sem_lock); + + mtx_lock(&ks->ks_mtx); +#ifdef MAC + if ((error = mac_check_posix_sem_wait(td->td_ucred, ks))) { + DP(("MAC Framework: mac_check_posix_sem_wait access denied\n")); + goto err_wait; } +#endif DP(("kern_sem_wait value = %d, tryflag %d\n", ks->ks_value, tryflag)); if (ks->ks_value == 0) { ks->ks_waiters++; - error = tryflag ? EAGAIN : cv_wait_sig(&ks->ks_cv, &sem_lock); + error = tryflag ? EAGAIN : cv_wait_sig(&ks->ks_cv, &ks->ks_mtx); ks->ks_waiters--; if (error) - goto err; + goto err_wait; } ks->ks_value--; - error = 0; -err: - if (ks != NULL) - sem_rel(ks); +err_wait: + mtx_unlock(&ks->ks_mtx); + DP(("<<< kern_sem_wait leaving, error = %d\n", error)); + mtx_lock(&sem_lock); + REF_DOWN(ks); mtx_unlock(&sem_lock); - DP(("<<< kern_sem_wait leaving, error = %d\n", error)); return (error); } @@ -734,9 +860,24 @@ mtx_unlock(&sem_lock); return (EINVAL); } + REF_UP(ks);/* Pump up the refs to avoid the race with SEM_FREE */ + mtx_unlock(&sem_lock); + + mtx_lock(&ks->ks_mtx); +#ifdef MAC + if((error = mac_check_posix_sem_getvalue(td->td_ucred, ks))) { + DP(("MAC Framework: mac_check_posix_sem_getvalue access denied\n")); + mtx_unlock(&ks->ks_mtx); + goto err_getvalue; + } +#endif val = ks->ks_value; + mtx_unlock(&ks->ks_mtx); + error = copyout(&val, uap->val, sizeof(val)); +err_getvalue: + mtx_lock(&sem_lock); + REF_DOWN(ks); mtx_unlock(&sem_lock); - error = copyout(&val, uap->val, sizeof(val)); return (error); } @@ -752,22 +893,24 @@ struct ksem_destroy_args *uap; { struct ksem *ks; - int error; + int error = 0; mtx_lock(&sem_lock); ks = ID_TO_SEM(uap->id); if (ks == NULL || !sem_hasopen(td, ks) || ks->ks_name != NULL) { error = EINVAL; - goto err; + goto err_destroy; } - if (ks->ks_waiters != 0) { - error = EBUSY; - goto err; +#ifdef MAC + if((error = mac_check_posix_sem_destroy(td->td_ucred, ks))) { + DP(("MAC Framework: mac_check_posix_sem_destroy access denied\n")); + goto err_destroy; } - sem_rel(ks); - error = 0; -err: +#endif + ks->ks_unlinked = 1; /* Indicate that the sem needs to be destroyed */ + SEM_FREE(ks); +err_destroy: mtx_unlock(&sem_lock); return (error); } @@ -783,13 +926,9 @@ ks = LIST_FIRST(&ksem_head); while (ks != NULL) { ksnext = LIST_NEXT(ks, ks_entry); - sem_leave(p, ks); - ks = ksnext; - } - ks = LIST_FIRST(&ksem_deadhead); - while (ks != NULL) { - ksnext = LIST_NEXT(ks, ks_entry); - sem_leave(p, ks); + if((ks->ks_name != NULL) && (!sem_leave(p, ks))) + if (ks->ks_unlinked && LIST_EMPTY(&ks->ks_users)) + SEM_FREE(ks); ks = ksnext; } mtx_unlock(&sem_lock);