aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPawel Jakub Dawidek <pjd@FreeBSD.org>2007-04-05 23:19:13 +0000
committerPawel Jakub Dawidek <pjd@FreeBSD.org>2007-04-05 23:19:13 +0000
commitdc68a633328c51142a160e723a32cc1427404674 (patch)
tree9ed987e18488c2d072c46b0c8e96ad41c49a8b1d
parent616db5f04c01535e19a01056bbd75cbec5284559 (diff)
downloadsrc-dc68a633328c51142a160e723a32cc1427404674.tar.gz
src-dc68a633328c51142a160e723a32cc1427404674.zip
Implement functionality I called 'jail services'.
It may be used for external modules to attach some data to jail's in-kernel structure. - Change allprison_mtx mutex to allprison_sx sx(9) lock. We will need to call external functions while holding this lock, which may want to allocate memory. Make use of the fact that this is shared-exclusive lock and use shared version when possible. - Implement the following functions: prison_service_register() - registers a service that wants to be noticed when a jail is created and destroyed prison_service_deregister() - deregisters service prison_service_data_add() - adds service-specific data to the jail structure prison_service_data_get() - takes service-specific data from the jail structure prison_service_data_del() - removes service-specific data from the jail structure Reviewed by: rwatson
Notes
Notes: svn path=/head/; revision=168401
-rw-r--r--sys/kern/kern_jail.c271
-rw-r--r--sys/sys/jail.h20
2 files changed, 263 insertions, 28 deletions
diff --git a/sys/kern/kern_jail.c b/sys/kern/kern_jail.c
index 8b6a9b1f54a1..ea244cc5b630 100644
--- a/sys/kern/kern_jail.c
+++ b/sys/kern/kern_jail.c
@@ -25,6 +25,7 @@ __FBSDID("$FreeBSD$");
#include <sys/jail.h>
#include <sys/lock.h>
#include <sys/mutex.h>
+#include <sys/sx.h>
#include <sys/namei.h>
#include <sys/mount.h>
#include <sys/queue.h>
@@ -77,12 +78,28 @@ SYSCTL_INT(_security_jail, OID_AUTO, mount_allowed, CTLFLAG_RW,
&jail_mount_allowed, 0,
"Processes in jail can mount/unmount jail-friendly file systems");
-/* allprison, lastprid, and prisoncount are protected by allprison_mtx. */
+/* allprison, lastprid, and prisoncount are protected by allprison_lock. */
struct prisonlist allprison;
-struct mtx allprison_mtx;
+struct sx allprison_lock;
int lastprid = 0;
int prisoncount = 0;
+/*
+ * List of jail services. Protected by allprison_lock.
+ */
+TAILQ_HEAD(prison_services_head, prison_service);
+static struct prison_services_head prison_services =
+ TAILQ_HEAD_INITIALIZER(prison_services);
+static int prison_service_slots = 0;
+
+struct prison_service {
+ prison_create_t ps_create;
+ prison_destroy_t ps_destroy;
+ int ps_slotno;
+ TAILQ_ENTRY(prison_service) ps_next;
+ char ps_name[0];
+};
+
static void init_prison(void *);
static void prison_complete(void *context, int pending);
static int sysctl_jail_list(SYSCTL_HANDLER_ARGS);
@@ -91,7 +108,7 @@ static void
init_prison(void *data __unused)
{
- mtx_init(&allprison_mtx, "allprison", NULL, MTX_DEF);
+ sx_init(&allprison_lock, "allprison");
LIST_INIT(&allprison);
}
@@ -107,6 +124,7 @@ jail(struct thread *td, struct jail_args *uap)
{
struct nameidata nd;
struct prison *pr, *tpr;
+ struct prison_service *psrv;
struct jail j;
struct jail_attach_args jaa;
int vfslocked, error, tryprid;
@@ -139,9 +157,15 @@ jail(struct thread *td, struct jail_args *uap)
pr->pr_ip = j.ip_number;
pr->pr_linux = NULL;
pr->pr_securelevel = securelevel;
+ if (prison_service_slots == 0)
+ pr->pr_slots = NULL;
+ else {
+ pr->pr_slots = malloc(sizeof(*pr->pr_slots) * prison_service_slots,
+ M_PRISON, M_ZERO | M_WAITOK);
+ }
/* Determine next pr_id and add prison to allprison list. */
- mtx_lock(&allprison_mtx);
+ sx_xlock(&allprison_lock);
tryprid = lastprid + 1;
if (tryprid == JAIL_MAX)
tryprid = 1;
@@ -150,7 +174,7 @@ next:
if (tpr->pr_id == tryprid) {
tryprid++;
if (tryprid == JAIL_MAX) {
- mtx_unlock(&allprison_mtx);
+ sx_xunlock(&allprison_lock);
error = EAGAIN;
goto e_dropvnref;
}
@@ -160,7 +184,11 @@ next:
pr->pr_id = jaa.jid = lastprid = tryprid;
LIST_INSERT_HEAD(&allprison, pr, pr_list);
prisoncount++;
- mtx_unlock(&allprison_mtx);
+ sx_downgrade(&allprison_lock);
+ TAILQ_FOREACH(psrv, &prison_services, ps_next) {
+ psrv->ps_create(psrv, pr);
+ }
+ sx_sunlock(&allprison_lock);
error = jail_attach(td, &jaa);
if (error)
@@ -171,10 +199,14 @@ next:
td->td_retval[0] = jaa.jid;
return (0);
e_dropprref:
- mtx_lock(&allprison_mtx);
+ sx_xlock(&allprison_lock);
LIST_REMOVE(pr, pr_list);
prisoncount--;
- mtx_unlock(&allprison_mtx);
+ sx_downgrade(&allprison_lock);
+ TAILQ_FOREACH(psrv, &prison_services, ps_next) {
+ psrv->ps_destroy(psrv, pr);
+ }
+ sx_sunlock(&allprison_lock);
e_dropvnref:
vfslocked = VFS_LOCK_GIANT(pr->pr_root->v_mount);
vrele(pr->pr_root);
@@ -211,15 +243,15 @@ jail_attach(struct thread *td, struct jail_attach_args *uap)
return (error);
p = td->td_proc;
- mtx_lock(&allprison_mtx);
+ sx_slock(&allprison_lock);
pr = prison_find(uap->jid);
if (pr == NULL) {
- mtx_unlock(&allprison_mtx);
+ sx_sunlock(&allprison_lock);
return (EINVAL);
}
pr->pr_ref++;
mtx_unlock(&pr->pr_mtx);
- mtx_unlock(&allprison_mtx);
+ sx_sunlock(&allprison_lock);
vfslocked = VFS_LOCK_GIANT(pr->pr_root->v_mount);
vn_lock(pr->pr_root, LK_EXCLUSIVE | LK_RETRY, td);
@@ -260,7 +292,7 @@ prison_find(int prid)
{
struct prison *pr;
- mtx_assert(&allprison_mtx, MA_OWNED);
+ sx_assert(&allprison_lock, SX_LOCKED);
LIST_FOREACH(pr, &allprison, pr_list) {
if (pr->pr_id == prid) {
mtx_lock(&pr->pr_mtx);
@@ -273,22 +305,27 @@ prison_find(int prid)
void
prison_free(struct prison *pr)
{
+ struct prison_service *psrv;
- mtx_lock(&allprison_mtx);
+ sx_xlock(&allprison_lock);
mtx_lock(&pr->pr_mtx);
pr->pr_ref--;
if (pr->pr_ref == 0) {
LIST_REMOVE(pr, pr_list);
mtx_unlock(&pr->pr_mtx);
prisoncount--;
- mtx_unlock(&allprison_mtx);
+ sx_downgrade(&allprison_lock);
+ TAILQ_FOREACH(psrv, &prison_services, ps_next) {
+ psrv->ps_destroy(psrv, pr);
+ }
+ sx_sunlock(&allprison_lock);
TASK_INIT(&pr->pr_task, 0, prison_complete, pr);
taskqueue_enqueue(taskqueue_thread, &pr->pr_task);
return;
}
mtx_unlock(&pr->pr_mtx);
- mtx_unlock(&allprison_mtx);
+ sx_xunlock(&allprison_lock);
}
static void
@@ -700,6 +737,193 @@ prison_priv_check(struct ucred *cred, int priv)
}
}
+/*
+ * Register jail service. Provides 'create' and 'destroy' methods.
+ * 'create' method will be called for every existing jail and all
+ * jails in the future as they beeing created.
+ * 'destroy' method will be called for every jail going away and
+ * for all existing jails at the time of service deregistration.
+ */
+struct prison_service *
+prison_service_register(const char *name, prison_create_t create,
+ prison_destroy_t destroy)
+{
+ struct prison_service *psrv, *psrv2;
+ struct prison *pr;
+ int reallocate = 1, slotno = 0;
+ void **slots, **oldslots;
+
+ psrv = malloc(sizeof(*psrv) + strlen(name) + 1, M_PRISON,
+ M_WAITOK | M_ZERO);
+ psrv->ps_create = create;
+ psrv->ps_destroy = destroy;
+ strcpy(psrv->ps_name, name);
+ /*
+ * Grab the allprison_lock here, so we won't miss any jail
+ * creation/destruction.
+ */
+ sx_xlock(&allprison_lock);
+#ifdef INVARIANTS
+ /*
+ * Verify if service is not already registered.
+ */
+ TAILQ_FOREACH(psrv2, &prison_services, ps_next) {
+ KASSERT(strcmp(psrv2->ps_name, name) != 0,
+ ("jail service %s already registered", name));
+ }
+#endif
+ /*
+ * Find free slot. When there is no existing free slot available,
+ * allocate one at the end.
+ */
+ TAILQ_FOREACH(psrv2, &prison_services, ps_next) {
+ if (psrv2->ps_slotno != slotno) {
+ KASSERT(slotno < psrv2->ps_slotno,
+ ("Invalid slotno (slotno=%d >= ps_slotno=%d",
+ slotno, psrv2->ps_slotno));
+ /* We found free slot. */
+ reallocate = 0;
+ break;
+ }
+ slotno++;
+ }
+ psrv->ps_slotno = slotno;
+ /*
+ * Keep the list sorted by slot number.
+ */
+ if (psrv2 != NULL) {
+ KASSERT(reallocate == 0, ("psrv2 != NULL && reallocate != 0"));
+ TAILQ_INSERT_BEFORE(psrv2, psrv, ps_next);
+ } else {
+ KASSERT(reallocate == 1, ("psrv2 == NULL && reallocate == 0"));
+ TAILQ_INSERT_TAIL(&prison_services, psrv, ps_next);
+ }
+ prison_service_slots++;
+ sx_downgrade(&allprison_lock);
+ /*
+ * Allocate memory for new slot if we didn't found empty one.
+ * Do not use realloc(9), because pr_slots is protected with a mutex,
+ * so we can't sleep.
+ */
+ LIST_FOREACH(pr, &allprison, pr_list) {
+ if (reallocate) {
+ /* First allocate memory with M_WAITOK. */
+ slots = malloc(sizeof(*slots) * prison_service_slots,
+ M_PRISON, M_WAITOK);
+ /* Now grab the mutex and replace pr_slots. */
+ mtx_lock(&pr->pr_mtx);
+ oldslots = pr->pr_slots;
+ if (psrv->ps_slotno > 0) {
+ bcopy(oldslots, slots,
+ sizeof(*slots) * (prison_service_slots - 1));
+ }
+ slots[psrv->ps_slotno] = NULL;
+ pr->pr_slots = slots;
+ mtx_unlock(&pr->pr_mtx);
+ if (oldslots != NULL)
+ free(oldslots, M_PRISON);
+ }
+ /*
+ * Call 'create' method for each existing jail.
+ */
+ psrv->ps_create(psrv, pr);
+ }
+ sx_sunlock(&allprison_lock);
+
+ return (psrv);
+}
+
+void
+prison_service_deregister(struct prison_service *psrv)
+{
+ struct prison *pr;
+ void **slots, **oldslots;
+ int last = 0;
+
+ sx_xlock(&allprison_lock);
+ if (TAILQ_LAST(&prison_services, prison_services_head) == psrv)
+ last = 1;
+ TAILQ_REMOVE(&prison_services, psrv, ps_next);
+ prison_service_slots--;
+ sx_downgrade(&allprison_lock);
+ LIST_FOREACH(pr, &allprison, pr_list) {
+ /*
+ * Call 'destroy' method for every currently existing jail.
+ */
+ psrv->ps_destroy(psrv, pr);
+ /*
+ * If this is the last slot, free the memory allocated for it.
+ */
+ if (last) {
+ if (prison_service_slots == 0)
+ slots = NULL;
+ else {
+ slots = malloc(sizeof(*slots) * prison_service_slots,
+ M_PRISON, M_WAITOK);
+ }
+ mtx_lock(&pr->pr_mtx);
+ oldslots = pr->pr_slots;
+ /*
+ * We require setting slot to NULL after freeing it,
+ * this way we can check for memory leaks here.
+ */
+ KASSERT(oldslots[psrv->ps_slotno] == NULL,
+ ("Slot %d (service %s, jailid=%d) still contains data?",
+ psrv->ps_slotno, psrv->ps_name, pr->pr_id));
+ if (psrv->ps_slotno > 0) {
+ bcopy(oldslots, slots,
+ sizeof(*slots) * prison_service_slots);
+ }
+ pr->pr_slots = slots;
+ mtx_unlock(&pr->pr_mtx);
+ KASSERT(oldslots != NULL, ("oldslots == NULL"));
+ free(oldslots, M_PRISON);
+ }
+ }
+ sx_sunlock(&allprison_lock);
+ free(psrv, M_PRISON);
+}
+
+/*
+ * Function sets data for the given jail in slot assigned for the given
+ * jail service.
+ */
+void
+prison_service_data_set(struct prison_service *psrv, struct prison *pr,
+ void *data)
+{
+
+ mtx_assert(&pr->pr_mtx, MA_OWNED);
+ pr->pr_slots[psrv->ps_slotno] = data;
+}
+
+/*
+ * Function clears slots assigned for the given jail service in the given
+ * prison structure and returns current slot data.
+ */
+void *
+prison_service_data_del(struct prison_service *psrv, struct prison *pr)
+{
+ void *data;
+
+ mtx_assert(&pr->pr_mtx, MA_OWNED);
+ data = pr->pr_slots[psrv->ps_slotno];
+ pr->pr_slots[psrv->ps_slotno] = NULL;
+ return (data);
+}
+
+/*
+ * Function returns current data from the slot assigned to the given jail
+ * service for the given jail.
+ */
+void *
+prison_service_data_get(struct prison_service *psrv, struct prison *pr)
+{
+
+ mtx_assert(&pr->pr_mtx, MA_OWNED);
+ return (pr->pr_slots[psrv->ps_slotno]);
+}
+
static int
sysctl_jail_list(SYSCTL_HANDLER_ARGS)
{
@@ -709,21 +933,14 @@ sysctl_jail_list(SYSCTL_HANDLER_ARGS)
if (jailed(req->td->td_ucred))
return (0);
-retry:
- mtx_lock(&allprison_mtx);
- count = prisoncount;
- mtx_unlock(&allprison_mtx);
- if (count == 0)
+ sx_slock(&allprison_lock);
+ if ((count = prisoncount) == 0) {
+ sx_sunlock(&allprison_lock);
return (0);
+ }
sxp = xp = malloc(sizeof(*xp) * count, M_TEMP, M_WAITOK | M_ZERO);
- mtx_lock(&allprison_mtx);
- if (count != prisoncount) {
- mtx_unlock(&allprison_mtx);
- free(sxp, M_TEMP);
- goto retry;
- }
LIST_FOREACH(pr, &allprison, pr_list) {
mtx_lock(&pr->pr_mtx);
@@ -735,7 +952,7 @@ retry:
mtx_unlock(&pr->pr_mtx);
xp++;
}
- mtx_unlock(&allprison_mtx);
+ sx_sunlock(&allprison_lock);
error = SYSCTL_OUT(req, sxp, sizeof(*sxp) * count);
free(sxp, M_TEMP);
diff --git a/sys/sys/jail.h b/sys/sys/jail.h
index bdc42400f8ec..b8972f83d64d 100644
--- a/sys/sys/jail.h
+++ b/sys/sys/jail.h
@@ -54,7 +54,7 @@ MALLOC_DECLARE(M_PRISON);
* delete the struture when the last inmate is dead.
*
* Lock key:
- * (a) allprison_mtx
+ * (a) allprison_lock
* (p) locked by pr_mtx
* (c) set only during creation before the structure is shared, no mutex
* required to read
@@ -73,6 +73,7 @@ struct prison {
int pr_securelevel; /* (p) securelevel */
struct task pr_task; /* (d) destroy task */
struct mtx pr_mtx;
+ void **pr_slots; /* (p) additional data */
};
#endif /* _KERNEL || _WANT_PRISON */
@@ -91,6 +92,7 @@ extern int jail_chflags_allowed;
LIST_HEAD(prisonlist, prison);
extern struct prisonlist allprison;
+extern struct sx allprison_lock;
/*
* Kernel support functions for jail().
@@ -114,5 +116,21 @@ int prison_ip(struct ucred *cred, int flag, u_int32_t *ip);
int prison_priv_check(struct ucred *cred, int priv);
void prison_remote_ip(struct ucred *cred, int flags, u_int32_t *ip);
+/*
+ * Kernel jail services.
+ */
+struct prison_service;
+typedef int (*prison_create_t)(struct prison_service *psrv, struct prison *pr);
+typedef int (*prison_destroy_t)(struct prison_service *psrv, struct prison *pr);
+
+struct prison_service *prison_service_register(const char *name,
+ prison_create_t create, prison_destroy_t destroy);
+void prison_service_deregister(struct prison_service *psrv);
+
+void prison_service_data_set(struct prison_service *psrv, struct prison *pr,
+ void *data);
+void *prison_service_data_get(struct prison_service *psrv, struct prison *pr);
+void *prison_service_data_del(struct prison_service *psrv, struct prison *pr);
+
#endif /* _KERNEL */
#endif /* !_SYS_JAIL_H_ */