Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 30 Sep 2013 19:48:29 GMT
From:      Chariton Karamitas <chakaram@auth.gr>
To:        freebsd-gnats-submit@FreeBSD.org
Subject:   kern/182521: [patch] BSM subsystem modifications
Message-ID:  <201309301948.r8UJmTMH005830@oldred.freebsd.org>
Resent-Message-ID: <201309301950.r8UJo0dd078211@freefall.freebsd.org>

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

>Number:         182521
>Category:       kern
>Synopsis:       [patch] BSM subsystem modifications
>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 30 19:50:00 UTC 2013
>Closed-Date:
>Last-Modified:
>Originator:     Chariton Karamitas
>Release:        9.0-RELEASE
>Organization:
Electrical Engineering Department, Aristotle University of Thessaloniki, Greece
>Environment:
>Description:
As part of my diploma thesis at the engineering department, I modified the audit subsystem to return the lwpid of the audited thread in the subject token of an audit record. Normally, the kernel produces records which only contain the sid and pid; this makes the process of separating record streams coming from different threads of a multithreaded application a hard (or even impossible) task. Hopefully, the attached patch can be used to bypass this limitation, however it comes at a price; modifications are required in kernel land as well as in userland. I understand that deciding to merge these changes in the FreeBSD code base is a hard choice for many reasons (quality standards, number of affected components and so on), nevertheless, the patch may be helpful for people building tools on top of the audit mechanism :)

The patch also adds missing AUDIT_ARG_*() macros in certain network related system calls like bind().

If you have any questions/suggestions, I can be reached at the mail address specified in this message. Alternatively, you can talk to my supervisor who is also a member at the FreeBSD forums: George Mamalakis <mamalos@eng.auth.gr>
>How-To-Repeat:

>Fix:


Patch attached with submission follows:

diff -ur src.orig/contrib/openbsm/bsm/libbsm.h src/contrib/openbsm/bsm/libbsm.h
--- src.orig/contrib/openbsm/bsm/libbsm.h	2012-01-03 05:24:43.000000000 +0200
+++ src/contrib/openbsm/bsm/libbsm.h	2013-01-14 00:19:18.000000000 +0200
@@ -601,6 +601,7 @@
 	u_int32_t	egid;
 	u_int32_t	ruid;
 	u_int32_t	rgid;
+	u_int32_t	lwpid;
 	u_int32_t	pid;
 	u_int32_t	sid;
 	au_tid32_t	tid;
@@ -612,6 +613,7 @@
 	u_int32_t	egid;
 	u_int32_t	ruid;
 	u_int32_t	rgid;
+	u_int32_t	lwpid;
 	u_int32_t	pid;
 	u_int32_t	sid;
 	au_tid64_t	tid;
@@ -636,6 +638,7 @@
 	u_int32_t	egid;
 	u_int32_t	ruid;
 	u_int32_t	rgid;
+	u_int32_t	lwpid;
 	u_int32_t	pid;
 	u_int32_t	sid;
 	au_tidaddr32_t	tid;
@@ -647,6 +650,7 @@
 	u_int32_t	egid;
 	u_int32_t	ruid;
 	u_int32_t	rgid;
+	u_int32_t	lwpid;
 	u_int32_t	pid;
 	u_int32_t	sid;
 	au_tidaddr64_t	tid;
@@ -1088,8 +1092,8 @@
  * XXXRW: In Apple's bsm-8, these are marked __APPLE_API_PRIVATE.
  */
 int	audit_write_success(short event_code, token_t *misctok, au_id_t auid,
-	    uid_t euid, gid_t egid, uid_t ruid, gid_t rgid, pid_t pid,
-	    au_asid_t sid, au_tid_t *tid);
+	    uid_t euid, gid_t egid, uid_t ruid, gid_t rgid, lwpid_t lwpid, 
+	    pid_t pid, au_asid_t sid, au_tid_t *tid);
 
 /*
  * audit_write_success_self()
@@ -1158,7 +1162,7 @@
  */
 int	audit_write_failure(short event_code, char *errmsg, int errret,
 	    au_id_t auid, uid_t euid, gid_t egid, uid_t ruid, gid_t rgid,
-	    pid_t pid, au_asid_t sid, au_tid_t *tid);
+	    lwpid_t lwpid, pid_t pid, au_asid_t sid, au_tid_t *tid);
 
 /*
  * audit_write_failure_self()
@@ -1215,7 +1219,7 @@
  * XXXRW: In Apple's bsm-8, these are marked __APPLE_API_PRIVATE.
  */
 int	audit_write_failure_na(short event_code, char *errmsg, int errret,
-	    uid_t euid, gid_t egid, pid_t pid, au_tid_t *tid);
+	    uid_t euid, gid_t egid, lwpid_t lwpid, pid_t pid, au_tid_t *tid);
 
 /* END au_write() WRAPPERS */
 
diff -ur src.orig/contrib/openbsm/libauditd/auditd_lib.c src/contrib/openbsm/libauditd/auditd_lib.c
--- src.orig/contrib/openbsm/libauditd/auditd_lib.c	2012-01-03 05:24:43.000000000 +0200
+++ src/contrib/openbsm/libauditd/auditd_lib.c	2013-01-14 00:41:51.000000000 +0200
@@ -44,6 +44,7 @@
 
 #include <sys/stat.h>
 #include <sys/time.h>
+#include <sys/thr.h>
 
 #include <netinet/in.h>
 
@@ -943,10 +944,13 @@
 	int aufd;
 	uid_t uid;
 	pid_t pid;
+	long lwpid = -1;
 	char *autext = NULL;
 	token_t *tok;
 	struct auditinfo_addr aia;
 
+	thr_self(&lwpid);
+
 	if (event == AUE_audit_startup)
 		asprintf(&autext, "%s::Audit startup", getprogname());
 	else if (event == AUE_audit_shutdown)
