Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 7 Sep 1998 19:18:52 -0400 (EDT)
From:      robert@fledge.watson.org
To:        FreeBSD-gnats-submit@FreeBSD.ORG
Subject:   kern/7856: Patches to add lkm hooks to cmsg_data ancillary data processing
Message-ID:  <199809072318.TAA11967@fledge.watson.org>

next in thread | raw e-mail | index | archive | help

>Number:         7856
>Category:       kern
>Synopsis:       Patches to add lkm hooks to cmsg_data ancillary data processing
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    freebsd-bugs
>State:          open
>Quarter:
>Keywords:
>Date-Required:
>Class:          change-request
>Submitter-Id:   current-users
>Arrival-Date:   Mon Sep  7 16:20:00 PDT 1998
>Last-Modified:
>Originator:     Robert Watson
>Organization:
>Release:        3.0-CURRENT
>Environment:

3.0-CURRENT

>Description:

This set of patches generalizes the current ancillary data passing code
in the uipc_socket.c code.  That is, it pulls the distinct SCM_RIGHTS and
SCM_CREDS behavior out of a single function into their own calls for
processing.  It also provides lkm hooks for the three data-passing calls --
unp_internalize (called when SOL_SOCKET is used on a write to the kernel),
unp_externalize (called when the user process attempts to read ancillary
data with SOL_SOCKET set), and unp_gc (called to garbage collect ancillary
data when both ends of the socket close after unp_internalize but before
unp_externalize).  Now lkm's can hook their own behavior here by calling
at_unp_internalize, at_unp_externalize and at_unp_gc.

One sample use of this behavior is in the FreeBSD Authentication/Authorization
Tokens work (http://www.watson.org/fbsd-hardening/tokens/).  With the tokens
lkm loaded, additional authentication information is associated with each
process in the form of a set of "tokens" that the kernel tracks and maintains.
At times, it is desirable for a user process to pass one or more tokens to
anothe process via a UNIX domain socket.  Because the tokens may represent
authorization to perform restricted activities, they may not simply be passed
as data, but require the lkm to perform the transfer following an authorization
check.  The tokens module hooks these calls and defines a new SCM_ value,
SCM_TOKENS indicating that a token identifier follows in the cmsg_data (in
the same manner as file descriptor indexes are used to pass SCM_RIGHTS).

The cleanup and modularization of the code may help in debugging ancillary
data passing-related problems that exist in the code already.  Additionally,
I have removed some assumptions about the size of an mbuf in the internalize
function for SCM_CREDS, which while not technically incorrect, could cause
problems if the size of the mbuf was changed.  The lkm hooks would be useful
any time an lkm wants to provide the ability to pass kernel data structures
associated with processes between processes.  Other possible uses include
work with the VM system to allow mappings to be passed via UNIX domain sockets,
as well as other applications in security environments (for example, rights
associated with sandboxes).  An assumption concerning curproc being the
relevant proc reading the data still exists in the code (it was there in
the original code).  This appears to be a fair assumption.

>How-To-Repeat:
>Fix:
>Audit-Trail:
>Unformatted:
>Patches

These patches were generated against 3.0-CURRENT in May.  However, they
appear to apply cleanly (and run correctly) on versions as recent as a few
days ago (Sept 4ish).

diff -ur sys/kern/uipc_socket.c /usr/tmp/fdtokens/sys/kern/uipc_socket.c
--- sys/kern/uipc_socket.c	Fri May 15 16:11:30 1998
+++ /usr/tmp/fdtokens/sys/kern/uipc_socket.c	Fri Jul 17 23:10:59 1998
@@ -707,9 +707,11 @@
 			sbfree(&so->so_rcv, m);
 			if (controlp) {
 				if (pr->pr_domain->dom_externalize &&
-				    mtod(m, struct cmsghdr *)->cmsg_type ==
-				    SCM_RIGHTS)
-				   error = (*pr->pr_domain->dom_externalize)(m);
+				    mtod(m, struct cmsghdr *)->cmsg_level ==
+				    SOL_SOCKET)
+					/* assume curproc is the right proc */
+					/* XXXX */
+				   error = (*pr->pr_domain->dom_externalize)(m, curproc);
 				*controlp = m;
 				so->so_rcv.sb_mb = m->m_next;
 				m->m_next = 0;
diff -ur sys/kern/uipc_usrreq.c /usr/tmp/fdtokens/sys/kern/uipc_usrreq.c
--- sys/kern/uipc_usrreq.c	Fri May 15 16:11:31 1998
+++ /usr/tmp/fdtokens/sys/kern/uipc_usrreq.c	Mon Jul 20 23:45:09 1998
@@ -58,6 +58,26 @@
 #include <vm/vm_zone.h>
 
 struct	vm_zone *unp_zone;
+
+typedef struct unp_internalize_list_element {
+    struct unp_internalize_list_element *next;
+    unp_internalize_fn function;
+} *uile_p;
+
+typedef struct unp_externalize_list_element {
+    struct unp_externalize_list_element *next;
+    unp_externalize_fn function;
+} *uele_p;
+
+typedef struct unp_gc_list_element {
+    struct unp_gc_list_element *next;
+    unp_gc_fn function;
+} *ugle_p;
+
+static uile_p unp_internalize_list = 0;
+static uele_p unp_externalize_list = 0;
+static ugle_p unp_gc_list = 0;
+
 static	unp_gen_t unp_gencnt;
 static	u_int unp_count;
 
@@ -843,17 +863,17 @@
 }
 #endif
 
