From: Michael Brown Date: Wed, 5 Aug 2026 12:17:16 +0000 (+0100) Subject: [settings] Fix limited out-of-bounds read in fetch_numeric_setting() X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=36c25549c8b0a5684e5501ca6b6eed04bf79ea9d;p=thirdparty%2Fipxe.git [settings] Fix limited out-of-bounds read in fetch_numeric_setting() The code in fetch_numeric_setting() reads the setting value into a local fixed-size buffer but then passes the full setting length to numeric_setting_value(). If the setting length exceeds the size of the fixed-size buffer, then numeric_setting_value() will continue to read bytes from the stack. The number of bytes read is constrained: numeric_setting_value() will exit with -ERANGE as soon as the value being constructed exceeds the range of an unsigned long. The existence of a return address on the stack thus provides an upper bound on how far numeric_setting_value() can read before terminating with an error. Creating a setting with a length of more than an unsigned long is trivial, for example: set thing:hexraw 00000000000000000000000000000000 However, the out-of-bounds read can be reached only via calls to the fetch_[u]int[z]_setting() family of internal helper functions. Reading the setting in a script via e.g. ${thing:uint32} goes via a different code path that does not use a fixed-length buffer. The fetch_[u]int[z]_setting() functions are called from only a few places. Most uses are for boolean flags or bit masks. A few are genuinely used as numeric values: the settings mechanism itself reads and uses the "priority" setting, the network core reads the "mtu" setting, and the SAN boot mechanism reads the drive number and retry count. An extremely determined attacker could potentially obtain up to eight bytes of information from the stack (in a 64-bit build) by, for example, creating two sibling settings blocks where one has an overlength "priority" setting value, and then repeatedly manipulating the priority in the other settings block and testing to see which block ends up with the higher priority. The information that could be obtained in this way is limited to the temporary values stored on the stack by fetch_numeric_setting() itself, along with its own return address. None of this information is security-sensitive, and so any information leakage is a mere curiosity. Fix by allocating a temporary copy within fetch_numeric_setting() instead of using a fixed-size buffer. This has the downside of introducing an otherwise unnecessary memory allocation (which could potentially itself fail), but guarantees consistency with other numeric interpretations of setting values. (The alternative approach of rejecting overlength setting values would introduce a potential inconsistency between the value returned by fetch_numeric_setting() and the value obtained by formatting a setting using a numeric setting type, or by numerating the setting.) Signed-off-by: Michael Brown --- diff --git a/src/core/settings.c b/src/core/settings.c index 2e5760330..221d8a1bd 100644 --- a/src/core/settings.c +++ b/src/core/settings.c @@ -998,19 +998,29 @@ static int numeric_setting_value ( int is_signed, const void *raw, size_t len, int fetch_numeric_setting ( struct settings *settings, const struct setting *setting, unsigned long *value, int is_signed ) { - unsigned long tmp; + void *raw; + int raw_len; int len; /* Avoid returning uninitialised data on error */ *value = 0; /* Fetch raw (network-ordered, variable-length) setting */ - len = fetch_raw_setting ( settings, setting, &tmp, sizeof ( tmp ) ); - if ( len < 0 ) - return len; + raw_len = fetch_raw_setting_copy ( settings, setting, &raw ); + if ( raw_len < 0 ) { + len = raw_len; + goto err_fetch_copy; + } /* Extract numeric value */ - return numeric_setting_value ( is_signed, &tmp, len, value ); + len = numeric_setting_value ( is_signed, raw, raw_len, value ); + if ( len < 0 ) + goto err_value; + + err_value: + free ( raw ); + err_fetch_copy: + return len; } /**