@@ -965,7 +969,7 @@
 	bzero(&aia, sizeof(aia));
 	uid = getuid(); pid = getpid();
 	if ((tok = au_to_subject32_ex(uid, geteuid(), getegid(), uid, getgid(),
-	     pid, pid, &aia.ai_termid)) != NULL)
+	     (lwpid_t)lwpid, pid, pid, &aia.ai_termid)) != NULL)
 		au_write(aufd, tok);
 	if ((tok = au_to_text(autext)) != NULL)
 		au_write(aufd, tok);
diff -ur src.orig/contrib/openbsm/libbsm/bsm_io.c src/contrib/openbsm/libbsm/bsm_io.c
--- src.orig/contrib/openbsm/libbsm/bsm_io.c	2012-01-03 05:24:43.000000000 +0200
+++ src/contrib/openbsm/libbsm/bsm_io.c	2013-01-13 22:36:37.000000000 +0200
@@ -544,19 +544,19 @@
 			fprintf(fp, "<socket-inet6 ");
 
 		case AUT_SUBJECT32:
-			fprintf(fp, "<subject ");
+			fprintf(fp, "<subject type=\"32\" ");
 			break;
 
 		case AUT_SUBJECT64:
-			fprintf(fp, "<subject ");
+			fprintf(fp, "<subject type=\"64\" ");
 			break;
 
 		case AUT_SUBJECT32_EX:
-			fprintf(fp, "<subject ");
+			fprintf(fp, "<subject type=\"32_ex\" ");
 			break;
 
 		case AUT_SUBJECT64_EX:
-			fprintf(fp, "<subject ");
+			fprintf(fp, "<subject type=\"64_ex\" ");
 			break;
 
 		case AUT_TEXT:
@@ -3340,6 +3340,10 @@
 	if (err)
 		return (-1);
 
+	READ_TOKEN_U_INT32(buf, len, tok->tt.subj32.lwpid, tok->len, err);
+	if (err)
+		return (-1);
+
 	READ_TOKEN_U_INT32(buf, len, tok->tt.subj32.pid, tok->len, err);
 	if (err)
 		return (-1);
@@ -3382,6 +3386,9 @@
 		open_attr(fp, "rgid");
 		print_group(fp, tok->tt.subj32.rgid, raw);
 		close_attr(fp);
+		open_attr(fp, "lwpid");
+		print_user(fp, tok->tt.subj32.lwpid, raw);
+		close_attr(fp);
 		open_attr(fp,"pid");
 		print_4_bytes(fp, tok->tt.subj32.pid, "%u");
 		close_attr(fp);
@@ -3405,6 +3412,8 @@
 		print_delim(fp, del);
 		print_group(fp, tok->tt.subj32.rgid, raw);
 		print_delim(fp, del);
+		print_4_bytes(fp, tok->tt.subj32.lwpid, "%u");
+		print_delim(fp, del);
 		print_4_bytes(fp, tok->tt.subj32.pid, "%u");
 		print_delim(fp, del);
 		print_4_bytes(fp, tok->tt.subj32.sid, "%u");
@@ -3452,6 +3461,10 @@
 	if (err)
 		return (-1);
 
+	READ_TOKEN_U_INT32(buf, len, tok->tt.subj64.lwpid, tok->len, err);
+	if (err)
+		return (-1);
+
 	READ_TOKEN_U_INT32(buf, len, tok->tt.subj64.pid, tok->len, err);
 	if (err)
 		return (-1);
@@ -3494,6 +3507,9 @@
 		open_attr(fp, "rgid");
 		print_group(fp, tok->tt.subj64.rgid, raw);
 		close_attr(fp);
+		open_attr(fp, "lwpid");
+		print_4_bytes(fp, tok->tt.subj64.lwpid, "%u");
+		close_attr(fp);
 		open_attr(fp, "pid");
 		print_4_bytes(fp, tok->tt.subj64.pid, "%u");
 		close_attr(fp);
@@ -3517,6 +3533,8 @@
 		print_delim(fp, del);
 		print_group(fp, tok->tt.subj64.rgid, raw);
 		print_delim(fp, del);
+		print_4_bytes(fp, tok->tt.subj64.lwpid, "%u");
+		print_delim(fp, del);
 		print_4_bytes(fp, tok->tt.subj64.pid, "%u");
 		print_delim(fp, del);
 		print_4_bytes(fp, tok->tt.subj64.sid, "%u");
@@ -3565,6 +3583,10 @@
 	if (err)
 		return (-1);
 
+	READ_TOKEN_U_INT32(buf, len, tok->tt.subj32_ex.lwpid, tok->len, err);
+	if (err)
+		return (-1);
+
 	READ_TOKEN_U_INT32(buf, len, tok->tt.subj32_ex.pid, tok->len, err);
 	if (err)
 		return (-1);
@@ -3621,6 +3643,9 @@
 		open_attr(fp, "rgid");
 		print_group(fp, tok->tt.subj32_ex.rgid, raw);
 		close_attr(fp);
+		open_attr(fp, "lwpid");
+		print_4_bytes(fp, tok->tt.subj32_ex.lwpid, "%u");
+		close_attr(fp);
 		open_attr(fp, "pid");
 		print_4_bytes(fp, tok->tt.subj32_ex.pid, "%u");
 		close_attr(fp);
@@ -3645,6 +3670,8 @@
 		print_delim(fp, del);
 		print_group(fp, tok->tt.subj32_ex.rgid, raw);
 		print_delim(fp, del);
+		print_4_bytes(fp, tok->tt.subj32_ex.lwpid, "%u");
+		print_delim(fp, del);
 		print_4_bytes(fp, tok->tt.subj32_ex.pid, "%u");
 		print_delim(fp, del);
 		print_4_bytes(fp, tok->tt.subj32_ex.sid, "%u");
@@ -3694,6 +3721,10 @@
 	if (err)
 		return (-1);
 
+	READ_TOKEN_U_INT32(buf, len, tok->tt.subj64_ex.lwpid, tok->len, err);
+	if (err)
+		return (-1);
+
 	READ_TOKEN_U_INT32(buf, len, tok->tt.subj64_ex.pid, tok->len, err);
 	if (err)
 		return (-1);