-int
-unp_externalize(rights)
-	struct mbuf *rights;
+static int
+unp_externalize_rights(control, p)
+        struct mbuf *control;
+        struct proc *p;
 {
-	struct proc *p = curproc;		/* XXX */
-	register int i;
-	register struct cmsghdr *cm = mtod(rights, struct cmsghdr *);
+	register struct cmsghdr *cm = mtod(control, struct cmsghdr *);
 	register struct file **rp = (struct file **)(cm + 1);
 	register struct file *fp;
+
 	int newfds = (cm->cmsg_len - sizeof(*cm)) / sizeof (int);
-	int f;
+	int f, i;
 
 	/*
 	 * if the new FD's will not fit, then we free them all
@@ -884,6 +904,57 @@
 	return (0);
 }
 
+static int
+unp_externalize_creds(control, p)
+        struct mbuf *control;
+	struct proc *p;
+{
+        /* nothing to do here, really */
+	printf("%%% unp_externalize_creds(): proc %d\n", p->p_pid);
+        return(0);
+
+}
+
+int
+unp_externalize(control, p)
+        struct mbuf *control;
+	struct proc *p;
+{
+        register int i;
+	register struct cmsghdr *cm = mtod(control, struct cmsghdr *);
+        uele_p ep;
+
+	if (cm->cmsg_level != SOL_SOCKET) 
+	        return (EINVAL);
+
+	switch(cm->cmsg_type) {
+    	        /* built-in types */
+	case SCM_CREDS:
+	    return (unp_externalize_creds(control, p));
+	case SCM_RIGHTS:
+	    return (unp_externalize_rights(control, p));
+	default:
+	}
+
+	/* only get here if not a built-in type */
+
+	/* 
+	 * scan list -- return on first response of non-EINVAL, continue
+	 * on EINVAL until done.  Then return EINVAL 
+	 */
+
+	ep = unp_externalize_list;
+	while (ep) {
+	    i = ((*ep->function)(control, p));
+	    if (i != EINVAL) return(i);
+            ep = ep->next;
+	}
+	/* was not accepted by any other registered functions 
+	 * should we call dispose here?  Probably not, as if we don't have
+	 * an externlize handler, we won't have a dispose handler*/
+	return (EINVAL);
+}
+
 void
 unp_init(void)
 {
@@ -898,22 +969,13 @@
 #define	MIN(a,b) (((a)<(b))?(a):(b))
 #endif
 
-static int
-unp_internalize(control, p)
+static int unp_internalize_creds(control, p)
 	struct mbuf *control;
 	struct proc *p;
 {
-	struct filedesc *fdp = p->p_fd;
 	register struct cmsghdr *cm = mtod(control, struct cmsghdr *);
-	register struct file **rp;
-	register struct file *fp;
-	register int i, fd;
 	register struct cmsgcred *cmcred;
-	int oldfds;
-
-	if ((cm->cmsg_type != SCM_RIGHTS && cm->cmsg_type != SCM_CREDS) ||
-	    cm->cmsg_level != SOL_SOCKET || cm->cmsg_len != control->m_len)
-		return (EINVAL);
+        int i;
 
 	/*
 	 * Fill in credential information.
@@ -930,6 +992,19 @@
 			cmcred->cmcred_groups[i] = p->p_ucred->cr_groups[i];
 		return(0);
 	}
+	panic("unp_internalize_creds: control is not SCM_CREDS"); /* what? */
+}
+
+static int unp_internalize_rights(control, p)
+	struct mbuf *control;
+	struct proc *p;
+{
+	struct filedesc *fdp = p->p_fd;
+	register struct cmsghdr *cm = mtod(control, struct cmsghdr *);
+	register struct file **rp;
+	register struct file *fp;
+	register int i, fd;
+	int oldfds;
 
 	oldfds = (cm->cmsg_len - sizeof (*cm)) / sizeof (int);
 	/*
@@ -959,6 +1034,175 @@
 	return (0);
 }
 
+static int
+unp_internalize(control, p)
+	struct mbuf *control;
+	struct proc *p;
+{
+	register struct cmsghdr *cm = mtod(control, struct cmsghdr *);
+	uile_p ep;
+	int i;
+
+	if ((cm->cmsg_level != SOL_SOCKET) ||
+	    (cm->cmsg_len != control->m_len))
+		return (EINVAL);
+
+	/* 
+	 * Assume: SOL_SOCKET is the only level we would get, so decide based
+	 * on type
+	 */
+
+	switch(cm->cmsg_type) {
+		/* built-in types */
+	case SCM_CREDS:
+	    return (unp_internalize_creds(control, p));
+	case SCM_RIGHTS:
+	    return (unp_internalize_rights(control, p));
+	default:
+	}
+
+	/* 
+	 * only get here if not a built-in type, look to see if anyone
+	 * else has registered for it
+	 */
+
+	/* 
+	 * scan list -- return on the first response of non-EINVAL --
+	 * keep going for EINVAL, return on anything else.  At the
+	 * end, return EINVAL as should have exited 
+	 */
+
+	ep = unp_internalize_list;
+	while (ep) {
+	    i = ((*ep->function)(control,  p));
+	    if (i != EINVAL) return(i);
+            ep = ep->next;
+	}
+	/* was not accepted by any other registered functions */
+	return(EINVAL);
+}
+
+int
+at_unp_internalize(function)
+        unp_internalize_fn function;
+{  
+        uile_p ep;
+
+	if (rm_at_unp_internalize(function))
+	        printf("unp_internalize callout entry already present\n");
+	ep = malloc(sizeof(*ep), M_TEMP, M_NOWAIT);
+	if (ep == NULL)
+	        return(ENOMEM);
+	ep->next = unp_internalize_list;
+	ep->function = function;
+	unp_internalize_list = ep;
+	return(0);
+}
+
+int
+rm_at_unp_internalize(function)
+        unp_internalize_fn function;
+{
+        uile_p *epp, ep;
+	int count;
+
+	count = 0;
+	epp = &unp_internalize_list;
+	ep = *epp;
+	while(ep) {
+	        if (ep->function == function) {
+		        *epp = ep->next;
+			free(ep, M_TEMP);
+			count++;
+		} else {
+		        epp = &ep->next;
+		}
+		ep = *epp;
+	}
+	return (count);
+}
+
+int
+at_unp_externalize(function)
+        unp_externalize_fn function;
+{  
+        uele_p ep;
+
+	if (rm_at_unp_externalize(function))
+	        printf("unp_externalize callout entry already present\n");
+	ep = malloc(sizeof(*ep), M_TEMP, M_NOWAIT);
+	if (ep == NULL)
+	        return(ENOMEM);
+	ep->next = unp_externalize_list;
+	ep->function = function;
+	unp_externalize_list = ep;
+	return(0);
+}
+
+int
+rm_at_unp_externalize(function)
+        unp_externalize_fn function;
+{
+        uele_p *epp, ep;
+	int count;
+
+	count = 0;
+	epp = &unp_externalize_list;
+	ep = *epp;
+	while(ep) {
+	        if (ep->function == function) {
+		        *epp = ep->next;
+			free(ep, M_TEMP);
+			count++;
+		} else {
+		        epp = &ep->next;
+		}
+		ep = *epp;
+	}
+	return (count);
+}
+
+int
+at_unp_gc(function)
+        unp_gc_fn function;
+{
+        ugle_p ep;
+
+        if (rm_at_unp_gc(function))
+                printf("unp_gc callout entry already present\n");
+        ep = malloc(sizeof(*ep), M_TEMP, M_NOWAIT);
+        if (ep == NULL)
+                return(ENOMEM);
+        ep->next = unp_gc_list;
+        ep->function = function;
+        unp_gc_list = ep;
+        return(0);
+}
+
+int
+rm_at_unp_gc(function)
+        unp_gc_fn function;
+{
+        ugle_p *epp, ep;
+        int count;
+
+        count = 0;
+        epp = &unp_gc_list;
+        ep = *epp;
+        while(ep) {
+                if (ep->function == function) {
+                        *epp = ep->next;
+                        free(ep, M_TEMP);
+                        count++;
+                } else {
+                        epp = &ep->next;
+                }
+                ep = *epp;
+        }
+        return (count);
+}
+
+
 static int	unp_defer, unp_gcing;
 
 static void
@@ -1132,6 +1376,34 @@
 }
 
 static void
