config: bound conf_parse_name() loop to CONF_MAX_DIR_DEPTH - #3710
Open
94xhn wants to merge 1 commit into
Open
Conversation
conf_parse_name() tokenized a config name string and wrote each
segment pointer into name_argv[] with no check against the caller's
fixed-size array. All real callers (conf_set_value(), conf_get_value(),
conf_commit(), conf_save_tree()) declare `char *name_argv[CONF_MAX_DIR_DEPTH]`
(8 entries) on the stack and pass it straight in, so a name with more
than 8 '/'-separated segments overflows the array and corrupts adjacent
stack memory.
This is reachable remotely: config_mgmt.c (mcumgr "config read"/"config
write") copies an attacker-supplied CBOR "name" string of up to
CONF_MAX_NAME_LEN (64) bytes into a local buffer and passes it to
conf_get_value()/conf_set_value() unvalidated for segment count. A name
such as 16 single-character segments ("a/a/a/.../a", 31 bytes, well
under the 64-byte limit) is enough to write 8 pointers past the end of
the 8-slot array.
Bound the tokenizer loop and return an error once CONF_MAX_DIR_DEPTH is
reached. conf_parse_and_lookup() and every caller already treat a
non-zero conf_parse_name() rc / NULL lookup result as an invalid name
(OS_INVALID_PARM), so no other code needed to change.
Verified with a standalone harness reproducing the real stack-frame
layout (name_argc + name_argv[8] + adjacent locals) that the unpatched
function corrupts the trailing memory once fed >8 segments, and that
the patched function rejects over-depth names while leaving normal
parsing (including the exact 8-segment boundary) unchanged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
conf_parse_name()insys/config/src/config.ctokenizes a configname string on
/and writes each token pointer into the caller'sname_argv[]array with no bound check:Every real caller declares a fixed 8-entry array
(
CONF_MAX_DIR_DEPTH,sys/config/include/config/config.h:39) on thestack and passes it straight in:
conf_set_value()—sys/config/src/config.c:447conf_get_value()—sys/config/src/config.c:475conf_commit()—sys/config/src/config.c:497conf_save_tree()—sys/config/src/config_store.c:264A name with more than 8
/-separated segments overflowsname_argv[]and writes past the end of the array on the stack, corrupting whatever
local variables/saved registers follow it.
This is reachable remotely, not just from local misuse:
mgmt/config(
sys/config/src/config_mgmt.c, mcumgr "config read"/"config write")copies an attacker-supplied CBOR
"name"string of up toCONF_MAX_NAME_LEN(64) bytes into a local buffer and passes itunvalidated (for segment count) into
conf_get_value()/conf_set_value(). A name such as 16 single-character segments(
"a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a", 31 bytes — well under the 64-bytelimit) is enough to write 8 pointers past the end of the 8-slot array.
The same unbounded loop is also reachable locally via the config shell
command, since the defect is in the shared
conf_parse_name()/conf_parse_and_lookup()core, not inconfig_mgmt-specific code.Fix
Bound the tokenizer loop and return an error (
-1) onceCONF_MAX_DIR_DEPTHsegments have been collected, instead of writingpast the array:
No other code needs to change:
conf_parse_and_lookup()(
sys/config/src/config.c:151-160) already doesif (rc) { return NULL; }, and every one of the four callers alreadytreats a
NULLlookup result the same way it treats "handler notfound" — returning
OS_INVALID_PARM(or, forconf_commit(NULL),simply skipping that entry). So an over-depth name is now rejected the
same way an unregistered/garbled name already was, with no behavior
change for any name that has ≤ 8 segments (including the exact
boundary case of 8 segments, which still parses correctly).
Verification
I do not have the full Mynewt build toolchain set up to build a target
image, so I verified the change with a standalone C harness (gcc 8.1.0,
-std=c99 -Wall -Wextra -O0, zero warnings) built from the functionbody extracted verbatim (via
awk) from the actual patchedsys/config/src/config.c, driven through a struct reproducing the realstack layout (
name_argc+name_argv[CONF_MAX_DIR_DEPTH]+ anadjacent canary standing in for the real neighboring locals):
I additionally ran the identical harness against an unpatched copy of
conf_parse_name()beforehand and confirmed the 16-segment casecorrupts the canary (
canary_intact=NO (BUG!)) while the 3-segmentnormal case does not — matching the described failure mode.
Disclosure
Generative AI (Claude, Anthropic) was used to help investigate this
issue and implement/verify the fix. All findings and the change itself
were reviewed by me before submission.