@@ -3749,6 +3780,9 @@
 		open_attr(fp, "rgid");
 		print_group(fp, tok->tt.subj64_ex.rgid, raw);
 		close_attr(fp);
+		open_attr(fp, "lwpid");
+		print_4_bytes(fp, tok->tt.subj64_ex.lwpid, "%u");
+		close_attr(fp);
 		open_attr(fp, "pid");
 		print_4_bytes(fp, tok->tt.subj64_ex.pid, "%u");
 		close_attr(fp);
@@ -3773,6 +3807,8 @@
 		print_delim(fp, del);
 		print_group(fp, tok->tt.subj64_ex.rgid, raw);
 		print_delim(fp, del);
+		print_4_bytes(fp, tok->tt.subj64_ex.lwpid, "%u");
+		print_delim(fp, del);
 		print_4_bytes(fp, tok->tt.subj64_ex.pid, "%u");
 		print_delim(fp, del);
 		print_4_bytes(fp, tok->tt.subj64_ex.sid, "%u");
diff -ur src.orig/contrib/openbsm/libbsm/bsm_token.c src/contrib/openbsm/libbsm/bsm_token.c
--- src.orig/contrib/openbsm/libbsm/bsm_token.c	2012-01-03 05:24:43.000000000 +0200
+++ src/contrib/openbsm/libbsm/bsm_token.c	2013-01-14 00:16:28.000000000 +0200
@@ -59,6 +59,7 @@
 #include <sys/socket.h>
 #include <sys/time.h>
 #include <sys/un.h>
+#include <sys/thr.h>
 
 #include <sys/ipc.h>
 
@@ -75,6 +76,7 @@
 #include <bsm/audit_internal.h>
 #include <bsm/libbsm.h>
 
+
 #define	GET_TOKEN_AREA(t, dptr, length) do {				\
 	(t) = malloc(sizeof(token_t));					\
 	if ((t) != NULL) {						\
@@ -1102,12 +1104,12 @@
  */
 token_t *
 au_to_subject32(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid, gid_t rgid,
-    pid_t pid, au_asid_t sid, au_tid_t *tid)
+    lwpid_t lwpid, pid_t pid, au_asid_t sid, au_tid_t *tid)
 {
 	token_t *t;
 	u_char *dptr = NULL;
 
-	GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 9 * sizeof(u_int32_t));
+	GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 10 * sizeof(u_int32_t));
 	if (t == NULL)
 		return (NULL);
 
@@ -1117,6 +1119,7 @@
 	ADD_U_INT32(dptr, egid);
 	ADD_U_INT32(dptr, ruid);
 	ADD_U_INT32(dptr, rgid);
+	ADD_U_INT32(dptr, lwpid);
 	ADD_U_INT32(dptr, pid);
 	ADD_U_INT32(dptr, sid);
 	ADD_U_INT32(dptr, tid->port);
@@ -1127,12 +1130,12 @@
 
 token_t *
 au_to_subject64(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid, gid_t rgid,
-    pid_t pid, au_asid_t sid, au_tid_t *tid)
+    lwpid_t lwpid, pid_t pid, au_asid_t sid, au_tid_t *tid)
 {
 	token_t *t;
 	u_char *dptr = NULL;
 
-	GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 7 * sizeof(u_int32_t) +
+	GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 8 * sizeof(u_int32_t) +
 	    sizeof(u_int64_t) + sizeof(u_int32_t));
 	if (t == NULL)
 		return (NULL);
@@ -1143,6 +1146,7 @@
 	ADD_U_INT32(dptr, egid);
 	ADD_U_INT32(dptr, ruid);
 	ADD_U_INT32(dptr, rgid);
+	ADD_U_INT32(dptr, lwpid);
 	ADD_U_INT32(dptr, pid);
 	ADD_U_INT32(dptr, sid);
 	ADD_U_INT64(dptr, tid->port);
@@ -1153,11 +1157,11 @@
 
 token_t *
 au_to_subject(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid, gid_t rgid,
-    pid_t pid, au_asid_t sid, au_tid_t *tid)
+    lwpid_t lwpid, pid_t pid, au_asid_t sid, au_tid_t *tid)
 {
 
-	return (au_to_subject32(auid, euid, egid, ruid, rgid, pid, sid,
-	    tid));
+	return (au_to_subject32(auid, euid, egid, ruid, rgid, lwpid, pid, 
+	    sid, tid));
 }
 
 /*
@@ -1176,16 +1180,16 @@
  */
 token_t *
 au_to_subject32_ex(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid,
-    gid_t rgid, pid_t pid, au_asid_t sid, au_tid_addr_t *tid)
+    gid_t rgid, lwpid_t lwpid, pid_t pid, au_asid_t sid, au_tid_addr_t *tid)
 {
 	token_t *t;
 	u_char *dptr = NULL;
 
 	if (tid->at_type == AU_IPv4)
-		GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 10 *
+		GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 11 *
 		    sizeof(u_int32_t));
 	else if (tid->at_type == AU_IPv6)
