Skip to content

fix(falcon): preserve CrowdStrike OTA updates across reboots - #1899

Merged
shunkakinoki merged 1 commit into
mainfrom
fix/falcon-preserve-ota-updates
Jun 8, 2026
Merged

fix(falcon): preserve CrowdStrike OTA updates across reboots#1899
shunkakinoki merged 1 commit into
mainfrom
fix/falcon-preserve-ota-updates

Conversation

@shunkakinoki

@shunkakinoki shunkakinoki commented Jun 8, 2026

Copy link
Copy Markdown
Owner

Summary

  • CrowdStrike cloud pushed OTA update (18410 -> 19004) but falcon-init.sh rsync'd old binaries back on boot, causing CS_PREVENTION_MISSING
  • Init script now compares build numbers and skips rsync when installed version is newer
  • Reverts kernel pin from 6.17 back to 6.18 (fix(matic): pin kernel to 6.17 for Falcon sensor compatibility #1898 was a misdiagnosis - the kernel was never the issue)

Root cause

Audit logs showed falcon-sensor-bpf19004 failing to exec /opt/CrowdStrike/falcon-fxpredict (ENOENT) because the rsync restored 18410-versioned symlinks over the OTA-updated 19004 binaries.

Test plan

  • sudo nixos-rebuild switch
  • sudo systemctl restart falcon-sensor
  • Verify CS_PREVENTION_MISSING resolves in Drata/Falcon console

Summary by cubic

Prevents CrowdStrike Falcon OTA updates from being overwritten on reboot by skipping rsync when a newer build is already installed. Also reverts the kernel pin to 6.18 for sensor compatibility and resolves CS_PREVENTION_MISSING.

  • Bug Fixes
    • falcon-init.sh: compare installed vs packaged build numbers via falconctl and skip rsync when installed is newer, preserving OTA updates (e.g., 19004).
    • Kernel pin set to 6.18 (6.19 remains RFM) to align with Falcon sensor support.

Written for commit e7386a4. Summary will update on new commits.

Review in cubic

Root cause: CrowdStrike cloud pushed an OTA update from build 18410 to
19004, but falcon-init.sh rsync'd the old 18410 binaries back on boot,
clobbering the update. The running 19004 sensor then failed to exec
helper binaries (falcon-fxpredict) with ENOENT, causing
CS_PREVENTION_MISSING.

The init script now compares installed vs packaged build numbers and
skips the rsync when the installed version is newer.

Also reverts the kernel pin from 6.17 back to 6.18 (#1898 was a
misdiagnosis).
@indent-zero

indent-zero Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor
PR Summary

Restores /opt/CrowdStrike/ OTA-installed Falcon binaries from being clobbered by the boot-time rsync from the Nix store, which had caused the running sensor to exec missing helper binaries (CS_PREVENTION_MISSING). Also reverts the matic kernel pin from 6.17 back to 6.18, which the author now considers a misdiagnosis in #1898.

  • named-hosts/matic/falcon-init.sh: adds a build-number comparison (readlink -f + trailing-digit regex) on falconctl between the Nix-packaged copy and the installed copy; skips the rsync when the installed build is newer, preserving OTA updates. State-file excludes (falconstore*, CsConfig) are kept inside the new conditional.
  • named-hosts/matic/default.nix: boot.kernelPackages reverted from linuxPackages_6_17 to linuxPackages_6_18, with updated inline comment.

Issues

5 potential issues found:

  • Version detection silently falls back to 0/0 if /opt/CrowdStrike/falconctl isn't a falconctl -> falconctl<build> symlink, which makes inst > pkg false and runs the OTA-clobbering rsync exactly as before — with no journal signal that the comparison degraded. Trigger: any state where the installer/OTA leaves falconctl as a plain file (partial OTA, manual reinstall, future layout change). → Autofix
  • 2>/dev/null on [ "$inst_build" -gt "$pkg_build" ] is dead defensiveness: both operands are coerced to integers via the || echo "0" fallback so the test can't emit a non-integer error. Drop the redirect to avoid implying a failure mode that can't actually happen. → Autofix
  • After a successful OTA-skip, the Nix package version in falcon/default.nix (7.31.0-18410) and the hardcoded Version: 7.31.0-18410 in the kolide.nix dpkg-status shim no longer match the running sensor — so Kolide/osquery's deb_packages compliance check will report the stale build. Pre-existing, but the new logic deliberately preserves the OTA build and makes the drift load-bearing rather than incidental. → Autofix
  • Sync path is silent about the detected build numbers — only the skip branch logs. If the comparison ever degrades to 0/0 (see prior issue) or a future regression re-clobbers an OTA, there's nothing in journalctl -u falcon-sensor to point at the cause. Add an unconditional echo "falcon-init: installed=$inst_build packaged=$pkg_build" after the comparison. → Autofix
  • pkg_ver and installed_ver hold filesystem paths, not versions — the actual versions are pkg_build/inst_build. Rename to pkg_path/installed_path (or similar) for clarity. → Autofix

CI Checks

Waiting for CI checks...


⚡ Autofix All Issues

@shunkakinoki
shunkakinoki merged commit c5987fd into main Jun 8, 2026
26 of 29 checks passed
@shunkakinoki
shunkakinoki deleted the fix/falcon-preserve-ota-updates branch June 8, 2026 19:17
@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ffcd8752-1733-4328-a48e-c0f02e43d50f

📥 Commits

Reviewing files that changed from the base of the PR and between 7c057b7 and e7386a4.

📒 Files selected for processing (2)
  • named-hosts/matic/default.nix
  • named-hosts/matic/falcon-init.sh

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.


📝 Walkthrough

Summary by CodeRabbit

  • Updates

    • Updated kernel to version 6.18 for CrowdStrike Falcon compatibility.
  • Improvements

    • Optimized initialization by skipping redundant file synchronization when the installed version is current.

Walkthrough

Updated the matic NixOS host to pin the kernel to version 6.18 for CrowdStrike Falcon compatibility. Enhanced the Falcon binary initialization script with conditional rsync logic that compares build numbers between installed and packaged falconctl versions, skipping synchronization when the installed build is newer.

Changes

CrowdStrike Falcon host compatibility

Layer / File(s) Summary
Kernel version pin for Falcon compatibility
named-hosts/matic/default.nix
Kernel packages pinned from linuxPackages_6_17 to linuxPackages_6_18 with corresponding comment update documenting the Falcon sensor/kernel relationship.
Conditional rsync logic for Falcon binary updates
named-hosts/matic/falcon-init.sh
Added build-number comparison between installed and packaged falconctl to determine whether rsync should execute. Build IDs are extracted from readlink symlink targets, rsync is skipped when installed build is newer, and excluded state files are preserved on update.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

Possibly related PRs

  • shunkakinoki/dotfiles#1055: Modifies falcon-init.sh's CrowdStrike binary synchronization logic with rsync-based updates and state-file preservation.

  • shunkakinoki/dotfiles#905: Updates named-hosts/matic/default.nix to pin the kernel to pkgs.linuxPackages_6_18 for CrowdStrike compatibility.

  • shunkakinoki/dotfiles#743: Changes CrowdStrike Falcon initialization logic to avoid unnecessary reinitialization through conditional/idempotent logic patterns.

Suggested labels

bug

Poem

🐰 A kernel bump and sync review,
Build checks now decide what's new,
Falcon stays safe, no waste in sight,
Conditional logic sets things right!

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/falcon-preserve-ota-updates

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request updates the pinned kernel version to 6.18 for CrowdStrike Falcon compatibility and modifies the falcon-init.sh script to skip the rsync process if the installed build is newer than the packaged build, preventing OTA updates from being clobbered. Feedback suggests replacing the external grep pipeline in falcon-init.sh with Bash's built-in regular expression matching to improve robustness and efficiency.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +21 to +28
if [ -x "$installed_ver" ]; then
pkg_build=$(readlink -f "$pkg_ver" | grep -oP '\d+$' || echo "0")
inst_build=$(readlink -f "$installed_ver" | grep -oP '\d+$' || echo "0")
if [ "$inst_build" -gt "$pkg_build" ] 2>/dev/null; then
echo "falcon-init: installed build $inst_build is newer than packaged $pkg_build, skipping rsync"
need_sync=false
fi
fi

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.

medium

Using external grep -oP in a pipeline under set -euo pipefail can be fragile and spawns unnecessary subprocesses. Additionally, if the extracted build numbers are not valid integers, the -gt comparison will throw a bash evaluation error (which is currently suppressed with 2>/dev/null).

We can make this much more robust and efficient by using Bash's built-in regular expression matching ([[ ... =~ ... ]]). This avoids spawning grep processes, guarantees that the variables are valid integers, and eliminates the need to suppress comparison errors.

Suggested change
if [ -x "$installed_ver" ]; then
pkg_build=$(readlink -f "$pkg_ver" | grep -oP '\d+$' || echo "0")
inst_build=$(readlink -f "$installed_ver" | grep -oP '\d+$' || echo "0")
if [ "$inst_build" -gt "$pkg_build" ] 2>/dev/null; then
echo "falcon-init: installed build $inst_build is newer than packaged $pkg_build, skipping rsync"
need_sync=false
fi
fi
if [ -x "$installed_ver" ]; then
pkg_target=$(readlink -f "$pkg_ver" 2>/dev/null || true)
inst_target=$(readlink -f "$installed_ver" 2>/dev/null || true)
pkg_build=0
if [[ "$pkg_target" =~ ([0-9]+)$ ]]; then
pkg_build=${BASH_REMATCH[1]}
fi
inst_build=0
if [[ "$inst_target" =~ ([0-9]+)$ ]]; then
inst_build=${BASH_REMATCH[1]}
fi
if [ "$inst_build" -gt "$pkg_build" ]; then
echo "falcon-init: installed build $inst_build is newer than packaged $pkg_build, skipping rsync"
need_sync=false
fi
fi


if [ -x "$installed_ver" ]; then
pkg_build=$(readlink -f "$pkg_ver" | grep -oP '\d+$' || echo "0")
inst_build=$(readlink -f "$installed_ver" | grep -oP '\d+$' || echo "0")

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.

Version detection silently no-ops on a plain-file falconctl. readlink -f /opt/CrowdStrike/falconctl only ends in build digits when CrowdStrike's installer keeps falconctl as a symlink to falconctl<build> (the documented layout per yaleman.org's CrowdStrike cleanup post, and what the .deb ships). If that ever isn't true on this host — partial OTA, manual reinstall, future packaging change — grep -oP '\d+$' misses, || echo "0" kicks in, inst_build=0, 0 -gt $pkg_build is false, and the rsync below clobbers the OTA exactly as before. There's no log of the detected values on the sync path either (only the skip branch logs), so the degradation is invisible in the journal.