+unp_gc_rights(cm, op)
+	register struct cmsghdr *cm;
+	void (*op) __P((struct file *));
+{
+        register struct file **rp;
+        register int i;
+        int qfds;
+
+	if (cm->cmsg_type != SCM_RIGHTS)
+	    panic("unp_gc_rights: non-SCM_RIGHTS cmsg");
+
+        qfds = (cm->cmsg_len - sizeof *cm)
+               / sizeof (struct file *);
+        rp = (struct file **)(cm + 1);
+        for (i = 0; i < qfds; i++)
+            (*op)(*rp++);
+}
+
+static void 
+unp_gc_creds(cm)
+	register struct cmsghdr *cm;
+{
+	/* do nothing */
+	if (cm->cmsg_type != SCM_CREDS)
+	    panic("unp_gc_creds: non-SCM_CREDS cmsg");
+}
+
+static void
 unp_scan(m0, op)
 	register struct mbuf *m0;
 	void (*op) __P((struct file *));
@@ -1141,23 +1413,42 @@
 	register struct cmsghdr *cm;
 	register int i;
 	int qfds;
+	ugle_p ep;
 
 	while (m0) {
-		for (m = m0; m; m = m->m_next)
+		for (m = m0; m; m = m->m_next) /* walk buffers of packet */
 			if (m->m_type == MT_CONTROL &&
 			    m->m_len >= sizeof(*cm)) {
 				cm = mtod(m, struct cmsghdr *);
-				if (cm->cmsg_level != SOL_SOCKET ||
-				    cm->cmsg_type != SCM_RIGHTS)
-					continue;
-				qfds = (cm->cmsg_len - sizeof *cm)
-						/ sizeof (struct file *);
-				rp = (struct file **)(cm + 1);
-				for (i = 0; i < qfds; i++)
-					(*op)(*rp++);
-				break;		/* XXX, but saves time */
+				if (cm->cmsg_level == SOL_SOCKET) {
+				    switch(cm->cmsg_type) {
+				    case SCM_CREDS:
+					unp_gc_creds(cm);
+					break; /* assume no more SOL_SOCKET */
+				    case SCM_RIGHTS:
+					unp_gc_rights(cm, op);
+					break; /* assume no more SOL_SOCKET */
+				    default:
+				    }
+				    /* now check callout list for a claimer of
+		    		       this cmsg_type.  Call sequentially until
+				       one returns non-EINVAL.  When done,
+				       regardless of whether we succeeded,
+				       break -- should only be one SOL_SOCKET
+				       per mbuf.  This was an assumption in the
+				       base 4.4bsdlite code, so do the same here
+				       */
+				    ep = unp_gc_list;
+			    	    while (ep) {
+				        i = ((*ep->function)(cm));
+				        if (i != EINVAL) break;
+				        ep = ep->next;
+				    }
+				    break; /* assume no more SOL_SOCKET */
+				}
+				/* ignore the case of non-SOL_SOCKET */
 			}
-		m0 = m0->m_act;
+		m0 = m0->m_act; /* next packet */
 	}
 }
 