-		GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 13 *
+		GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 14 *
 		    sizeof(u_int32_t));
 	else {
 		errno = EINVAL;
@@ -1200,6 +1204,7 @@
 	ADD_U_INT32(dptr, egid);
 	ADD_U_INT32(dptr, ruid);
 	ADD_U_INT32(dptr, rgid);
+	ADD_U_INT32(dptr, lwpid);
 	ADD_U_INT32(dptr, pid);
 	ADD_U_INT32(dptr, sid);
 	ADD_U_INT32(dptr, tid->at_port);
@@ -1214,18 +1219,18 @@
 
 token_t *
 au_to_subject64_ex(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid,
-    gid_t rgid, pid_t pid, au_asid_t sid, au_tid_addr_t *tid)
+    gid_t rgid, lwpid_t lwpid, pid_t pid, au_asid_t sid, au_tid_addr_t *tid)
 {
 	token_t *t;
 	u_char *dptr = NULL;
 
 	if (tid->at_type == AU_IPv4)
 		GET_TOKEN_AREA(t, dptr, sizeof(u_char) +
-		    7 * sizeof(u_int32_t) + sizeof(u_int64_t) +
+		    8 * sizeof(u_int32_t) + sizeof(u_int64_t) +
 		    2 * sizeof(u_int32_t));
 	else if (tid->at_type == AU_IPv6)
 		GET_TOKEN_AREA(t, dptr, sizeof(u_char) +
-		    7 * sizeof(u_int32_t) + sizeof(u_int64_t) +
+		    8 * sizeof(u_int32_t) + sizeof(u_int64_t) +
 		    5 * sizeof(u_int32_t));
 	else {
 		errno = EINVAL;
@@ -1240,6 +1245,7 @@
 	ADD_U_INT32(dptr, egid);
 	ADD_U_INT32(dptr, ruid);
 	ADD_U_INT32(dptr, rgid);
+	ADD_U_INT32(dptr, lwpid);
 	ADD_U_INT32(dptr, pid);
 	ADD_U_INT32(dptr, sid);
 	ADD_U_INT64(dptr, tid->at_port);
@@ -1254,11 +1260,11 @@
 
 token_t *
 au_to_subject_ex(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid,
-    gid_t rgid, pid_t pid, au_asid_t sid, au_tid_addr_t *tid)
+    gid_t rgid, lwpid_t lwpid, pid_t pid, au_asid_t sid, au_tid_addr_t *tid)
 {
 
-	return (au_to_subject32_ex(auid, euid, egid, ruid, rgid, pid, sid,
-	    tid));
+	return (au_to_subject32_ex(auid, euid, egid, ruid, rgid, lwpid, pid, 
+	    sid, tid));
 }
 
 #if !defined(_KERNEL) && !defined(KERNEL) && defined(HAVE_AUDIT_SYSCALLS)
@@ -1271,6 +1277,9 @@
 {
 	auditinfo_t auinfo;
 	auditinfo_addr_t aia;
+	long lwpid = -1;
+
+	thr_self(&lwpid);
 
 	/*
 	 * Try to use getaudit_addr(2) first.  If this kernel does not support
@@ -1281,8 +1290,8 @@
 			if (getaudit(&auinfo) != 0)
 				return (NULL);
 			return (au_to_subject32(auinfo.ai_auid, geteuid(),
-				getegid(), getuid(), getgid(), getpid(),
-				auinfo.ai_asid, &auinfo.ai_termid));
+				getegid(), getuid(), getgid(), (lwpid_t)lwpid,
+				getpid(), auinfo.ai_asid, &auinfo.ai_termid));
 		} else {
 			/* getaudit_addr(2) failed for some other reason. */
 			return (NULL); 
@@ -1290,7 +1299,8 @@
 	} 
 
 	return (au_to_subject32_ex(aia.ai_auid, geteuid(), getegid(), getuid(),
-		getgid(), getpid(), aia.ai_asid, &aia.ai_termid));
+		getgid(), (lwpid_t)lwpid, getpid(), aia.ai_asid, 
+		&aia.ai_termid));
 }
 #endif
 
diff -ur src.orig/contrib/openbsm/libbsm/bsm_wrappers.c src/contrib/openbsm/libbsm/bsm_wrappers.c
--- src.orig/contrib/openbsm/libbsm/bsm_wrappers.c	2012-01-03 05:24:43.000000000 +0200
+++ src/contrib/openbsm/libbsm/bsm_wrappers.c	2013-01-14 00:18:12.000000000 +0200
@@ -41,6 +41,7 @@
 #endif
 
 #include <sys/sysctl.h>
+#include <sys/thr.h>
 
 #include <bsm/libbsm.h>
 
@@ -66,6 +67,7 @@
 	int acond;
 	va_list ap;
 	pid_t pid;
+	long lwpid = -1;
 	int error, afd, subj_ex;
 	struct auditinfo ai;
 	struct auditinfo_addr aia;
@@ -134,14 +136,16 @@
 	if (aia.ai_termid.at_type == AU_IPv6)
 		subj_ex = 1;
 	pid = getpid();
+	thr_self(&lwpid);
 	if (subj_ex == 0) {
 		atid.port = aia.ai_termid.at_port;
 		atid.machine = aia.ai_termid.at_addr[0];
 		token = au_to_subject32(auid, geteuid(), getegid(),
-		    getuid(), getgid(), pid, pid, &atid);
+		    getuid(), getgid(), (lwpid_t)lwpid, pid, pid, &atid);
 	} else
 		token = au_to_subject_ex(auid, geteuid(), getegid(),
-		    getuid(), getgid(), pid, pid, &aia.ai_termid);
+		    getuid(), getgid(), (lwpid_t)lwpid, pid, pid, 
+		    &aia.ai_termid);
 	if (token == NULL) {
 		syslog(LOG_AUTH | LOG_ERR,
 		    "audit: unable to build subject token");
@@ -351,14 +355,14 @@
  */
 int
 audit_write_success(short event_code, token_t *tok, au_id_t auid, uid_t euid,
-    gid_t egid, uid_t ruid, gid_t rgid, pid_t pid, au_asid_t sid,
+    gid_t egid, uid_t ruid, gid_t rgid, lwpid_t lwpid, pid_t pid, au_asid_t sid,
     au_tid_t *tid)
 {
 	char *func = "audit_write_success()";
 	token_t *subject = NULL;
 
 	/* Tokenize and save subject. */
-	subject = au_to_subject32(auid, euid, egid, ruid, rgid, pid, sid,
+	subject = au_to_subject32(auid, euid, egid, ruid, rgid, lwpid, pid, sid,
 	    tid);
 	if (subject == NULL) {
 		syslog(LOG_ERR, "%s: au_to_subject32() failed", func);
@@ -395,13 +399,14 @@
  */
 int
 audit_write_failure(short event_code, char *errmsg, int errcode, au_id_t auid,
-    uid_t euid, gid_t egid, uid_t ruid, gid_t rgid, pid_t pid, au_asid_t sid,
-    au_tid_t *tid)
+    uid_t euid, gid_t egid, uid_t ruid, gid_t rgid, lwpid_t lwpid, pid_t pid, 
+    au_asid_t sid, au_tid_t *tid)
 {
 	char *func = "audit_write_failure()";
 	token_t *subject, *errtok;
 
-	subject = au_to_subject32(auid, euid, egid, ruid, rgid, pid, sid, tid);
+	subject = au_to_subject32(auid, euid, egid, ruid, rgid, lwpid, pid, 
+		sid, tid);
 	if (subject == NULL) {
 		syslog(LOG_ERR, "%s: au_to_subject32() failed", func);
 		return (kAUMakeSubjectTokErr);
@@ -452,11 +457,11 @@
  */
 int
 audit_write_failure_na(short event_code, char *errmsg, int errret, uid_t euid,
-    uid_t egid, pid_t pid, au_tid_t *tid)
+    uid_t egid, lwpid_t lwpid, pid_t pid, au_tid_t *tid)
 {
 
 	return (audit_write_failure(event_code, errmsg, errret, -1, euid,
-	    egid, -1, -1, pid, -1, tid));
+	    egid, -1, -1, lwpid, pid, -1, tid));
 }
 
 /* END OF au_write() WRAPPERS */
diff -ur src.orig/sys/bsm/audit_record.h src/sys/bsm/audit_record.h
--- src.orig/sys/bsm/audit_record.h	2012-01-03 05:26:45.000000000 +0200
+++ src/sys/bsm/audit_record.h	2013-01-13 21:13:26.000000000 +0200
@@ -258,17 +258,17 @@
 token_t	*au_to_sock_inet128(struct sockaddr_in6 *so);
 token_t	*au_to_sock_unix(struct sockaddr_un *so);
 token_t	*au_to_subject(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid,
-	    gid_t rgid, pid_t pid, au_asid_t sid, au_tid_t *tid);
+	    gid_t rgid, lwpid_t lwpid, pid_t pid, au_asid_t sid, au_tid_t *tid);
 token_t	*au_to_subject32(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid,
-	    gid_t rgid, pid_t pid, au_asid_t sid, au_tid_t *tid);
+	    gid_t rgid, lwpid_t lwpid, pid_t pid, au_asid_t sid, au_tid_t *tid);
 token_t	*au_to_subject64(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid,
-	    gid_t rgid, pid_t pid, au_asid_t sid, au_tid_t *tid);
+	    gid_t rgid, lwpid_t lwpid, pid_t pid, au_asid_t sid, au_tid_t *tid);
 token_t	*au_to_subject_ex(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid,
-	    gid_t rgid, pid_t pid, au_asid_t sid, au_tid_addr_t *tid);
+	    gid_t rgid, lwpid_t lwpid, pid_t pid, au_asid_t sid, au_tid_addr_t *tid);
 token_t	*au_to_subject32_ex(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid,
-	    gid_t rgid, pid_t pid, au_asid_t sid, au_tid_addr_t *tid);
+	    gid_t rgid, lwpid_t lwpid, pid_t pid, au_asid_t sid, au_tid_addr_t *tid);
 token_t	*au_to_subject64_ex(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid,
-	    gid_t rgid, pid_t pid, au_asid_t sid, au_tid_addr_t *tid);
+	    gid_t rgid, lwpid_t lwpid, pid_t pid, au_asid_t sid, au_tid_addr_t *tid);
 #if defined(_KERNEL) || defined(KERNEL)
 token_t	*au_to_exec_args(char *args, int argc);
 token_t	*au_to_exec_env(char *envs, int envc);
diff -ur src.orig/sys/kern/kern_thr.c src/sys/kern/kern_thr.c
--- src.orig/sys/kern/kern_thr.c	2012-01-03 05:26:16.000000000 +0200
+++ src/sys/kern/kern_thr.c	2013-08-16 04:47:07.000000000 +0300
@@ -318,6 +318,8 @@
 	PROC_LOCK(p);
 	racct_sub(p, RACCT_NTHR, 1);
 
+	AUDIT_ARG_PROCESS(p);
+
 	/*
 	 * Shutting down last thread in the proc.  This will actually
 	 * call exit() in the trampoline when it returns.
diff -ur src.orig/sys/kern/uipc_socket.c src/sys/kern/uipc_socket.c
--- src.orig/sys/kern/uipc_socket.c	2012-01-03 05:26:16.000000000 +0200
+++ src/sys/kern/uipc_socket.c	2013-09-30 04:01:22.000000000 +0300
@@ -138,6 +138,7 @@
 
 #include <net/vnet.h>
 
+#include <security/audit/audit.h>
 #include <security/mac/mac_framework.h>
 
 #include <vm/uma.h>
@@ -2399,8 +2400,29 @@
 	if (valsize > len)
 		sopt->sopt_valsize = valsize = len;
 
+	/* This if block indicates a request coming from userland. */
 	if (sopt->sopt_td != NULL)
-		return (copyin(sopt->sopt_val, buf, valsize));
+	{
+		int ret = copyin(sopt->sopt_val, buf, valsize);
+
+		/* Unfortunately, there's no AUDIT_ARG_*() macro that can be used 
+ 		 * to copy arbitrary buffers in audit records. However, we can use
+ 		 * the following switch-case to catch some common valsize values.
+ 		 */
+		switch(valsize)
+		{
+			case 1:
+				AUDIT_ARG_VALUE(*(int8_t *)buf);
+				break;
+			case 2:
+				AUDIT_ARG_VALUE(*(int16_t *)buf);
+				break;
+			case 4:
+				AUDIT_ARG_VALUE(*(int32_t *)buf);
+				break;
+		}
+		return (ret);
+	}
 
 	bcopy(sopt->sopt_val, buf, valsize);
 	return (0);
diff -ur src.orig/sys/kern/uipc_syscalls.c src/sys/kern/uipc_syscalls.c
--- src.orig/sys/kern/uipc_syscalls.c	2012-01-03 05:26:16.000000000 +0200
+++ src/sys/kern/uipc_syscalls.c	2013-09-30 03:20:29.000000000 +0300
@@ -240,6 +240,7 @@
 	int error;
 
 	AUDIT_ARG_FD(fd);
