Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 23 Apr 2026 20:29:03 +0000
From:      Kyle Evans <kevans@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Cc:        Christian S.J. Peron <csjp@FreeBSD.org>
Subject:   git: a46205a100b3 - main - Fix memory corruption bugs in BSM record parsing
Message-ID:  <69ea810f.236d5.50248d4c@gitrepo.freebsd.org>

index | next in thread | raw e-mail

The branch main has been updated by kevans:

URL: https://cgit.FreeBSD.org/src/commit/?id=a46205a100b3201a60aaca26e4ac8097e1e136a7

commit a46205a100b3201a60aaca26e4ac8097e1e136a7
Author:     Christian S.J. Peron <csjp@FreeBSD.org>
AuthorDate: 2026-04-23 20:26:50 +0000
Commit:     Kyle Evans <kevans@FreeBSD.org>
CommitDate: 2026-04-23 20:26:50 +0000

    Fix memory corruption bugs in BSM record parsing
    
    fetch_newgroups_tok(3): clamp group count to AUDIT_MAX_GROUPS before the
    loop to prevent a stack buffer overflow when a crafted record specifies
    more than 16 groups.
    
    fetch_execarg_tok(3), fetch_execenv_tok(3): add a bounds check at the
    top of the string-walking loop to prevent an out-of-bounds read when the
    previous string's nul byte is the last byte of the record buffer.
    
    fetch_sock_unix_tok(3): clamp the memchr search length to the number of
    bytes remaining in the buffer to prevent an out-of-bounds read on short
    tokens. Also clamp slen to sizeof(path) to prevent a one-byte overflow
    when no nul byte is found within the path data.
    
    fetch_socket_tok: fix copy-paste error where the remote address was
    written into l_addr instead of r_addr.
    Previously reported by: @haginara
    
    Define AU_UNIX_PATH_MAX as 108 (the largest sun_path across all
    supported platforms) and use it in au_socketunix_t instead of the
    hardcoded 104.
    
    Update fetch_sock_unix_tok to derive its search bound from
    sizeof(tok->tt.sockunix.path) so cross-platform records from Solaris and
    Linux with paths up to 108 bytes parse correctly without truncation.
    
    REF: https://github.com/openbsm/openbsm/pull/87
    
    Reviewed by:    kevans, markj
    Differential Revision:  https://reviews.freebsd.org/D56510
---
 contrib/openbsm/bsm/libbsm.h       | 10 ++++++--
 contrib/openbsm/libbsm/bsm_io.c    | 50 +++++++++++++++++++++++++++++++++-----
 contrib/openbsm/libbsm/bsm_token.c |  2 +-
 3 files changed, 53 insertions(+), 9 deletions(-)

diff --git a/contrib/openbsm/bsm/libbsm.h b/contrib/openbsm/bsm/libbsm.h
index 2614a84f01e2..f0ea3943254d 100644
--- a/contrib/openbsm/bsm/libbsm.h
+++ b/contrib/openbsm/bsm/libbsm.h
@@ -585,13 +585,19 @@ typedef struct {
 	u_int32_t	addr;
 } au_socketinet32_t;
 
+/*
+ * Largest sun_path across all supported platforms (Linux and Solaris use 108,
+ * macOS and FreeBSD use 104).
+ */
+#define	AU_UNIX_PATH_MAX	108
+
 /*
  * socket family           2 bytes
- * path                    104 bytes
+ * path                    up to AU_UNIX_PATH_MAX bytes (NUL terminated)
  */
 typedef struct {
 	u_int16_t	family;
-	char		path[104];
+	char		path[AU_UNIX_PATH_MAX];
 } au_socketunix_t;
 
 /*
diff --git a/contrib/openbsm/libbsm/bsm_io.c b/contrib/openbsm/libbsm/bsm_io.c
index f0b3e4a1a2df..323f4ed337f8 100644
--- a/contrib/openbsm/libbsm/bsm_io.c
+++ b/contrib/openbsm/libbsm/bsm_io.c
@@ -1867,6 +1867,15 @@ fetch_execarg_tok(tokenstr_t *tok, u_char *buf, int len)
 		return (-1);
 
 	for (i = 0; i < tok->tt.execarg.count; i++) {
+		/*
+		 * Make sure that tok->len has not reached the end of the
+		 * buffer. If the previous string's nul byte was the last byte
+		 * in the buffer, the nul accounting below will have set
+		 * tok->len == len, leaving no room for another string.
+		 */
+		if (tok->len >= (u_int32_t)len) {
+			return (-1);
+		}
 		bptr = buf + tok->len;
 		if (i < AUDIT_MAX_ARGS)
 			tok->tt.execarg.text[i] = (char*)bptr;