diff -ur sys/sys/domain.h /usr/tmp/fdtokens/sys/sys/domain.h
--- sys/sys/domain.h	Tue Nov 18 01:48:43 1997
+++ /usr/tmp/fdtokens/sys/sys/domain.h	Fri Jul 17 16:02:25 1998
@@ -52,7 +52,7 @@
 	void	(*dom_init)		/* initialize domain data structures */
 		__P((void));
 	int	(*dom_externalize)	/* externalize access rights */
-		__P((struct mbuf *));
+		__P((struct mbuf *, struct proc *p));
 	void	(*dom_dispose)		/* dispose of internalized rights */
 		__P((struct mbuf *));
 	struct	protosw *dom_protosw, *dom_protoswNPROTOSW;
diff -ur sys/sys/systm.h /usr/tmp/fdtokens/sys/sys/systm.h
--- sys/sys/systm.h	Thu Jun 25 08:32:21 1998
+++ /usr/tmp/fdtokens/sys/sys/systm.h	Mon Jul 20 23:13:30 1998
@@ -270,6 +270,22 @@
 int	at_shutdown __P((bootlist_fn function, void *arg, int position));
 int	rm_at_shutdown __P((bootlist_fn function, void *arg));
 
+/* unp_{ex,in,gc}ternalize* function callouts */
+typedef int (*unp_internalize_fn) __P((void *control, struct proc *p));
+/* void * -> struct mbuf *control */
+typedef int (*unp_externalize_fn) __P((void *control, struct proc *p));
+/* void * -> struct mbuf *control */
+typedef int (*unp_gc_fn) __P((void *control));
+/* void * -> struct cmsghdr *control */
+
+int	at_unp_internalize __P((unp_internalize_fn function));
+int	rm_at_unp_internalize __P((unp_internalize_fn function));
+int	at_unp_externalize __P((unp_externalize_fn function));
+int	rm_at_unp_externalize __P((unp_externalize_fn function));
+int	at_unp_gc __P((unp_gc_fn function));
+int	rm_at_unp_gc __P((unp_gc_fn function));
+
+
 /*
  * Not exactly a callout LIST, but a callout entry.
  * Allow an external module to define a hardware watchdog tickler.
diff -ur sys/sys/un.h /usr/tmp/fdtokens/sys/sys/un.h
--- sys/sys/un.h	Sun May 17 14:17:59 1998
+++ /usr/tmp/fdtokens/sys/sys/un.h	Sun Jul 12 22:35:23 1998
@@ -54,7 +54,7 @@
 		struct mbuf *nam, struct mbuf *control));
 int	unp_connect2 __P((struct socket *so, struct socket *so2));
 void	unp_dispose __P((struct mbuf *m));
-int	unp_externalize __P((struct mbuf *rights));
+int	unp_externalize __P((struct mbuf *control, struct proc *p));
 void	unp_init __P((void));
 extern	struct pr_usrreqs uipc_usrreqs;
 #else /* !KERNEL */

To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-bugs" in the body of the message



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?199809072318.TAA11967>