Reproduced locally:

$ readlink -f /tmp/test/opt/CrowdStrike/falconctl   # plain file
/tmp/test/opt/CrowdStrike/falconctl
$ readlink -f /tmp/test/opt/CrowdStrike/falconctl | grep -oP '\d+$' || echo 0
0

Consider either (a) calling falconctl -g --version (or reading the build from the sensor directly) instead of inferring from the symlink target, or at minimum (b) echo "falcon-init: installed=$inst_build packaged=$pkg_build" unconditionally so a regression is obvious in journalctl -u falcon-sensor.

echo "falcon-init: installed build $inst_build is newer than packaged $pkg_build, skipping rsync"
need_sync=false
fi
fi

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.

Consider logging the detected builds unconditionally (e.g. echo "falcon-init: installed=$inst_build packaged=$pkg_build" right after the if [ -x "$installed_ver" ] block) so the next time someone debugs a clobbered OTA they can see whether the comparison ran correctly or silently fell back to 0.

# version is not newer than the package, otherwise the rsync clobbers the
# update and the running sensor can't find its helper binaries (ENOENT).
pkg_ver="@falcon@/opt/CrowdStrike/falconctl"
installed_ver=/opt/CrowdStrike/falconctl

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.

Nit: pkg_ver / installed_ver are misleading — they're paths to falconctl, not version strings. Renaming to pkg_path / installed_path would line up with the pkg_build / inst_build variables that do hold versions.

if [ -x "$installed_ver" ]; then
pkg_build=$(readlink -f "$pkg_ver" | grep -oP '\d+$' || echo "0")
inst_build=$(readlink -f "$installed_ver" | grep -oP '\d+$' || echo "0")
if [ "$inst_build" -gt "$pkg_build" ] 2>/dev/null; then

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.

Nit: the 2>/dev/null is redundant — pkg_build and inst_build are both normalized to integers by the || echo "0" fallback, so [ -gt ] can't print a non-integer error here. Removing it makes the intent ("this is a plain integer compare") clearer.

Suggested change
if [ "$inst_build" -gt "$pkg_build" ] 2>/dev/null; then
if [ "$inst_build" -gt "$pkg_build" ]; then

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.

1 participant