+	AUDIT_ARG_SOCKADDR(td, sa);
 	error = getsock_cap(td->td_proc->p_fd, fd, CAP_BIND, &fp, NULL);
 	if (error)
 		return (error);
@@ -440,6 +441,9 @@
 	(void) fo_ioctl(nfp, FIOASYNC, &tmp, td->td_ucred, td);
 	sa = 0;
 	error = soaccept(so, &sa);
+	if (sa) 
+		AUDIT_ARG_SOCKADDR(td, sa);
+
 	if (error) {
 		/*
 		 * return a namelen of zero for older code which might
@@ -549,6 +553,7 @@
 	int interrupted = 0;
 
 	AUDIT_ARG_FD(fd);
+	AUDIT_ARG_SOCKADDR(td, sa);
 	error = getsock_cap(td->td_proc->p_fd, fd, CAP_CONNECT, &fp, NULL);
 	if (error)
 		return (error);
@@ -1349,9 +1354,16 @@
 	}
 
 	AUDIT_ARG_FD(s);
+	/* There are no specific AUDIT_ARG_*() macros for setsockopt(), but we
+ 	 * can use AUDIT_ARG_VALUE() and AUDIT_ARG_CMD().
+ 	 */
+	AUDIT_ARG_CMD(((level & 0xffff) << 16) | (name & 0xffff));
 	error = getsock_cap(td->td_proc->p_fd, s, CAP_SETSOCKOPT, &fp, NULL);
 	if (error == 0) {
 		so = fp->f_data;
+        /* To audit the option itself we need to issue a copyin(). Postpone
+         * that until sooptcopyin() is called.
+         */
 		error = sosetopt(so, &sopt);
 		fdrop(fp, td);
 	}
@@ -1428,6 +1440,8 @@
 	}
 
 	AUDIT_ARG_FD(s);
+	/* See kern_setsockopt() above... */
+	AUDIT_ARG_CMD(((level & 0xffff) << 16) | (name & 0xffff));
 	error = getsock_cap(td->td_proc->p_fd, s, CAP_GETSOCKOPT, &fp, NULL);
 	if (error == 0) {
 		so = fp->f_data;
diff -ur src.orig/sys/security/audit/audit.c src/sys/security/audit/audit.c
--- src.orig/sys/security/audit/audit.c	2012-01-03 05:26:57.000000000 +0200
+++ src/sys/security/audit/audit.c	2013-09-29 22:42:05.000000000 +0300
@@ -220,6 +220,11 @@
 	ar->k_ar.ar_magic = AUDIT_RECORD_MAGIC;
 	nanotime(&ar->k_ar.ar_starttime);
 
+	/* I think this is the most appropriate place to initialize k_uthread 
+ 	 * which otherwise remains unused. 
+ 	 */
+	ar->k_uthread = (struct uthread *)td;
+
 	/*
 	 * Export the subject credential.
 	 */
diff -ur src.orig/sys/security/audit/audit.h src/sys/security/audit/audit.h
--- src.orig/sys/security/audit/audit.h	2012-01-03 05:26:57.000000000 +0200
+++ src/sys/security/audit/audit.h	2013-09-22 21:06:11.000000000 +0300
@@ -191,6 +191,11 @@
 		audit_arg_fd((fd));					\
 } while (0)
 
+#define AUDIT_ARG_SOCKADDR(td, sa) do {					\
+	if (AUDITING_TD(curthread))					\
+		audit_arg_sockaddr((td), (sa));				\
+} while(0)
+
 #define	AUDIT_ARG_FILE(p, fp) do {					\
 	if (AUDITING_TD(curthread))					\
 		audit_arg_file((p), (fp));				\
diff -ur src.orig/sys/security/audit/audit_bsm.c src/sys/security/audit/audit_bsm.c
--- src.orig/sys/security/audit/audit_bsm.c	2012-01-03 05:26:57.000000000 +0200
+++ src/sys/security/audit/audit_bsm.c	2013-09-30 03:55:24.000000000 +0300
@@ -41,6 +41,7 @@
 #include <sys/fcntl.h>
 #include <sys/user.h>
 #include <sys/systm.h>
+#include <sys/proc.h>
 
 #include <bsm/audit.h>
 #include <bsm/audit_internal.h>
@@ -486,6 +487,7 @@
 		    ar->ar_subj_egid,	/* eff group id */
 		    ar->ar_subj_ruid,	/* real uid */
 		    ar->ar_subj_rgid,	/* real group id */
+		    ((struct thread *)kar->k_uthread)->td_tid,
 		    ar->ar_subj_pid,	/* process id */
 		    ar->ar_subj_asid,	/* session ID */
 		    &tid);
@@ -496,6 +498,7 @@
 		    ar->ar_subj_egid,
 		    ar->ar_subj_ruid,
 		    ar->ar_subj_rgid,
+		    ((struct thread *)kar->k_uthread)->td_tid,
 		    ar->ar_subj_pid,
 		    ar->ar_subj_asid,
 		    &ar->ar_subj_term_addr);
@@ -507,6 +510,7 @@
 		    ar->ar_subj_egid,
 		    ar->ar_subj_ruid,
 		    ar->ar_subj_rgid,
+		    ((struct thread *)kar->k_uthread)->td_tid,
 		    ar->ar_subj_pid,
 		    ar->ar_subj_asid,
 		    &tid);
@@ -548,7 +552,14 @@
 			kau_write(rec, tok);
 			UPATH1_TOKENS;
 		}
-		/* XXX Need to handle ARG_SADDRINET6 */
+		/* XXX: I think it would be better to modify au_to_sock_inet() 
+ 		 * rather than calling au_to_sock_inet128() directly here. 
+ 		 */
+		if (ARG_IS_VALID(kar, ARG_SADDRINET6)) {
+			tok = au_to_sock_inet128((struct sockaddr_in6 *)
+			    &ar->ar_arg_sockaddr);
+			kau_write(rec, tok);
+		}
 		break;
 
 	case AUE_SOCKET:
@@ -567,6 +578,20 @@
 		break;
 
 	case AUE_SETSOCKOPT:
+		if (ARG_IS_VALID(kar, ARG_FD)) {
+			tok = au_to_arg32(1, "fd", ar->ar_arg_fd);
+			kau_write(rec, tok);
+		}
+		if (ARG_IS_VALID(kar, ARG_CMD)) {
+			tok = au_to_arg32(2, "cmd", ar->ar_arg_cmd);
+			kau_write(rec, tok);
+		}
+		if (ARG_IS_VALID(kar, ARG_VALUE)) {
+			tok = au_to_arg32(3, "value", ar->ar_arg_value);
+			kau_write(rec, tok);
+		}
+		break;
+
 	case AUE_SHUTDOWN:
 		if (ARG_IS_VALID(kar, ARG_FD)) {
 			tok = au_to_arg32(1, "fd", ar->ar_arg_fd);
diff -ur src.orig/sys/security/audit/audit_bsm_token.c src/sys/security/audit/audit_bsm_token.c
--- src.orig/sys/security/audit/audit_bsm_token.c	2012-01-03 05:26:57.000000000 +0200
+++ src/sys/security/audit/audit_bsm_token.c	2013-09-30 04:30:03.000000000 +0300
@@ -1003,7 +1003,11 @@
 	ADD_U_CHAR(dptr, 0);
 	ADD_U_CHAR(dptr, so->sin6_family);
 
-	ADD_U_INT16(dptr, so->sin6_port);
+	/* No, it should be ADD_MEM() instead, otherwise AF_INET6 related 
+	 * tokens, get a port number with reversed endianness.
+	 */
+	/* ADD_U_INT16(dptr, so->sin6_port); */
+	ADD_MEM(dptr, &so->sin6_port, sizeof(uint16_t));
 	ADD_MEM(dptr, &so->sin6_addr, 4 * sizeof(uint32_t));
 
 	return (t);
@@ -1031,12 +1035,12 @@
  */
 token_t *
 au_to_subject32(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid, gid_t rgid,
-    pid_t pid, au_asid_t sid, au_tid_t *tid)
+    lwpid_t lwpid, pid_t pid, au_asid_t sid, au_tid_t *tid)
 {
 	token_t *t;
 	u_char *dptr = NULL;
 
-	GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 9 * sizeof(u_int32_t));
+	GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 10 * sizeof(u_int32_t));
 
 	ADD_U_CHAR(dptr, AUT_SUBJECT32);
 	ADD_U_INT32(dptr, auid);
@@ -1044,6 +1048,7 @@
 	ADD_U_INT32(dptr, egid);
 	ADD_U_INT32(dptr, ruid);
 	ADD_U_INT32(dptr, rgid);
+	ADD_U_INT32(dptr, lwpid);
 	ADD_U_INT32(dptr, pid);
 	ADD_U_INT32(dptr, sid);
 	ADD_U_INT32(dptr, tid->port);
@@ -1054,12 +1059,12 @@
 
 token_t *
 au_to_subject64(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid, gid_t rgid,
-    pid_t pid, au_asid_t sid, au_tid_t *tid)
+    lwpid_t lwpid, pid_t pid, au_asid_t sid, au_tid_t *tid)
 {
 	token_t *t;
 	u_char *dptr = NULL;
 
-	GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 7 * sizeof(u_int32_t) +
+	GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 8 * sizeof(u_int32_t) +
 	    sizeof(u_int64_t) + sizeof(u_int32_t));
 
 	ADD_U_CHAR(dptr, AUT_SUBJECT64);
@@ -1068,6 +1073,7 @@
 	ADD_U_INT32(dptr, egid);
 	ADD_U_INT32(dptr, ruid);
 	ADD_U_INT32(dptr, rgid);
+	ADD_U_INT32(dptr, lwpid);
 	ADD_U_INT32(dptr, pid);
 	ADD_U_INT32(dptr, sid);
 	ADD_U_INT64(dptr, tid->port);
@@ -1078,11 +1084,11 @@
 
 token_t *
 au_to_subject(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid, gid_t rgid,
-    pid_t pid, au_asid_t sid, au_tid_t *tid)
+    lwpid_t lwpid, pid_t pid, au_asid_t sid, au_tid_t *tid)
 {
 
-	return (au_to_subject32(auid, euid, egid, ruid, rgid, pid, sid,
-	    tid));
+	return (au_to_subject32(auid, euid, egid, ruid, rgid, lwpid, pid, 
+	    sid, tid));
 }
 
 /*
@@ -1101,7 +1107,7 @@
  */
 token_t *
 au_to_subject32_ex(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid,
-    gid_t rgid, pid_t pid, au_asid_t sid, au_tid_addr_t *tid)
+    gid_t rgid, lwpid_t lwpid, pid_t pid, au_asid_t sid, au_tid_addr_t *tid)
 {
 	token_t *t;
 	u_char *dptr = NULL;
@@ -1110,10 +1116,10 @@
 	    ("au_to_subject32_ex: type %u", (unsigned int)tid->at_type));
 
 	if (tid->at_type == AU_IPv4)
-		GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 10 *
+		GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 11 *
 		    sizeof(u_int32_t));
 	else
-		GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 13 *
+		GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 14 *
 		    sizeof(u_int32_t));
 
 	ADD_U_CHAR(dptr, AUT_SUBJECT32_EX);
@@ -1122,6 +1128,7 @@
 	ADD_U_INT32(dptr, egid);
 	ADD_U_INT32(dptr, ruid);
 	ADD_U_INT32(dptr, rgid);
+	ADD_U_INT32(dptr, lwpid);
 	ADD_U_INT32(dptr, pid);
 	ADD_U_INT32(dptr, sid);
 	ADD_U_INT32(dptr, tid->at_port);
