Skip to content

fix(bootloader-env): block all fw_setenv options, not only -s/--script - #679

Merged
JanZachmann merged 4 commits into
omnect:mainfrom
JanZachmann:jz-2026-07-31-bootloader-env-quoting
Aug 5, 2026
Merged

fix(bootloader-env): block all fw_setenv options, not only -s/--script#679
JanZachmann merged 4 commits into
omnect:mainfrom
JanZachmann:jz-2026-07-31-bootloader-env-quoting

Conversation

@JanZachmann

@JanZachmann JanZachmann commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Summary

Harden the u-boot bootloader_env.sh wrapper against fw_setenv/fw_printenv option injection, refactor the wrapper along the way, and pin omnect-device-service 0.45.1 which carries the same fix for its own wrapper sudo/fw_setenv_no_script.sh.

  • Pass -- to fw_setenv/fw_printenv so key and value are always treated as data. This blocks script mode and every other option, e.g. an attacker-chosen config file, which a flag blocklist would miss (getopt accepts attached values like -sFILE and abbreviations like --scr=FILE).
  • Rename set/unset/get/list to cmd_* so nothing shadows the bash builtins.
  • Each cmd_* takes its own quoted arguments and checks its own argc. Top-level dispatcher looks the function up via declare -F instead of an unquoted string match.
  • get prints values with printf '%s\n' and strips the key= prefix with a quoted pattern, so values with a leading -n/-e and keys with glob metacharacters do not misbehave.
  • Bump omnect-device-service to 0.45.1. Recipe regenerated with cargo-bitbake; crate set unchanged.

Reason

The old blocklist covered -s, --script, -s=* and --script=*. fw_setenv parses arguments with getopt_long and the optstring Vc:f:s:nhm: (libubootenv, src/fw_printenv.c), which accepts attached values (-sFILE) and abbreviations (--scr=FILE), so those forms passed the filter and reached fw_setenv as options. GNU getopt also keeps scanning after the first positional argument, so the value position was enough to sneak in an option.

recipes-azure-iot/iot-hub-device-update/iot-hub-device-update/adu-bootloader-env pins the key but passes the value unfiltered for omnect_extra_bootargs and omnect_validate_extra_bootargs, so the adu user could write files as root — via a script file (-s) or an attacker-chosen config (-c), the latter not covered by a -s/--script blocklist at all.

The same defect existed in ods's own wrapper sudo/fw_setenv_no_script.sh (omnect/omnect-device-service#208). Both copies ship in the same image, so the ods pin belongs in this PR — otherwise the image would keep the unfixed wrapper.

Supersedes #678.

The set command compared key and value against '-s', '--script' and their
'=' forms. getopt accepts attached values and abbreviations, so a value like
'-sFILE' or '--scr=FILE' reached fw_setenv as script mode, and '-cFILE' as an
attacker-chosen config, both writing files as root. The adu sudoers rules pass
the value unfiltered for omnect_extra_bootargs and omnect_validate_extra_bootargs,
so a crafted value could exploit this.

Pass '--' instead: key and value are always data, which covers every option
rather than a list of known ones. Same for unset and get, whose key reaches
fw_setenv/fw_printenv the same way.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
The dispatch called "${1} ${@:2}" unquoted, so a value with spaces was split
into words and rejoined inside set() via "${@:2}". Two consequences: repeated
whitespace collapsed to a single space, and a value containing glob characters
was pathname-expanded, e.g. "set omnect_extra_bootargs '*'" stored the listing
of the current directory. The argument count was checked against a global taken
before the split, which is why the split went unnoticed.

Quote the dispatch and take the value from "${2}", so each function gets its
arguments as they were passed and can count them itself.

Rename the commands to cmd_* as well. set and unset shadowed shell builtins,
which only works because bash prefers functions over builtins outside POSIX
mode - and set is a POSIX special builtin. The prefix also lets the dispatch
check the function exists instead of matching the command against a list as a
substring.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
A value of -n, -e or -E is an option of echo, so "get" printed nothing and the
caller could not tell it apart from an unset key. printf takes no options after
the format string, so the value is passed through unchanged.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Refactors the bootloader_env.sh wrappers (u-boot and GRUB variants) to safely dispatch commands and preserve argument integrity (notably values containing whitespace or glob characters), while also avoiding conflicts with shell builtins.

Changes:

  • Rename command functions to cmd_* and update dispatch to call "cmd_${1}" "${@:2}" with a function-existence check (declare -F) instead of substring-matching against a list.
  • Stop re-joining value arguments via "${@:2}"; set now takes the value from "${2}" (so a value with spaces must be passed as a single quoted argument).
  • Harden u-boot fw_setenv / fw_printenv calls by passing -- so key/value are always treated as data.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
recipes-omnect/bootloader_env/bootloader-env/bootloader_env_u-boot.sh Updates dispatch and argument handling; uses -- with fw_setenv/fw_printenv to prevent option injection.
recipes-omnect/bootloader_env/bootloader-env/bootloader_env_grub.sh Updates dispatch and argument handling for GRUB env editing, including quoting and cmd_* dispatch.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread recipes-omnect/bootloader_env/bootloader-env/bootloader_env_grub.sh

@HarryWaschkeit HarryWaschkeit left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see copilot comment

ods 0.45.1 carries the same fw_setenv fix for its own wrapper
(sudo/fw_setenv_no_script.sh) that this branch applies to the layer's
bootloader_env.sh, so both copies of the defect get fixed together.

Recipe regenerated with cargo-bitbake; the crate set did not change.

Signed-off-by: Jan Zachmann <50990105+JanZachmann@users.noreply.github.com>
(cherry picked from commit 80d0500)
@JanZachmann

JanZachmann commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

@HarryWaschkeit the review of #678 fixed code that was part of this PR as well. Thus I closed #678 and this one must be re-appropved

@HarryWaschkeit HarryWaschkeit left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imho the description should also mention the omnect-device-service update that is contained.

@JanZachmann JanZachmann changed the title refactor(bootloader-env): pass arguments as arguments fix(bootloader-env): block all fw_setenv options, not only -s/--script Aug 5, 2026
@JanZachmann

Copy link
Copy Markdown
Contributor Author

FYI: @mlilien

@JanZachmann
JanZachmann merged commit b01530d into omnect:main Aug 5, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants