fleet/ee/server/service/embedded_scripts/linux_wipe.sh
Allen Houchins 0873c50a30
Make Linux wipe script safer for network filesystems (#41812)
This pull request enhances the safety of the `linux_wipe.sh` script by
ensuring that destructive file operations do not affect network-mounted
filesystems. The changes introduce checks to detect network filesystems,
prevent accidental deletion of remote data, and improve the reliability
of wipe operations by avoiding crossing filesystem boundaries.

**Network filesystem safety improvements:**

* Added a `NETWORK_FS_TYPES` variable and functions to detect and
unmount network filesystems, preventing the script from deleting data on
NFS, CIFS, SMB, SSHFS, and similar mounts.
(`ee/server/service/embedded_scripts/linux_wipe.sh`)
[[1]](diffhunk://#diff-7ac85220cbd45e63481837a405dacf198822a4fbf885b88f89b9bc870c947fccR3-R4)
[[2]](diffhunk://#diff-7ac85220cbd45e63481837a405dacf198822a4fbf885b88f89b9bc870c947fccR17-R84)
* Introduced an `unmount_network_filesystems` function called before
wiping operations to unmount all detected network filesystems.
(`ee/server/service/embedded_scripts/linux_wipe.sh`)
* Added an `is_network_mount` function to skip wiping any path residing
on a network filesystem.
(`ee/server/service/embedded_scripts/linux_wipe.sh`)

**Safe file deletion enhancements:**

* Implemented a `safe_rm` function that ensures file deletions do not
cross filesystem boundaries, using `rm --one-file-system` or `find
-xdev` as a fallback. All destructive operations now use this wrapper.
(`ee/server/service/embedded_scripts/linux_wipe.sh`)
* Updated `wipe_non_essential_data` and `wipe_system_files` to use
`safe_rm` and to skip paths on network filesystems.
(`ee/server/service/embedded_scripts/linux_wipe.sh`)

These changes significantly reduce the risk of deleting data on remote
or shared filesystems during a wipe operation.


<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #

# Checklist for submitter

If some of the following don't apply, delete the relevant line.

- [ ] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.

- [ ] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements), JS
inline code is prevented especially for url redirects
- [ ] If paths of existing endpoints are modified without backwards
compatibility, checked the frontend/CLI for any necessary changes

## Testing

- [ ] Added/updated automated tests
- [ ] Where appropriate, [automated tests simulate multiple hosts and
test for host
isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing)
(updates to one hosts's records do not affect another)

- [ ] QA'd all new/changed functionality manually

For unreleased bug fixes in a release candidate, one of:

- [ ] Confirmed that the fix is not expected to adversely impact load
test results
- [ ] Alerted the release DRI if additional load testing is needed

## Database migrations

- [ ] Checked schema for all modified table for columns that will
auto-update timestamps during migration.
- [ ] Confirmed that updating the timestamps is acceptable, and will not
cause unwanted side effects.
- [ ] Ensured the correct collation is explicitly set for character
columns (`COLLATE utf8mb4_unicode_ci`).

## New Fleet configuration settings

- [ ] Setting(s) is/are explicitly excluded from GitOps

If you didn't check the box above, follow this checklist for
GitOps-enabled settings:

- [ ] Verified that the setting is exported via `fleetctl
generate-gitops`
- [ ] Verified the setting is documented in a separate PR to [the GitOps
documentation](https://github.com/fleetdm/fleet/blob/main/docs/Configuration/yaml-files.md#L485)
- [ ] Verified that the setting is cleared on the server if it is not
supplied in a YAML file (or that it is documented as being optional)
- [ ] Verified that any relevant UI is disabled when GitOps mode is
enabled

## fleetd/orbit/Fleet Desktop

- [ ] Verified compatibility with the latest released version of Fleet
(see [Must
rule](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/workflows/fleetd-development-and-release-strategy.md))
- [ ] If the change applies to only one platform, confirmed that
`runtime.GOOS` is used as needed to isolate changes
- [ ] Verified that fleetd runs on macOS, Linux and Windows
- [ ] Verified auto-update works from the released version of component
to the new version (see [tools/tuf/test](../tools/tuf/test/README.md))

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-04-08 09:16:22 -05:00

166 lines
5.4 KiB
Bash

#!/bin/sh
NETWORK_FS_TYPES="nfs|nfs4|cifs|smb|smbfs|fuse\.sshfs|afs|ncpfs|9p"
# Function to log out all users and lock their passwords except root
logout_users() {
for user in $(who | awk '{print $1}' | sort | uniq)
do
if [ "$user" != "root" ]; then
echo "Logging out $user"
pkill -KILL -u "$user"
passwd -l "$user"
fi
done
}
# Unmount all network filesystems to prevent remote data deletion.
unmount_network_filesystems() {
if [ ! -f /proc/mounts ]; then
echo "Error: /proc/mounts not found; aborting wipe to avoid unsafe network data deletion" >&2
exit 1
fi
awk '$3 ~ /^('"$NETWORK_FS_TYPES"')$/ {print $2}' /proc/mounts \
| awk '{print length, $0}' \
| sort -nr \
| cut -d" " -f2- \
| while read -r mnt_esc; do
# Unescape mountpoint in case it contains octal escapes like \040 for space.
mnt=$(printf '%b' "$mnt_esc")
# Never unmount critical mountpoints that may contain required userland.
case "$mnt" in
/|/usr|/bin|/sbin|/lib|/lib64|/usr/bin|/usr/sbin|/usr/lib|/usr/lib64)
echo "Skipping critical network-mounted filesystem: $mnt"
continue
;;
esac
echo "Unmounting network filesystem: $mnt"
umount -f -l "$mnt" 2>/dev/null || echo "Warning: failed to unmount $mnt"
done
}
# Returns 0 (true) if the given path resides on a network filesystem.
is_network_mount() {
if [ ! -f /proc/mounts ]; then
echo "Error: /proc/mounts not found; aborting wipe to avoid unsafe network data deletion" >&2
exit 1
fi
_target="$1"
# Resolve the target to a canonical path if possible, so that symlinks
# (e.g. /home -> /mnt/nfs/home) do not hide network mounts.
if command -v readlink >/dev/null 2>&1; then
_resolved=$(readlink -f -- "$_target" 2>/dev/null || printf '%s\n' "$_target")
_target="$_resolved"
fi
# Walk up to find the mount point that contains this path.
_match=$(awk '$3 ~ /^('"$NETWORK_FS_TYPES"')$/ {print $2}' /proc/mounts | while read -r mnt_esc; do
mnt=$(printf '%b' "$mnt_esc")
case "$mnt" in
/)
case "$_target" in
/*) echo "$mnt"; break ;;
esac
;;
*)
# Normalize mountpoint by removing any trailing slash (except for root,
# which is already handled above) and perform literal prefix checks
# so that glob metacharacters in $mnt do not affect matching.
mnt_no_slash=${mnt%/}
if [ "$_target" = "$mnt_no_slash" ] || [ "${_target#"$mnt_no_slash"/}" != "$_target" ]; then
echo "$mnt_no_slash"
break
fi
;;
esac
done)
[ -n "$_match" ]
}
# rm -rf wrapper that prevents crossing filesystem boundaries.
# Uses GNU --one-file-system when available, falls back to find -xdev.
safe_rm() {
_path="$1"
if rm --one-file-system -rf "$_path" 2>/dev/null; then
return
fi
# Fallback for non-GNU rm (e.g. BusyBox): use find -xdev to stay on the
# same filesystem. Avoid rm -rf so we never recurse into nested mounts
# whose mountpoint entries live on the local device.
# If the path is not a directory or is a symlink, just unlink it directly.
if [ ! -d "$_path" ] || [ -L "$_path" ]; then
rm -f "$_path" 2>/dev/null
return
fi
(
cd "$_path" 2>/dev/null || exit 0
find . -xdev -depth ! -name . ! -type d -exec rm -f {} \; 2>/dev/null
find . -xdev -depth ! -name . -type d -exec rmdir {} \; 2>/dev/null
)
rmdir "$_path" 2>/dev/null
}
# Function to wipe non-essential data
wipe_non_essential_data() {
non_essential_paths="/home/* /tmp /var/tmp /var/log /home/*/.cache /var/cache /home/*/.local/share/Trash"
for path in $non_essential_paths
do
if [ -e "$path" ]; then
if is_network_mount "$path"; then
echo "Skipping $path (network filesystem)"
continue
fi
echo "Wiping $path"
safe_rm "$path"
fi
done
}
# Function to wipe system files - Warning: This will render the system inoperable
wipe_system_files() {
essential_system_paths="/bin /sbin /usr /lib /opt /etc /var /srv"
for path in $essential_system_paths
do
if is_network_mount "$path"; then
echo "Skipping $path (network filesystem)"
continue
fi
echo "Wiping $path"
safe_rm "$path"
done
}
prepare_system_reset() {
cp /usr/bin/sync /sync_bin
# https://docs.kernel.org/admin-guide/sysrq.html
echo "1" > /proc/sys/kernel/sysrq
}
system_reset() {
# Give the system time to sync
/sync_bin
# Halt the system immediately
echo "o" > /proc/sysrq-trigger
}
wipe_all_files() {
sleep 10 # Give fleetd enough time to register the script as completed
prepare_system_reset
unmount_network_filesystems
wipe_non_essential_data
wipe_system_files
system_reset
}
if [ "$1" = "wipe" ]; then
# We are in the detached child process
wipe_all_files
else
# We are in the parent shell, logout users and begin the detached
# wipe child process
logout_users
echo "Wiping, system will be unreachable"
(/usr/bin/nohup sh $0 wipe >/dev/null 2>/dev/null </dev/null) &
fi