Skip to content

Harden CHEF_POWERSHELL_BIN/architecture resolution and fix a native assembly-resolve crash - #382

Merged
johnmccrae merged 4 commits into
18-Stablefrom
jfm/gem-hardening-part-2
Sep 14, 2026
Merged

johnmccrae merged 4 commits into
18-Stablefrom
jfm/gem-hardening-part-2

Conversation

@johnmccrae

Copy link
Copy Markdown
Contributor

What we did

While building a fault-injection test harness (chef-powershell/chef_gem_break_test.rb) to deliberately try to reproduce error 126 / "missing DLL" failures against chef-powershell, we found and fixed three real bugs, plus added the harness itself as a permanent regression tool.

1. CHEF_POWERSHELL_BIN was silently clobbered at require time

powershell_exec.rb unconditionally overwrote ENV["CHEF_POWERSHELL_BIN"] whenever the loaded gem's own bundled DLL folder existed — even if the caller had already set it intentionally (e.g. to point at a freshly built Habitat package for testing, or any other override). Fixed to only auto-detect when the variable isn't already set, so an explicit override is always respected.

2. ENV["PROCESSOR_ARCHITECTURE"] || "AMD64" doesn't fall back correctly

Ruby's || only falls back on nil/false, not on an empty string. If PROCESSOR_ARCHITECTURE is set but blank (confirmed reproducible via the fault-injection harness), this pattern silently built a broken path instead of defaulting to AMD64, causing a LoadError. Fixed in pwsh.rb, powershell.rb, powershell_exec.rb, and extconf.rb.

3. Native assembly resolution could hard-crash the whole ruby.exe host

Chef.PowerShell.Wrapper.cpp's AssemblyResolve handler read CHEF_POWERSHELL_BIN independently of Ruby's own DLL resolution logic. If that env var was set to anything that didn't contain Chef.PowerShell.dll — even while the wrapper DLL itself had been correctly loaded from a completely different, correct location — the resolver only tried CHEF_POWERSHELL_BIN and gave up, causing an unhandled managed exception that crashes the entire process (SIGSEGV, not a catchable Ruby exception). Fixed by adding a fallback to the directory the wrapper assembly itself was actually loaded from, so a stale/wrong CHEF_POWERSHELL_BIN can no longer break assembly resolution when the correct DLL is sitting right next to the wrapper.

4. New: chef_gem_break_test.rb fault-injection harness

A permanent, repo-committed script that deliberately sabotages chef-powershell in ways a real user/CI/machine might (accidentally or otherwise) and verifies the result is either full resilience or a clean, understandable failure — never a silent wrong result or an unexplained crash. Covers:

  • Corrupted/partial DLL layouts (missing hostfxr.dll, missing shared/Microsoft.NETCore.App, missing wrapper DLLs, missing CRT dependencies)
  • Hostile environment variables (bogus DOTNET_ROOT, bogus CHEF_POWERSHELL_BIN, blank PROCESSOR_ARCHITECTURE)
  • Stale/multiple installed gem versions shadowing each other
  • An optional control scenario against a real Chef install (destructive; only runs when explicitly pointed at a disposable environment via CHEF_HAB_IDENT/CHEF_OMNIBUS_RUBY)

Every scenario operates on disposable scratch copies — it never touches a real installed chef-powershell-shim/chef-infra-client package.

5. Regression spec

Added an integration spec locking in fix #1 (does not clobber an explicit CHEF_POWERSHELL_BIN override when requiring chef-powershell).

Why this helps

Error 126 ("missing DLL") has been a recurring, hard-to-diagnose failure mode for this gem. These fixes close three concrete gaps that could each independently reproduce that class of failure in real-world conditions (a stale env var left over from a previous run, a machine with a blank/unusual PROCESSOR_ARCHITECTURE, or any situation where CHEF_POWERSHELL_BIN doesn't exactly match where the wrapper DLL was actually loaded from). The fault-injection harness turns "we think this is fixed" into something we can re-verify on demand, and gives us a repeatable way to catch the next regression before it ships.

Verification

  • Full spec suite: 69 examples, 0 failures (before and after the C++ rebuild)
  • chef_gem_break_test.rb: confirmed each fix flips its corresponding scenario from crash/failure to the expected clean outcome
  • C++ change rebuilt via hab pkg build habitat (0 compile errors) and re-tested against the harness and full spec suite

Known follow-up

A handful of chef_gem_break_test.rb scenarios (missing wrapper DLL, missing CRT dependency, stale multi-version shadowing) still report unexpected "success" on this dev machine — traced directly to this machine's own pre-existing chef/chef-powershell-shim Habitat install acting as a fallback, not a code defect. These should be re-verified in a clean, disposable container without that package pre-installed.

Signed-off-by: John McCrae <mccrae@progress.com>
Signed-off-by: John McCrae <mccrae@progress.com>
Signed-off-by: John McCrae <mccrae@progress.com>
Signed-off-by: John McCrae <mccrae@progress.com>

@tpowell-progress tpowell-progress 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.

We probably want to extract arch = ENV["PROCESSOR_ARCHITECTURE"].to_s.empty? ? "AMD64" : ENV["PROCESSOR_ARCHITECTURE"] to an arch.rb helper, but this works for now.

@johnmccrae
johnmccrae merged commit 205574d into 18-Stable Sep 14, 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.

2 participants