Fix double-counted Linux disk space from bind-mounted filesystems (#44969)

**Related issue:** Resolves #43091

# Checklist for submitter

- [x] 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.

- [x] 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, and untrusted
data interpolated into shell scripts/commands is validated against shell
metacharacters.

## Testing

- [ ] Added/updated automated tests

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

Was able to reproduce by enrolling a Ubuntu 25.10 host and mounting /
onto a different path like this:

```bash
sudo mkdir -p /tmp/snap.rootfs_TESTING
sudo mount --bind / /tmp/snap.rootfs_TESTING
```

Then, refetched vitals and saw the total disk space doubled:
<img width="211" height="108" alt="Screenshot 2026-05-07 at 9 40 12 PM"
src="https://github.com/user-attachments/assets/81cad4af-f146-4ea9-84e3-ee56eb426d8a"
/>

With the fix applied on this branch, refetched again and saw the correct
total disk space:

<img width="251" height="118" alt="Screenshot 2026-05-07 at 9 31 15 PM"
src="https://github.com/user-attachments/assets/cbb6f91b-320a-4d48-8583-f5fdf5a4fcd9"
/>

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Bug Fixes
- Resolved inaccurate total disk space reporting on Linux systems where
the same filesystem is bind-mounted at multiple paths.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Nico
2026-05-15 08:48:17 +02:00
committed by GitHub
parent 854379c1de
commit c19df6d2c7
4 changed files with 22 additions and 16 deletions
@@ -0,0 +1 @@
* Fixed Linux total disk space being double-counted when a filesystem was bind-mounted at multiple paths (e.g. snap-confine's `/tmp/snap.rootfs_*`).
@@ -175,7 +175,7 @@ SELECT (blocks_available * 100 / blocks) AS percent_disk_space_available,
SELECT (blocks_available * 100 / blocks) AS percent_disk_space_available,
round((blocks_available * blocks_size * 10e-10),2) AS gigs_disk_space_available,
round((blocks * blocks_size * 10e-10),2) AS gigs_total_disk_space,
(SELECT round(SUM(blocks * blocks_size) * 10e-10, 2) FROM mounts WHERE
(SELECT round(SUM(per_device_size) * 10e-10, 2) FROM (SELECT MAX(blocks * blocks_size) AS per_device_size FROM mounts WHERE
-- exclude mounts with no space
blocks > 0
AND blocks_size > 0
@@ -223,7 +223,7 @@ OR device LIKE '/dev/nvme%'
OR device LIKE '/dev/mapper%'
OR device LIKE '/dev/md%'
OR device LIKE '/dev/dm-%'
)) AS gigs_all_disk_space
) GROUP BY device)) AS gigs_all_disk_space
FROM mounts WHERE path = '/' LIMIT 1;
```
+17 -13
View File
@@ -113,7 +113,8 @@ spec:
SELECT (blocks_available * 100 / blocks) AS percent_disk_space_available,
round((blocks_available * blocks_size * 10e-10),2) AS gigs_disk_space_available,
round((blocks * blocks_size * 10e-10),2) AS gigs_total_disk_space,
(SELECT round(SUM(blocks * blocks_size) * 10e-10, 2) FROM mounts WHERE
(SELECT round(SUM(per_device_size) * 10e-10, 2) FROM (
SELECT MAX(blocks * blocks_size) AS per_device_size FROM mounts WHERE
-- exclude mounts with no space
blocks > 0
AND blocks_size > 0
@@ -121,7 +122,7 @@ spec:
-- exclude external storage
AND path NOT LIKE '/media%' AND path NOT LIKE '/mnt%'
-- exclude device drivers
AND path NOT LIKE '/dev%'
AND path NOT LIKE '/dev%'
-- exclude kernel-related mounts
AND path NOT LIKE '/proc%'
@@ -132,7 +133,7 @@ spec:
AND path NOT LIKE '/var/run%'
-- exclude boot files
AND path NOT LIKE '/boot%'
AND path NOT LIKE '/boot%'
-- exclude snap packages
AND path NOT LIKE '/snap%' AND path NOT LIKE '/var/snap%'
@@ -142,24 +143,27 @@ spec:
AND path NOT LIKE '/var/lib/containers%'
AND type IN (
'ext4',
'ext3',
'ext2',
'xfs',
'btrfs',
'ntfs',
'ext4',
'ext3',
'ext2',
'xfs',
'btrfs',
'ntfs',
'vfat',
'fuseblk', --seen on NTFS and exFAT volumes mounted via FUSE
'zfs' --also valid storage
)
AND (
device LIKE '/dev/sd%'
OR device LIKE '/dev/hd%'
OR device LIKE '/dev/vd%'
OR device LIKE '/dev/nvme%'
device LIKE '/dev/sd%'
OR device LIKE '/dev/hd%'
OR device LIKE '/dev/vd%'
OR device LIKE '/dev/nvme%'
OR device LIKE '/dev/mapper%'
OR device LIKE '/dev/md%'
OR device LIKE '/dev/dm-%'
)
-- group by device so a filesystem bind-mounted at multiple paths is only counted once
GROUP BY device
)) AS gigs_all_disk_space
FROM mounts WHERE path = '/' LIMIT 1;
bash: df -P / | awk 'NR==2 { blocks=$2; blocks_available=$4; blocks_size=$2/$3 } END { printf "percent_disk_space_available=%.2f\n", (blocks_available * 100 / blocks); printf "gigs_disk_space_available=%.2f\n", (blocks_available * blocks_size * 10e-10); printf "gigs_total_disk_space=%.2f\n", (blocks * blocks_size * 10e-10) }'
+2 -1
View File
@@ -442,11 +442,12 @@ var hostDetailQueries = map[string]DetailQuery{
Platforms: append(fleet.HostLinuxOSs, "darwin", "windows"), // not chrome
},
"disk_space_unix": {
// GROUP BY device collapses bind mounts so a filesystem mounted at multiple paths is counted once.
Query: fmt.Sprintf(`
SELECT (blocks_available * 100 / blocks) AS percent_disk_space_available,
round((blocks_available * blocks_size * 10e-10),2) AS gigs_disk_space_available,
round((blocks * blocks_size * 10e-10),2) AS gigs_total_disk_space,
(SELECT round(SUM(blocks * blocks_size) * 10e-10, 2) FROM mounts %s) AS gigs_all_disk_space
(SELECT round(SUM(per_device_size) * 10e-10, 2) FROM (SELECT MAX(blocks * blocks_size) AS per_device_size FROM mounts %s GROUP BY device)) AS gigs_all_disk_space
FROM mounts WHERE path = '/' LIMIT 1;`, linuxGigsAllDiskSpaceSubQueryConditions),
Platforms: fleet.HostLinuxOSs,
DirectIngestFunc: directIngestDiskSpace,