Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F172265660
D59751.id186933.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
27 KB
Referenced Files
None
Subscribers
None
D59751.id186933.diff
View Options
diff --git a/lib/libbsdconf/bsdconf.h b/lib/libbsdconf/bsdconf.h
--- a/lib/libbsdconf/bsdconf.h
+++ b/lib/libbsdconf/bsdconf.h
@@ -34,10 +34,16 @@
/*
* Library version info
*/
-#define BSDCONF_VERSION "1.1.1 2026-09-16"
-#define BSDCONF_VERSION_MAJOR 1
-#define BSDCONF_VERSION_MINOR 1
-#define BSDCONF_VERSION_PATCH 1
+#define BSDCONF_VERSION "2.0.0 2026-09-16"
+#define BSDCONF_VERSION_MAJOR 2
+#define BSDCONF_VERSION_MINOR 0
+#define BSDCONF_VERSION_PATCH 0
+
+/*
+ * Ceiling on bytes read from a configuration file or stream. Override with
+ * the BSDCONF_MAX_BYTES environment variable (an unsigned decimal count).
+ */
+#define BSDCONF_MAX_BYTES_DEFAULT (64U * 1024U * 1024U)
/*
* Union for storing various types of data in a single common container.
diff --git a/lib/libbsdconf/bsdconf.3 b/lib/libbsdconf/bsdconf.3
--- a/lib/libbsdconf/bsdconf.3
+++ b/lib/libbsdconf/bsdconf.3
@@ -3,7 +3,7 @@
.\"
.\" SPDX-License-Identifier: BSD-2-Clause
.\"
-.Dd August 2, 2026
+.Dd September 16, 2026
.Dt BSDCONF 3
.Os
.Sh NAME
@@ -261,15 +261,23 @@
.Fa fd ,
which remains open on return
.Pq the caller retains ownership .
+The descriptor is read into a bounded in-memory buffer and then scanned
+as an array of characters
+.Pq the same tokenizer used by Xr bsdconf_put 3 .
+The buffer is capped at
+.Dv BSDCONF_MAX_BYTES_DEFAULT
+bytes
+.Pq 67108864; 64 MiB ;
+.Ev BSDCONF_MAX_BYTES
+overrides that ceiling
+.Pq see Sx ENVIRONMENT .
+Input larger than the cap fails with
+.Er EFBIG .
+The descriptor need not be seekable;
+a pipe or socket is read to EOF subject to the same cap.
This allows the caller to constrain the process
.Pq for example with Xr capsicum 4
before parsing begins.
-The scanner requires a seekable descriptor;
-input that cannot seek
-.Pq a pipe or socket, standard input included
-is detected up front and transparently spooled through
-.Fn bsdconf_spool ,
-at the cost of one transient copy of the data.
.Pp
.Fn bsdconf_spool
copies the remaining contents of
@@ -280,13 +288,8 @@
which the caller must
.Xr close 2
.Pq the backing storage is reclaimed then .
-It is exported for callers that must adapt non-seekable input themselves
-before revoking their own ability to create files,
-as
-.Xr sysconf 8
-does before entering its
-.Xr capsicum 4
-sandbox.
+It is exported for callers that need a seekable snapshot of a pipe or
+socket.
.Pp
.Fn bsdconf_get_option
traverses the options-array and returns the option that matches via
@@ -311,6 +314,11 @@
is returned and the global variable
.Va errno
is set to indicate the error.
+Input that exceeds the
+.Ev BSDCONF_MAX_BYTES
+cap fails with
+.Er EFBIG .
+.Pp
.Fn bsdconf_spool
returns a new seekable file descriptor on success;
otherwise -1 with
@@ -321,6 +329,20 @@
or
.Dv NULL
when none matches.
+.Sh ENVIRONMENT
+.Bl -tag -width "BSDCONF_MAX_BYTES"
+.It Ev BSDCONF_MAX_BYTES
+Maximum number of bytes
+.Fn bsdconf_fparse
+and
+.Fn bsdconf_put
+will read from a configuration file or stream.
+The default is
+.Dv BSDCONF_MAX_BYTES_DEFAULT
+.Pq 67108864; 64 MiB .
+A larger value is an opt-in pain threshold for unusual files.
+Unset, empty, zero, or unparseable values restore the default.
+.El
.Sh EXAMPLES
Read two known directives from a
.Ql name=value
@@ -417,16 +439,20 @@
section of
.Xr bsdconf_put 3 .
.Sh SECURITY CONSIDERATIONS
-Parsing allocates buffers sized by the longest directive and value
-encountered rather than by untrusted length fields,
-and
.Fn bsdconf_fparse
-accepts an already-open descriptor precisely so that a caller may
+reads the descriptor into a bounded in-memory buffer rather than
+trusting untrusted length fields,
+and accepts an already-open descriptor precisely so that a caller may
sandbox itself
.Pq for example with Xr capsicum 4
before touching untrusted input,
as
.Xr sysconf 8
does for its read-only operations.
+The
+.Ev BSDCONF_MAX_BYTES
+cap
+.Pq see Sx ENVIRONMENT
+is the bound on that buffer.
Write-path hardening is documented in
.Xr bsdconf_put 3 .
diff --git a/lib/libbsdconf/bsdconf.c b/lib/libbsdconf/bsdconf.c
--- a/lib/libbsdconf/bsdconf.c
+++ b/lib/libbsdconf/bsdconf.c
@@ -5,7 +5,6 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
-#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <fnmatch.h>
@@ -66,10 +65,9 @@
* Copy the remaining contents of the open file descriptor `fd' to an
* unlinked temporary file and return a seekable descriptor referencing it
* (which the caller must close(2); the backing storage is reclaimed then).
- * This adapts input that cannot seek -- a pipe or socket, standard input
- * included -- for the scanner in bsdconf_fparse() below, which seeks
- * liberally. Returns the new descriptor on success; otherwise returns -1
- * and errno should be consulted.
+ * Exported for callers that need a seekable snapshot of a pipe or socket.
+ * Returns the new descriptor on success; otherwise returns -1 and errno
+ * should be consulted.
*/
int
bsdconf_spool(int fd)
@@ -113,109 +111,6 @@
return (-1);
}
-/*
- * Read one byte into `*p', restarting on EINTR. Returns 1 on success, 0 on
- * EOF, or -1 on error (with errno set). Callers must treat a negative return
- * as failure: a loop conditioned only on `r != 0' spins forever on error
- * because read(2) returns -1, and a length counter in such a loop can grow
- * without bound (see the directive scan in bsdconf_fparse() below).
- */
-static ssize_t
-bsdconf_read1(int fd, char *p)
-{
- ssize_t r;
-
- do {
- r = read(fd, p, 1);
- } while (r < 0 && errno == EINTR);
- return (r);
-}
-
-/*
- * Read exactly `n' bytes into `buf', restarting on EINTR. Returns 0 on
- * success, or -1 on error / premature EOF (with errno set; EIO for a short
- * read after the caller measured a length on a seekable descriptor).
- */
-static int
-bsdconf_readn(int fd, void *buf, size_t n)
-{
- char *p = buf;
- size_t off = 0;
- ssize_t r;
-
- while (off < n) {
- r = read(fd, p + off, n - off);
- if (r < 0) {
- if (errno == EINTR)
- continue;
- return (-1);
- }
- if (r == 0) {
- errno = EIO;
- return (-1);
- }
- off += (size_t)r;
- }
- return (0);
-}
-
-/*
- * Advance past horizontal whitespace (spaces and tabs, not newline).
- * Updates `*r' and the byte in `*p'. Returns 0 on success, or -1 on
- * read error (errno set).
- */
-static int
-bsdconf_skip_hspace(int fd, char *p, ssize_t *r)
-{
-
- while (*r > 0 && isspace((unsigned char)*p) && *p != '\n') {
- *r = bsdconf_read1(fd, p);
- if (*r < 0)
- return (-1);
- }
- return (0);
-}
-
-/*
- * Truncate trailing whitespace from a NUL-terminated string whose end
- * (the NUL) is at `end'. Returns a pointer to the last remaining
- * character, or to `value' when the string is empty.
- */
-static char *
-bsdconf_rtrim_ws(char *value, char *end)
-{
- char *t = end;
-
- while (t > value && isspace((unsigned char)*--t))
- *t = '\0';
- return (t);
-}
-
-/*
- * Drop a trailing inline `#' or unescaped `;' that rode along with the
- * value (historic figpar behavior), then trim again. `ecomment' is set
- * when the end-key scan stopped on an unquoted `#'.
- */
-static char *
-bsdconf_trim_value_key(char *value, char *t, bool ecomment, bool bsemicolon)
-{
- uint32_t x;
-
- if (ecomment && t > value && *t == '#') {
- *t = '\0';
- return (bsdconf_rtrim_ws(value, t));
- }
- if (bsemicolon && t > value && *t == ';') {
- for (x = 0; t - x > value && *(t - x - 1) == '\\'; x++)
- ;
- if ((x & 1) == 0) {
- *t = '\0';
- return (bsdconf_rtrim_ws(value, t));
- }
- }
- return (t);
-}
-
/*
* Invoke the unknown-directive call-back with a stack-local option that
* carries the statement's assignment operator (there is no matched
@@ -233,126 +128,6 @@
return (unknown(&unk, dline, directive, value));
}
-/*
- * Scan from the current byte in `*p' to the end of the value. Handles
- * quotes, escaped end-keys, inline comments, and semicolon terminators.
- * On return, `*p' holds the terminating key (or is at EOF), and `*r',
- * `*line', `*comment', and `*ecomment' are updated. Returns 0 on
- * success, or -1 on seek/read error (errno set).
- */
-static int
-bsdconf_scan_value_end(int fd, char *p, ssize_t *r, uint32_t *line,
- uint8_t *comment, uint8_t *ecomment, bool bsemicolon)
-{
- uint8_t end = 0;
- uint8_t quote = 0;
- uint32_t n;
- off_t charpos;
-
- *ecomment = 0;
- while (*r > 0 && end == 0) {
- /* Advance to the next character if we know we can */
- if (*p != '\"' && *p != '#' && *p != '\n' &&
- (!bsemicolon || *p != ';')) {
- *r = bsdconf_read1(fd, p);
- if (*r < 0)
- return (-1);
- continue;
- }
-
- /*
- * If we get this far, we've hit an end-key
- */
-
- /* Get the current offset */
- if ((charpos = lseek(fd, 0, SEEK_CUR)) == -1)
- return (-1);
- charpos--;
-
- /*
- * Go back so we can read the character before the key to
- * check if the character is escaped (which means we should
- * continue).
- */
- if (lseek(fd, -2, SEEK_CUR) == -1)
- return (-1);
- *r = bsdconf_read1(fd, p);
- if (*r < 0)
- return (-1);
-
- /*
- * Count how many backslashes there are (an odd number means
- * the key is escaped, even means otherwise).
- */
- for (n = 1; *r > 0 && *p == '\\'; n++) {
- /* Move back another offset to read */
- if (lseek(fd, -2, SEEK_CUR) == -1)
- return (-1);
- *r = bsdconf_read1(fd, p);
- if (*r < 0)
- return (-1);
- }
-
- /* Move offset back to the key and read it */
- if (lseek(fd, charpos, SEEK_SET) == -1)
- return (-1);
- *r = bsdconf_read1(fd, p);
- if (*r < 0)
- return (-1);
-
- /*
- * If an even number of backslashes was counted meaning key
- * is not escaped, we should evaluate what to do.
- */
- if ((n & 1) == 1) {
- switch (*p) {
- case '\"':
- /*
- * Flag current sequence of characters to
- * follow as being quoted (hashes are not
- * considered comments).
- */
- quote = !quote;
- break;
- case '#':
- /*
- * If we aren't in a quoted series, we just
- * hit an inline comment and have found the
- * end of the value. Flag the remainder of
- * the line as a comment so it is not
- * mistaken for a new directive.
- */
- if (!quote) {
- *ecomment = *comment = 1;
- end = 1;
- }
- break;
- case '\n':
- /*
- * Newline characters must always be escaped,
- * whether inside a quoted series or not,
- * otherwise they terminate the value.
- */
- (*line)++;
- end = 1;
- /* FALLTHROUGH */
- case ';':
- if (!quote && bsemicolon)
- end = 1;
- break;
- }
- } else if (*p == '\n')
- /* Escaped newline character. increment */
- (*line)++;
-
- /* Advance to the next character */
- *r = bsdconf_read1(fd, p);
- if (*r < 0)
- return (-1);
- }
- return (0);
-}
-
/*
* Parse the configuration data on the open file descriptor `fd' and execute
* the `parse' call-back functions for any directives defined by the array of
@@ -361,12 +136,13 @@
* For unknown directives that are encountered, you can optionally pass a
* call-back function for the third argument to be called for unknowns.
*
- * The scanner requires a seekable descriptor; input that cannot seek (a
- * pipe or socket, standard input included) is detected up front and spooled
- * through bsdconf_spool() above, parsed from the temporary, and costs one
- * transient copy of the data. The descriptor is left positioned at
- * end-of-file (non-seekable input is left drained) and remains open (the
- * caller retains ownership).
+ * The descriptor is read into a bounded in-memory buffer (see
+ * bsdconf_slurp()) and then scanned as an array of characters with
+ * bsdconf_scan(), the same tokenizer used by bsdconf_put(). The descriptor
+ * need not be seekable; a pipe or socket is read to EOF subject to the
+ * BSDCONF_MAX_BYTES cap. The descriptor is left positioned at end-of-file
+ * (non-seekable input is left drained) and remains open (the caller retains
+ * ownership).
*
* Returns zero on success; otherwise returns -1 (or the non-zero result of a
* call-back) and errno should be consulted.
@@ -379,30 +155,26 @@
bool bequals;
bool bsemicolon;
bool case_sensitive;
+ bool found;
bool operator_equals;
bool require_equals;
bool strict_equals;
- uint8_t comment = 0;
- uint8_t ecomment;
- uint8_t found;
- uint8_t have_equals = 0;
- char p[2];
+ char *buf = NULL;
char *directive = NULL;
char *t;
char *value = NULL;
enum bsdconf_op op;
int error;
int rv = 0;
- int spoolfd = -1;
- ssize_t r = 1;
- uint32_t dline;
- uint32_t dsize = 0;
+ int sverrno;
+ size_t buflen = 0;
+ size_t dsize = 0;
+ size_t i = 0;
+ size_t n;
+ size_t vsize = 0;
+ struct bsdconf_stmt st;
uint32_t line = 1;
- uint32_t n;
- uint32_t vsize = 0;
- uint32_t x;
- off_t charpos;
- off_t curpos;
+ unsigned int x;
/* Sanity check: if no options and no unknown function, return */
if (options == NULL && unknown == NULL) {
@@ -410,14 +182,8 @@
return (-1);
}
- /* Spool input that cannot seek (see bsdconf_spool() above) */
- if (lseek(fd, 0, SEEK_CUR) == -1) {
- if (errno != ESPIPE)
- return (-1);
- if ((spoolfd = bsdconf_spool(fd)) == -1)
- return (-1);
- fd = spoolfd;
- }
+ if ((buf = bsdconf_slurp(fd, &buflen)) == NULL)
+ return (-1);
/* Processing options */
bequals = processing_options & BSDCONF_BREAK_ON_EQUALS;
@@ -427,185 +193,39 @@
require_equals = processing_options & BSDCONF_REQUIRE_EQUALS;
strict_equals = processing_options & BSDCONF_STRICT_EQUALS;
- /* Read the file until EOF */
- while (r > 0) {
- r = bsdconf_read1(fd, p);
- if (r < 0)
- goto fail;
-
- /* Skip to the beginning of a directive */
- while (r > 0 && (isspace((unsigned char)*p) || *p == '#' ||
- comment || (bsemicolon && *p == ';'))) {
- if (*p == '#')
- comment = 1;
- else if (*p == '\n') {
- comment = 0;
- line++;
- }
- r = bsdconf_read1(fd, p);
- if (r < 0)
- goto fail;
- }
- /* Test for EOF; if EOF then no directive was found */
- if (r == 0)
- goto cleanup;
-
- /* Record the line number the directive appears on */
- dline = line;
-
- /* Get the current offset */
- if ((curpos = lseek(fd, 0, SEEK_CUR)) == -1)
- goto fail;
- curpos--;
-
- /* Find the length of the directive */
- for (n = 0; r > 0; n++) {
- if (isspace((unsigned char)*p))
- break;
- if (bequals && *p == '=') {
- have_equals = 1;
- break;
- }
- if (bsemicolon && *p == ';')
- break;
- r = bsdconf_read1(fd, p);
- if (r < 0)
- goto fail;
- }
-
- /* Test for EOF, if EOF then no directive was found */
- if (n == 0 && r == 0)
- goto cleanup;
-
- /* Go back to the beginning of the directive */
- if (lseek(fd, curpos, SEEK_SET) == -1)
- goto fail;
-
- /*
- * Allocate and read the directive into memory. The buffer
- * must be grown on the first pass (directive == NULL) even
- * when the name is empty (a line beginning with `='), lest
- * the string terminator below store through a NULL pointer.
- */
+ while (bsdconf_scan(buf, buflen, &i, &line, bequals, bsemicolon,
+ strict_equals, operator_equals, &st)) {
+ n = st.dir_end - st.dir_start;
if (directive == NULL || n > dsize) {
if ((t = realloc(directive, n + 1)) == NULL)
goto fail;
directive = t;
dsize = n;
}
- if (bsdconf_readn(fd, directive, n) != 0)
- goto fail;
-
- /* Advance beyond the equals sign if appropriate/desired */
- if (bequals && *p == '=') {
- if (lseek(fd, 1, SEEK_CUR) != -1) {
- r = bsdconf_read1(fd, p);
- if (r < 0)
- goto fail;
- }
- if (strict_equals && isspace((unsigned char)*p))
- *p = '\n';
- }
-
- /* Terminate the string */
+ memcpy(directive, buf + st.dir_start, n);
directive[n] = '\0';
- /*
- * Split a make(1)-style operator (`+=' `?=' `:=' `!=') off
- * the tail of the directive if requested. The operator
- * character rode along with the directive because only the
- * `=' terminates the directive scan (above).
- */
- op = have_equals ? BSDCONF_OP_ASSIGN : BSDCONF_OP_DEFAULT;
- if (operator_equals && have_equals && n > 1) {
- switch (directive[n - 1]) {
- case '+': op = BSDCONF_OP_APPEND; break;
- case '?': op = BSDCONF_OP_COND; break;
- case ':': op = BSDCONF_OP_EXPAND; break;
- case '!': op = BSDCONF_OP_SHELL; break;
- }
- if (op != BSDCONF_OP_ASSIGN)
- directive[--n] = '\0';
- }
-
- /* Convert directive to lower case before comparison */
+ op = st.op;
if (!case_sensitive)
bsdconf_strtolower(directive);
- /* Move to what may be the start of the value */
- if (!(bsemicolon && *p == ';') &&
- !(strict_equals && *p == '=')) {
- if (bsdconf_skip_hspace(fd, p, &r) != 0)
- goto fail;
- }
-
- /* An equals sign may have stopped us, should we eat it? */
- if (r > 0 && bequals && *p == '=' && !strict_equals) {
- have_equals = 1;
- r = bsdconf_read1(fd, p);
- if (r < 0)
- goto fail;
- if (bsdconf_skip_hspace(fd, p, &r) != 0)
- goto fail;
- }
-
- /* If no value, allocate a dummy value and jump to action */
- if (r == 0 || *p == '\n' || *p == '#' ||
- (bsemicolon && *p == ';')) {
- /* Count the consumed terminator if a newline */
- if (r > 0 && *p == '\n')
- line++;
- /* Flag a trailing comment so it is skipped */
- if (r > 0 && *p == '#')
- comment = 1;
- /* Initialize the value if not already done */
+ if (!st.have_value) {
if (value == NULL && (value = malloc(1)) == NULL)
goto fail;
value[0] = '\0';
goto call_function;
}
- /* Get the current offset */
- if ((curpos = lseek(fd, 0, SEEK_CUR)) == -1)
- goto fail;
- curpos--;
-
- /* Find the end of the value */
- if (bsdconf_scan_value_end(fd, p, &r, &line, &comment,
- &ecomment, bsemicolon) != 0)
- goto fail;
-
- /* Get the current offset */
- if ((charpos = lseek(fd, 0, SEEK_CUR)) == -1)
- goto fail;
-
- /* Get the length of the value */
- n = (uint32_t)(charpos - curpos);
- if (r > 0) /* more to read, but don't read ending key */
- n--;
-
- /* Move offset back to the beginning of the value */
- if (lseek(fd, curpos, SEEK_SET) == -1)
- goto fail;
-
- /* Allocate and read the value into memory */
- if (n > vsize) {
+ n = st.val_end - st.val_start;
+ if (value == NULL || n > vsize) {
if ((t = realloc(value, n + 1)) == NULL)
goto fail;
value = t;
vsize = n;
}
- if (bsdconf_readn(fd, value, n) != 0)
- goto fail;
-
- /* Terminate the string */
+ memcpy(value, buf + st.val_start, n);
value[n] = '\0';
- /* Cut trailing whitespace and a trailing `#' / `;' key */
- t = bsdconf_rtrim_ws(value, value + n);
- t = bsdconf_trim_value_key(value, t, ecomment != 0,
- bsemicolon);
-
/* Escape the escaped quotes (see bsdconf_string.c) */
x = bsdconf_strcount(value, "\\\"");
if (x != 0 && (n + x) > vsize) {
@@ -626,12 +246,12 @@
call_function:
/* Abort if we're seeking only assignments */
- if (require_equals && !have_equals) {
+ if (require_equals && !st.have_equals) {
errno = EINVAL;
goto fail;
}
- found = have_equals = 0; /* reset */
+ found = 0;
/*
* Report the statement's assignment operator through a
@@ -639,7 +259,7 @@
* (there is no matched options[] slot to hang it on).
*/
if (options == NULL && unknown != NULL) {
- error = bsdconf_call_unknown(unknown, op, dline,
+ error = bsdconf_call_unknown(unknown, op, st.line,
directive, value);
if (error != 0) {
rv = error;
@@ -658,7 +278,7 @@
options[n].op = op;
if (options[n].parse != NULL) {
error = options[n].parse(&options[n],
- dline, directive, value);
+ st.line, directive, value);
if (error != 0) {
rv = error;
goto cleanup;
@@ -675,7 +295,7 @@
* No match was found for the value we read from the
* file; call function designated for unknown values.
*/
- error = bsdconf_call_unknown(unknown, op, dline,
+ error = bsdconf_call_unknown(unknown, op, st.line,
directive, value);
if (error != 0) {
rv = error;
@@ -690,12 +310,11 @@
rv = -1;
cleanup:
- x = errno; /* preserve errno across free(3) and close(2) */
- if (spoolfd != -1)
- close(spoolfd);
+ sverrno = errno; /* preserve errno across free(3) */
+ free(buf);
free(directive);
free(value);
- errno = x;
+ errno = sverrno;
return (rv);
}
diff --git a/lib/libbsdconf/bsdconf_internal.h b/lib/libbsdconf/bsdconf_internal.h
--- a/lib/libbsdconf/bsdconf_internal.h
+++ b/lib/libbsdconf/bsdconf_internal.h
@@ -54,6 +54,8 @@
const char *bsdconf_op_token(enum bsdconf_op _op);
int bsdconf_writeall(int _fd, const void *_data, size_t _len);
char *bsdconf_readfile(int _fd, size_t _size, size_t *_lenp);
+size_t bsdconf_max_bytes(void);
+char *bsdconf_slurp(int _fd, size_t *_lenp);
int bsdconf_emit(int _fd, const void *_data, size_t _len,
int *_last);
int bsdconf_ensure_tmp(int *_tmpfdp, char *_tpath, size_t _tpathsz,
diff --git a/lib/libbsdconf/bsdconf_put.3 b/lib/libbsdconf/bsdconf_put.3
--- a/lib/libbsdconf/bsdconf_put.3
+++ b/lib/libbsdconf/bsdconf_put.3
@@ -3,7 +3,7 @@
.\"
.\" SPDX-License-Identifier: BSD-2-Clause
.\"
-.Dd August 2, 2026
+.Dd September 16, 2026
.Dt BSDCONF_PUT 3
.Os
.Sh NAME
@@ -97,6 +97,10 @@
.Va mtime
is not bumped,
and hard links are not severed.
+The original is read in full, subject to the
+.Ev BSDCONF_MAX_BYTES
+cap documented in
+.Xr bsdconf 3 .
Otherwise the file is replaced atomically:
output is streamed to a temporary file created with
.Xr mkstemp 3
diff --git a/lib/libbsdconf/bsdconf_put.c b/lib/libbsdconf/bsdconf_put.c
--- a/lib/libbsdconf/bsdconf_put.c
+++ b/lib/libbsdconf/bsdconf_put.c
@@ -151,7 +151,7 @@
}
/* Slurp the original into memory */
- if ((buf = bsdconf_readfile(fd, (size_t)sb.st_size, &buflen)) == NULL)
+ if ((buf = bsdconf_slurp(fd, &buflen)) == NULL)
goto cleanup;
/*
diff --git a/lib/libbsdconf/bsdconf_stmt.c b/lib/libbsdconf/bsdconf_stmt.c
--- a/lib/libbsdconf/bsdconf_stmt.c
+++ b/lib/libbsdconf/bsdconf_stmt.c
@@ -15,6 +15,7 @@
#include <ctype.h>
#include <errno.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -109,6 +110,131 @@
return (buf);
}
+/*
+ * Ceiling on a slurp of configuration data. Unset, empty, zero, or
+ * unparseable BSDCONF_MAX_BYTES restores BSDCONF_MAX_BYTES_DEFAULT.
+ */
+size_t
+bsdconf_max_bytes(void)
+{
+ const char *s;
+ char *end;
+ unsigned long n;
+
+ s = getenv("BSDCONF_MAX_BYTES");
+ if (s == NULL || *s == '\0')
+ return (BSDCONF_MAX_BYTES_DEFAULT);
+ errno = 0;
+ n = strtoul(s, &end, 10);
+ if (errno != 0 || end == s || *end != '\0' || n == 0)
+ return (BSDCONF_MAX_BYTES_DEFAULT);
+ if (n > SIZE_MAX)
+ return (SIZE_MAX);
+ return ((size_t)n);
+}
+
+/*
+ * Read the remaining contents of `fd' into a freshly allocated,
+ * NUL-terminated buffer, stopping at EOF or the BSDCONF_MAX_BYTES cap.
+ * Regular files whose remaining length already exceeds the cap fail with
+ * EFBIG without reading. Returns the buffer on success (which the caller
+ * must free) or NULL (with errno set) on error.
+ */
+char *
+bsdconf_slurp(int fd, size_t *lenp)
+{
+ struct stat sb;
+ char *buf;
+ char *t;
+ size_t cap;
+ size_t maxb;
+ size_t off;
+ ssize_t r;
+
+ maxb = bsdconf_max_bytes();
+ if (maxb == 0) {
+ errno = EFBIG;
+ return (NULL);
+ }
+
+ /*
+ * Known remaining length on a regular file: fail fast if the cap
+ * cannot admit it, otherwise one exact allocation.
+ */
+ if (fstat(fd, &sb) == 0 && S_ISREG(sb.st_mode)) {
+ off_t cur;
+
+ cur = lseek(fd, 0, SEEK_CUR);
+ if (cur != -1 && sb.st_size >= cur) {
+ uintmax_t remain;
+
+ remain = (uintmax_t)(sb.st_size - cur);
+ if (remain > maxb) {
+ errno = EFBIG;
+ return (NULL);
+ }
+ return (bsdconf_readfile(fd, (size_t)remain, lenp));
+ }
+ }
+
+ /* Unknown length (pipe, socket, device): grow up to the cap */
+ cap = maxb < 8192 ? maxb : 8192;
+ if ((buf = malloc(cap + 1)) == NULL)
+ return (NULL);
+ off = 0;
+ for (;;) {
+ if (off == cap) {
+ size_t ncap;
+
+ if (cap >= maxb) {
+ char probe;
+
+ do {
+ r = read(fd, &probe, 1);
+ } while (r < 0 && errno == EINTR);
+ if (r < 0) {
+ free(buf);
+ return (NULL);
+ }
+ if (r > 0) {
+ free(buf);
+ errno = EFBIG;
+ return (NULL);
+ }
+ break;
+ }
+ ncap = cap * 2;
+ if (ncap / 2 != cap || ncap > maxb)
+ ncap = maxb;
+ if (ncap <= cap) {
+ free(buf);
+ errno = EFBIG;
+ return (NULL);
+ }
+ if ((t = realloc(buf, ncap + 1)) == NULL) {
+ free(buf);
+ return (NULL);
+ }
+ buf = t;
+ cap = ncap;
+ }
+ r = read(fd, buf + off, cap - off);
+ if (r < 0) {
+ if (errno == EINTR)
+ continue;
+ free(buf);
+ return (NULL);
+ }
+ if (r == 0)
+ break;
+ off += (size_t)r;
+ }
+
+ buf[off] = '\0';
+ *lenp = off;
+ return (buf);
+}
+
/*
* Write `len' bytes to `fd' and, when any bytes are written, remember the
* last one through `last' (as an unsigned char, or left untouched for a
diff --git a/usr.sbin/sysconf/sysconf.8 b/usr.sbin/sysconf/sysconf.8
--- a/usr.sbin/sysconf/sysconf.8
+++ b/usr.sbin/sysconf/sysconf.8
@@ -3,7 +3,7 @@
.\"
.\" SPDX-License-Identifier: BSD-2-Clause
.\"
-.Dd September 15, 2026
+.Dd September 16, 2026
\
.Dt SYSCONF 8
.Os
@@ -432,8 +432,7 @@
.Po
a fifo or
.Pa /dev/stdin ,
-for example,
-spooled to a temporary automatically
+for example
.Pc ;
write operations require a regular file.
A
@@ -598,6 +597,12 @@
.El
.Sh ENVIRONMENT
.Bl -tag -width "LOADER_DEFAULTS"
+.It Ev BSDCONF_MAX_BYTES
+Maximum bytes
+.Nm
+will read from a configuration file or stream;
+see
+.Xr bsdconf 3 .
.It Ev LOADER_DEFAULTS
Defaults file for the
.Ql loader
diff --git a/usr.sbin/sysconf/sysconf.c b/usr.sbin/sysconf/sysconf.c
--- a/usr.sbin/sysconf/sysconf.c
+++ b/usr.sbin/sysconf/sysconf.c
@@ -748,6 +748,8 @@
fprintf(stderr, OPTFMT, "-x",
"Remove name(s) from the target's files.");
fprintf(stderr, "ENVIRONMENT:\n");
+ fprintf(stderr, OPTFMT, "BSDCONF_MAX_BYTES",
+ "Cap on bytes read from a configuration file (see bsdconf(3)).");
fprintf(stderr, OPTFMT, "LOADER_DEFAULTS",
"Defaults file for the loader target (in place of");
fprintf(stderr, OPTFMT, "",
diff --git a/usr.sbin/sysconf/sysconf_priv.h b/usr.sbin/sysconf/sysconf_priv.h
--- a/usr.sbin/sysconf/sysconf_priv.h
+++ b/usr.sbin/sysconf/sysconf_priv.h
@@ -37,7 +37,7 @@
#define __unused /* not all compilers support attributes */
#endif
-#define SYSCONF_VERSION "1.1 2026-09-15"
+#define SYSCONF_VERSION "2.0 2026-09-16"
/* getopt(3) optstring; shared by parse_options() and find_target() */
#define OPTSTRING "AacdDEeFf:hij:k:lLnNqR:svVx"
diff --git a/usr.sbin/sysconf/sysconf_scan.c b/usr.sbin/sysconf/sysconf_scan.c
--- a/usr.sbin/sysconf/sysconf_scan.c
+++ b/usr.sbin/sysconf/sysconf_scan.c
@@ -236,22 +236,6 @@
fds[n] = open(conf_files[n], O_RDONLY);
if (fds[n] < 0 && errno != ENOENT)
err(EXIT_FAILURE, "%s", conf_files[n]);
-
- /*
- * Spool input that cannot seek (a fifo or /dev/stdin named
- * by `-f') before the sandbox slams shut: the library would
- * otherwise spool lazily inside bsdconf_fparse(), where
- * creating the temporary is no longer permitted.
- */
- if (fds[n] >= 0 && lseek(fds[n], 0, SEEK_CUR) == -1) {
- int sfd;
-
- if (errno != ESPIPE ||
- (sfd = bsdconf_spool(fds[n])) == -1)
- err(EXIT_FAILURE, "%s", conf_files[n]);
- close(fds[n]);
- fds[n] = sfd;
- }
}
#ifdef __FreeBSD__
diff --git a/usr.sbin/sysconf/tests/sysconf_test.sh b/usr.sbin/sysconf/tests/sysconf_test.sh
--- a/usr.sbin/sysconf/tests/sysconf_test.sh
+++ b/usr.sbin/sysconf/tests/sysconf_test.sh
@@ -416,6 +416,28 @@
cleanup_root
}
+atf_test_case max_bytes_cap cleanup
+max_bytes_cap_head()
+{
+ atf_set "descr" "BSDCONF_MAX_BYTES bounds parse input"
+}
+max_bytes_cap_body()
+{
+ setup_root
+ printf 'foo=ok\n' > "$ROOT/etc/make.conf"
+ atf_check -o inline:'foo: ok\n' "$SYSCONF" make -R "$ROOT" foo
+ # A tight cap rejects an otherwise valid file
+ atf_check -s not-exit:0 -e match:'File too large' \
+ env BSDCONF_MAX_BYTES=4 "$SYSCONF" make -R "$ROOT" foo
+ got=$( printf 'bar=piped\n' | "$SYSCONF" generic -f - bar ) ||
+ atf_fail "pipe parse failed"
+ atf_check_equal "bar: piped" "$got"
+}
+max_bytes_cap_cleanup()
+{
+ cleanup_root
+}
+
atf_init_test_cases()
{
atf_add_test_case make_src_knobs
@@ -428,4 +450,5 @@
atf_add_test_case verbatim_backslash
atf_add_test_case equal_value_nocheck_mtime
atf_add_test_case sysctl_oid_range
+ atf_add_test_case max_bytes_cap
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Sep 18, 8:56 AM (16 h, 19 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
39115201
Default Alt Text
D59751.id186933.diff (27 KB)
Attached To
Mode
D59751: libbsdconf: parse from a bounded in-memory buffer
Attached
Detach File
Event Timeline
Log In to Comment