@@ -1136,7 +1143,7 @@
 
 token_t *
 au_to_subject64_ex(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid,
-    gid_t rgid, pid_t pid, au_asid_t sid, au_tid_addr_t *tid)
+    gid_t rgid, lwpid_t lwpid, pid_t pid, au_asid_t sid, au_tid_addr_t *tid)
 {
 	token_t *t;
 	u_char *dptr = NULL;
@@ -1146,11 +1153,11 @@
 
 	if (tid->at_type == AU_IPv4)
 		GET_TOKEN_AREA(t, dptr, sizeof(u_char) +
-		    7 * sizeof(u_int32_t) + sizeof(u_int64_t) +
+		    8 * sizeof(u_int32_t) + sizeof(u_int64_t) +
 		    2 * sizeof(u_int32_t));
 	else
 		GET_TOKEN_AREA(t, dptr, sizeof(u_char) +
-		    7 * sizeof(u_int32_t) + sizeof(u_int64_t) +
+		    8 * sizeof(u_int32_t) + sizeof(u_int64_t) +
 		    5 * sizeof(u_int32_t));
 
 	ADD_U_CHAR(dptr, AUT_SUBJECT64_EX);
@@ -1159,6 +1166,7 @@
 	ADD_U_INT32(dptr, egid);
 	ADD_U_INT32(dptr, ruid);
 	ADD_U_INT32(dptr, rgid);
+	ADD_U_INT32(dptr, lwpid);
 	ADD_U_INT32(dptr, pid);
 	ADD_U_INT32(dptr, sid);
 	ADD_U_INT64(dptr, tid->at_port);
@@ -1173,11 +1181,11 @@
 
 token_t *
 au_to_subject_ex(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid,
-    gid_t rgid, pid_t pid, au_asid_t sid, au_tid_addr_t *tid)
+    gid_t rgid, lwpid_t lwpid, pid_t pid, au_asid_t sid, au_tid_addr_t *tid)
 {
 
-	return (au_to_subject32_ex(auid, euid, egid, ruid, rgid, pid, sid,
-	    tid));
+	return (au_to_subject32_ex(auid, euid, egid, ruid, rgid, lwpid, pid, 
+	    sid, tid));
 }
 
 #if !defined(_KERNEL) && !defined(KERNEL) && defined(HAVE_AUDIT_SYSCALLS)
diff -ur src.orig/usr.bin/login/login_audit.c src/usr.bin/login/login_audit.c
--- src.orig/usr.bin/login/login_audit.c	2012-01-03 05:23:42.000000000 +0200
+++ src/usr.bin/login/login_audit.c	2013-01-14 00:48:55.000000000 +0200
@@ -35,6 +35,7 @@
 __FBSDID("$FreeBSD: release/9.0.0/usr.bin/login/login_audit.c 191297 2009-04-19 23:34:22Z rwatson $");
 
 #include <sys/types.h>
+#include <sys/thr.h>
 
 #include <bsm/libbsm.h>
 #include <bsm/audit_uevents.h>
@@ -67,8 +68,11 @@
 	uid_t uid = pwd->pw_uid;
 	gid_t gid = pwd->pw_gid;
 	pid_t pid = getpid();
+	long lwpid = -1;
 	int au_cond;
 
+	thr_self(&lwpid);
+
 	/* If we are not auditing, don't cut an audit record; just return. */
  	if (auditon(A_GETCOND, &au_cond, sizeof(au_cond)) < 0) {
 		if (errno == ENOSYS)
@@ -93,8 +97,8 @@
 	if ((aufd = au_open()) == -1)
 		errx(1,"login: Audit Error: au_open() failed");
 
-	if ((tok = au_to_subject32(uid, geteuid(), getegid(), uid, gid, pid,
-	    pid, &tid)) == NULL)
+	if ((tok = au_to_subject32(uid, geteuid(), getegid(), uid, gid, 
+	    (lwpid_t)lwpid, pid, pid, &tid)) == NULL)
 		errx(1, "login: Audit Error: au_to_subject32() failed");
 	au_write(aufd, tok);
 
@@ -119,6 +123,9 @@
 	uid_t uid;
 	gid_t gid;
 	pid_t pid = getpid();
+	long lwpid = -1;
+
+	thr_self(&lwpid);
 
 	/* If we are not auditing, don't cut an audit record; just return. */
  	if (auditon(A_GETCOND, &au_cond, sizeof(au_cond)) < 0) {
@@ -138,14 +145,14 @@
 		 * within a user's session => auid,asid == -1.
 		 */
 		if ((tok = au_to_subject32(-1, geteuid(), getegid(), -1, -1,
-		    pid, -1, &tid)) == NULL)
+		    (lwpid_t)lwpid, pid, -1, &tid)) == NULL)
 			errx(1, "login: Audit Error: au_to_subject32() failed");
 	} else {
 		/* We know the subject -- so use its value instead. */
 		uid = pwd->pw_uid;
 		gid = pwd->pw_gid;
 		if ((tok = au_to_subject32(uid, geteuid(), getegid(), uid,
-		    gid, pid, pid, &tid)) == NULL)
+		    gid, (lwpid_t)lwpid, pid, pid, &tid)) == NULL)
 			errx(1, "login: Audit Error: au_to_subject32() failed");
 	}
 	au_write(aufd, tok);
@@ -175,8 +182,11 @@
 	uid_t uid = pwd->pw_uid;
 	gid_t gid = pwd->pw_gid;
 	pid_t pid = getpid();
+	long lwpid = -1;
 	int au_cond;
 
+	thr_self(&lwpid);
+
 	/* If we are not auditing, don't cut an audit record; just return. */
  	if (auditon(A_GETCOND, &au_cond, sizeof(int)) < 0) {
 		if (errno == ENOSYS)
@@ -190,8 +200,8 @@
 		errx(1, "login: Audit Error: au_open() failed");
 
 	/* The subject that is created (euid, egid of the current process). */
-	if ((tok = au_to_subject32(uid, geteuid(), getegid(), uid, gid, pid,
-	    pid, &tid)) == NULL)
+	if ((tok = au_to_subject32(uid, geteuid(), getegid(), uid, gid, 
+	    (lwpid_t)lwpid, pid, pid, &tid)) == NULL)
 		errx(1, "login: Audit Error: au_to_subject32() failed");
 	au_write(aufd, tok);
 


>Release-Note:
>Audit-Trail:
>Unformatted:



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