@@ -1925,6 +1934,15 @@ fetch_execenv_tok(tokenstr_t *tok, u_char *buf, int len)
 		return (-1);
 
 	for (i = 0; i < tok->tt.execenv.count; i++) {
+		/*
+		 * Make sure that tok->len has not reached the end of the
+		 * buffer. If the previous string's nul byte was the last byte
+		 * in the buffer, the nul accounting below will have set
+		 * tok->len == len, leaving no room for another string.
+		 */
+		if (tok->len >= (u_int32_t)len) {
+			return (-1);
+		}
 		bptr = buf + tok->len;
 		if (i < AUDIT_MAX_ENV)
 			tok->tt.execenv.text[i] = (char*)bptr;
@@ -2037,6 +2055,17 @@ fetch_newgroups_tok(tokenstr_t *tok, u_char *buf, int len)
 	if (err)
 		return (-1);
 
+	/*
+	 * grps.list[] is statically sized and set to AUDIT_MAX_GROUPS. If the
+	 * group count specified in the record is greater than this value just
+	 * clamp/truncate it. Silently truncating a malformed record changes
+	 * what was recorded and could mask tampering. However, a precedent
+	 * has been set in fetch_execarg_tok and fetch_execenv_tok which
+	 * truncate the count under similar circumstances.
+	 */
+	if (tok->tt.grps.no > AUDIT_MAX_GROUPS) {
+		tok->tt.grps.no = AUDIT_MAX_GROUPS;
+	}
 	for (i = 0; i<tok->tt.grps.no; i++) {
 		READ_TOKEN_U_INT32(buf, len, tok->tt.grps.list[i], tok->len,
 		    err);
@@ -3197,27 +3226,36 @@ print_sock_inet128_tok(FILE *fp, tokenstr_t *tok, char *del, int oflags)
 
 /*
  * socket family           2 bytes
- * path                    (up to) 104 bytes + NULL (NULL terminated string).
+ * path                    (up to) AU_UNIX_PATH_MAX bytes (NUL terminated)
  */
 static int
 fetch_sock_unix_tok(tokenstr_t *tok, u_char *buf, int len)
 {
+	size_t remaining, search, pathmax;
 	int err = 0;
 	u_char *p;
 	int slen;
 
-
 	READ_TOKEN_U_INT16(buf, len, tok->tt.sockunix.family, tok->len, err);
 	if (err)
 		return (-1);
 
-	/* slen = strnlen((buf + tok->len), 104) + 1; */
-	p = (u_char *)memchr((const void *)(buf + tok->len), '\0', 104);
-	slen = (p ? (int)(p - (buf + tok->len))  : 104) + 1;
+	/*
+	 * Clamp the search to the bytes remaining in the token and the path
+	 * storage size.  Using sizeof(tok->tt.sockunix.path) rather than a
+	 * literal keeps the bound in sync with au_socketunix_t automatically.
+	 */
+	pathmax = sizeof(tok->tt.sockunix.path);
+	remaining = (size_t)(len - (int)tok->len);
+	search = remaining < pathmax ? remaining : pathmax;
+	p = (u_char *)memchr((const void *)(buf + tok->len), '\0', search);
+	slen = (p ? (int)(p - (buf + tok->len)) + 1 : (int)search);
 
 	READ_TOKEN_BYTES(buf, len, tok->tt.sockunix.path, slen, tok->len, err);
 	if (err)
 		return (-1);
+	/* guarantee NUL termination when no NUL was found in the token data */
+	tok->tt.sockunix.path[pathmax - 1] = '\0';
 
 	return (0);
 }
@@ -3278,7 +3316,7 @@ fetch_socket_tok(tokenstr_t *tok, u_char *buf, int len)
 	if (err)
 		return (-1);
 
-	READ_TOKEN_BYTES(buf, len, &tok->tt.socket.l_addr,
+	READ_TOKEN_BYTES(buf, len, &tok->tt.socket.r_addr,
 	    sizeof(tok->tt.socket.r_addr), tok->len, err);
 	if (err)
 		return (-1);
diff --git a/contrib/openbsm/libbsm/bsm_token.c b/contrib/openbsm/libbsm/bsm_token.c
index 682836f43870..8f55945cb33b 100644
--- a/contrib/openbsm/libbsm/bsm_token.c
+++ b/contrib/openbsm/libbsm/bsm_token.c
@@ -1051,7 +1051,7 @@ au_to_socket_ex(u_short so_domain, u_short so_type,
 /*
  * token ID                1 byte
  * socket family           2 bytes
- * path                    (up to) 104 bytes + NULL  (NULL terminated string)
+ * path                    (up to) AU_UNIX_PATH_MAX bytes (NUL terminated)
  */
 token_t *
 au_to_sock_unix(struct sockaddr_un *so)


home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?69ea810f.236d5.50248d4c>