Page MenuHomeFreeBSD

D59751.id186933.diff
No OneTemporary

D59751.id186933.diff

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

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)

Event Timeline