1*96f4c251SChristian Brauner // SPDX-License-Identifier: GPL-2.0 2*96f4c251SChristian Brauner /* Copyright (c) 2026 Christian Brauner <brauner@kernel.org> */ 3*96f4c251SChristian Brauner 4*96f4c251SChristian Brauner /* 5*96f4c251SChristian Brauner * Test BPF LSM block device integrity hooks with dm-verity. 6*96f4c251SChristian Brauner * 7*96f4c251SChristian Brauner * Creates a dm-verity device over loopback, which triggers 8*96f4c251SChristian Brauner * security_bdev_setintegrity() during verity_preresume(). 9*96f4c251SChristian Brauner * Verifies that the BPF program correctly tracks the integrity 10*96f4c251SChristian Brauner * metadata in its hashmap. 11*96f4c251SChristian Brauner */ 12*96f4c251SChristian Brauner 13*96f4c251SChristian Brauner #define _GNU_SOURCE 14*96f4c251SChristian Brauner #include <test_progs.h> 15*96f4c251SChristian Brauner #include <fcntl.h> 16*96f4c251SChristian Brauner #include <stdio.h> 17*96f4c251SChristian Brauner #include <stdlib.h> 18*96f4c251SChristian Brauner #include <string.h> 19*96f4c251SChristian Brauner #include <sys/stat.h> 20*96f4c251SChristian Brauner #include <sys/types.h> 21*96f4c251SChristian Brauner #include <unistd.h> 22*96f4c251SChristian Brauner #include "lsm_bdev.skel.h" 23*96f4c251SChristian Brauner 24*96f4c251SChristian Brauner /* Must match the definition in progs/lsm_bdev.c. */ 25*96f4c251SChristian Brauner struct verity_info { 26*96f4c251SChristian Brauner __u8 has_roothash; 27*96f4c251SChristian Brauner __u8 sig_valid; 28*96f4c251SChristian Brauner __u32 setintegrity_cnt; 29*96f4c251SChristian Brauner }; 30*96f4c251SChristian Brauner 31*96f4c251SChristian Brauner #define DATA_SIZE_MB 8 32*96f4c251SChristian Brauner #define HASH_SIZE_MB 1 33*96f4c251SChristian Brauner #define DM_NAME "bpf_test_verity" 34*96f4c251SChristian Brauner #define DM_DEV_PATH "/dev/mapper/" DM_NAME 35*96f4c251SChristian Brauner 36*96f4c251SChristian Brauner /* Run a command and optionally capture the first line of stdout. */ 37*96f4c251SChristian Brauner static int run_cmd(const char *cmd, char *out, size_t out_sz) 38*96f4c251SChristian Brauner { 39*96f4c251SChristian Brauner FILE *fp; 40*96f4c251SChristian Brauner int ret; 41*96f4c251SChristian Brauner 42*96f4c251SChristian Brauner fp = popen(cmd, "r"); 43*96f4c251SChristian Brauner if (!fp) 44*96f4c251SChristian Brauner return -1; 45*96f4c251SChristian Brauner 46*96f4c251SChristian Brauner if (out && out_sz > 0) { 47*96f4c251SChristian Brauner if (!fgets(out, out_sz, fp)) 48*96f4c251SChristian Brauner out[0] = '\0'; 49*96f4c251SChristian Brauner /* strip trailing newline */ 50*96f4c251SChristian Brauner out[strcspn(out, "\n")] = '\0'; 51*96f4c251SChristian Brauner } 52*96f4c251SChristian Brauner 53*96f4c251SChristian Brauner ret = pclose(fp); 54*96f4c251SChristian Brauner return WIFEXITED(ret) ? WEXITSTATUS(ret) : -1; 55*96f4c251SChristian Brauner } 56*96f4c251SChristian Brauner 57*96f4c251SChristian Brauner static bool has_prerequisites(void) 58*96f4c251SChristian Brauner { 59*96f4c251SChristian Brauner if (getuid() != 0) { 60*96f4c251SChristian Brauner printf("SKIP: must be root\n"); 61*96f4c251SChristian Brauner return false; 62*96f4c251SChristian Brauner } 63*96f4c251SChristian Brauner 64*96f4c251SChristian Brauner if (run_cmd("modprobe loop 2>/dev/null", NULL, 0) && 65*96f4c251SChristian Brauner run_cmd("ls /dev/loop-control 2>/dev/null", NULL, 0)) { 66*96f4c251SChristian Brauner printf("SKIP: no loop device support\n"); 67*96f4c251SChristian Brauner return false; 68*96f4c251SChristian Brauner } 69*96f4c251SChristian Brauner 70*96f4c251SChristian Brauner if (run_cmd("modprobe dm-verity 2>/dev/null", NULL, 0) && 71*96f4c251SChristian Brauner run_cmd("dmsetup targets 2>/dev/null | grep -q verity", NULL, 0)) { 72*96f4c251SChristian Brauner printf("SKIP: dm-verity module not available\n"); 73*96f4c251SChristian Brauner return false; 74*96f4c251SChristian Brauner } 75*96f4c251SChristian Brauner 76*96f4c251SChristian Brauner if (run_cmd("which veritysetup >/dev/null 2>&1", NULL, 0)) { 77*96f4c251SChristian Brauner printf("SKIP: veritysetup not found\n"); 78*96f4c251SChristian Brauner return false; 79*96f4c251SChristian Brauner } 80*96f4c251SChristian Brauner 81*96f4c251SChristian Brauner return true; 82*96f4c251SChristian Brauner } 83*96f4c251SChristian Brauner 84*96f4c251SChristian Brauner void test_lsm_bdev(void) 85*96f4c251SChristian Brauner { 86*96f4c251SChristian Brauner char data_img[] = "/tmp/bpf_verity_data_XXXXXX"; 87*96f4c251SChristian Brauner char hash_img[] = "/tmp/bpf_verity_hash_XXXXXX"; 88*96f4c251SChristian Brauner char data_loop[64] = {}; 89*96f4c251SChristian Brauner char hash_loop[64] = {}; 90*96f4c251SChristian Brauner char roothash[256] = {}; 91*96f4c251SChristian Brauner char cmd[512]; 92*96f4c251SChristian Brauner int data_fd = -1, hash_fd = -1; 93*96f4c251SChristian Brauner struct lsm_bdev *skel = NULL; 94*96f4c251SChristian Brauner struct verity_info val; 95*96f4c251SChristian Brauner struct stat st; 96*96f4c251SChristian Brauner __u32 dev_key; 97*96f4c251SChristian Brauner int err; 98*96f4c251SChristian Brauner 99*96f4c251SChristian Brauner if (!has_prerequisites()) { 100*96f4c251SChristian Brauner test__skip(); 101*96f4c251SChristian Brauner return; 102*96f4c251SChristian Brauner } 103*96f4c251SChristian Brauner 104*96f4c251SChristian Brauner /* Clean up any stale device from a previous crashed run. */ 105*96f4c251SChristian Brauner snprintf(cmd, sizeof(cmd), "dmsetup remove %s 2>/dev/null", DM_NAME); 106*96f4c251SChristian Brauner run_cmd(cmd, NULL, 0); 107*96f4c251SChristian Brauner 108*96f4c251SChristian Brauner /* Create temporary image files. */ 109*96f4c251SChristian Brauner data_fd = mkstemp(data_img); 110*96f4c251SChristian Brauner if (!ASSERT_OK_FD(data_fd, "mkstemp data")) 111*96f4c251SChristian Brauner return; 112*96f4c251SChristian Brauner 113*96f4c251SChristian Brauner hash_fd = mkstemp(hash_img); 114*96f4c251SChristian Brauner if (!ASSERT_OK_FD(hash_fd, "mkstemp hash")) 115*96f4c251SChristian Brauner goto cleanup; 116*96f4c251SChristian Brauner 117*96f4c251SChristian Brauner if (!ASSERT_OK(ftruncate(data_fd, DATA_SIZE_MB * 1024 * 1024), 118*96f4c251SChristian Brauner "truncate data")) 119*96f4c251SChristian Brauner goto cleanup; 120*96f4c251SChristian Brauner 121*96f4c251SChristian Brauner if (!ASSERT_OK(ftruncate(hash_fd, HASH_SIZE_MB * 1024 * 1024), 122*96f4c251SChristian Brauner "truncate hash")) 123*96f4c251SChristian Brauner goto cleanup; 124*96f4c251SChristian Brauner 125*96f4c251SChristian Brauner close(data_fd); 126*96f4c251SChristian Brauner data_fd = -1; 127*96f4c251SChristian Brauner close(hash_fd); 128*96f4c251SChristian Brauner hash_fd = -1; 129*96f4c251SChristian Brauner 130*96f4c251SChristian Brauner /* Set up loop devices. */ 131*96f4c251SChristian Brauner snprintf(cmd, sizeof(cmd), 132*96f4c251SChristian Brauner "losetup --find --show %s 2>/dev/null", data_img); 133*96f4c251SChristian Brauner if (!ASSERT_OK(run_cmd(cmd, data_loop, sizeof(data_loop)), 134*96f4c251SChristian Brauner "losetup data")) 135*96f4c251SChristian Brauner goto teardown; 136*96f4c251SChristian Brauner 137*96f4c251SChristian Brauner snprintf(cmd, sizeof(cmd), 138*96f4c251SChristian Brauner "losetup --find --show %s 2>/dev/null", hash_img); 139*96f4c251SChristian Brauner if (!ASSERT_OK(run_cmd(cmd, hash_loop, sizeof(hash_loop)), 140*96f4c251SChristian Brauner "losetup hash")) 141*96f4c251SChristian Brauner goto teardown; 142*96f4c251SChristian Brauner 143*96f4c251SChristian Brauner /* Format the dm-verity device and capture the root hash. */ 144*96f4c251SChristian Brauner snprintf(cmd, sizeof(cmd), 145*96f4c251SChristian Brauner "veritysetup format %s %s 2>/dev/null | " 146*96f4c251SChristian Brauner "grep -i 'root hash' | awk '{print $NF}'", 147*96f4c251SChristian Brauner data_loop, hash_loop); 148*96f4c251SChristian Brauner if (!ASSERT_OK(run_cmd(cmd, roothash, sizeof(roothash)), 149*96f4c251SChristian Brauner "veritysetup format")) 150*96f4c251SChristian Brauner goto teardown; 151*96f4c251SChristian Brauner 152*96f4c251SChristian Brauner if (!ASSERT_GT((int)strlen(roothash), 0, "roothash not empty")) 153*96f4c251SChristian Brauner goto teardown; 154*96f4c251SChristian Brauner 155*96f4c251SChristian Brauner /* Load and attach BPF program before activating dm-verity. */ 156*96f4c251SChristian Brauner skel = lsm_bdev__open_and_load(); 157*96f4c251SChristian Brauner if (!ASSERT_OK_PTR(skel, "skel open_and_load")) 158*96f4c251SChristian Brauner goto teardown; 159*96f4c251SChristian Brauner 160*96f4c251SChristian Brauner err = lsm_bdev__attach(skel); 161*96f4c251SChristian Brauner if (!ASSERT_OK(err, "skel attach")) 162*96f4c251SChristian Brauner goto teardown; 163*96f4c251SChristian Brauner 164*96f4c251SChristian Brauner /* Activate dm-verity — triggers verity_preresume() hooks. */ 165*96f4c251SChristian Brauner snprintf(cmd, sizeof(cmd), 166*96f4c251SChristian Brauner "veritysetup open %s %s %s %s 2>/dev/null", 167*96f4c251SChristian Brauner data_loop, DM_NAME, hash_loop, roothash); 168*96f4c251SChristian Brauner if (!ASSERT_OK(run_cmd(cmd, NULL, 0), "veritysetup open")) 169*96f4c251SChristian Brauner goto teardown; 170*96f4c251SChristian Brauner 171*96f4c251SChristian Brauner /* Get the dm device's dev_t. */ 172*96f4c251SChristian Brauner if (!ASSERT_OK(stat(DM_DEV_PATH, &st), "stat dm dev")) 173*96f4c251SChristian Brauner goto remove_dm; 174*96f4c251SChristian Brauner 175*96f4c251SChristian Brauner dev_key = (__u32)st.st_rdev; 176*96f4c251SChristian Brauner 177*96f4c251SChristian Brauner /* Look up the device in the BPF map and verify. */ 178*96f4c251SChristian Brauner err = bpf_map__lookup_elem(skel->maps.verity_devices, 179*96f4c251SChristian Brauner &dev_key, sizeof(dev_key), 180*96f4c251SChristian Brauner &val, sizeof(val), 0); 181*96f4c251SChristian Brauner if (!ASSERT_OK(err, "map lookup")) 182*96f4c251SChristian Brauner goto remove_dm; 183*96f4c251SChristian Brauner 184*96f4c251SChristian Brauner ASSERT_EQ(val.has_roothash, 1, "has_roothash"); 185*96f4c251SChristian Brauner ASSERT_EQ(val.sig_valid, 0, "sig_valid (unsigned)"); 186*96f4c251SChristian Brauner /* 187*96f4c251SChristian Brauner * verity_preresume() always calls security_bdev_setintegrity() 188*96f4c251SChristian Brauner * for the roothash. The signature-validity call only happens 189*96f4c251SChristian Brauner * when CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG is enabled. 190*96f4c251SChristian Brauner */ 191*96f4c251SChristian Brauner ASSERT_GE(val.setintegrity_cnt, 1, "setintegrity_cnt min"); 192*96f4c251SChristian Brauner ASSERT_LE(val.setintegrity_cnt, 2, "setintegrity_cnt max"); 193*96f4c251SChristian Brauner 194*96f4c251SChristian Brauner /* Verify that the alloc hook fired at least once. */ 195*96f4c251SChristian Brauner ASSERT_GT(skel->bss->alloc_count, 0, "alloc_count"); 196*96f4c251SChristian Brauner 197*96f4c251SChristian Brauner remove_dm: 198*96f4c251SChristian Brauner snprintf(cmd, sizeof(cmd), "dmsetup remove %s 2>/dev/null", DM_NAME); 199*96f4c251SChristian Brauner run_cmd(cmd, NULL, 0); 200*96f4c251SChristian Brauner 201*96f4c251SChristian Brauner teardown: 202*96f4c251SChristian Brauner if (data_loop[0]) { 203*96f4c251SChristian Brauner snprintf(cmd, sizeof(cmd), "losetup -d %s 2>/dev/null", 204*96f4c251SChristian Brauner data_loop); 205*96f4c251SChristian Brauner run_cmd(cmd, NULL, 0); 206*96f4c251SChristian Brauner } 207*96f4c251SChristian Brauner if (hash_loop[0]) { 208*96f4c251SChristian Brauner snprintf(cmd, sizeof(cmd), "losetup -d %s 2>/dev/null", 209*96f4c251SChristian Brauner hash_loop); 210*96f4c251SChristian Brauner run_cmd(cmd, NULL, 0); 211*96f4c251SChristian Brauner } 212*96f4c251SChristian Brauner 213*96f4c251SChristian Brauner cleanup: 214*96f4c251SChristian Brauner lsm_bdev__destroy(skel); 215*96f4c251SChristian Brauner if (data_fd >= 0) 216*96f4c251SChristian Brauner close(data_fd); 217*96f4c251SChristian Brauner if (hash_fd >= 0) 218*96f4c251SChristian Brauner close(hash_fd); 219*96f4c251SChristian Brauner unlink(data_img); 220*96f4c251SChristian Brauner unlink(hash_img); 221*96f4c251SChristian Brauner } 222