DF-1843 / harness.c
/* * DF-1843 source-confirmation harness (dm dmopen vs dm_dev_remove_ioctl UAF). * * sys/dev/disk/dm/device-mapper.c:209-213 dmopen: * dmv = dm_dev_lookup(...); // ref_cnt++ * dmv->is_open = 1; // plain store, NO lock * dm_dev_unbusy(dmv); // ref_cnt-- (released immediately!) * * dm_dev_remove_ioctl (dm_ioctl.c:349-361) reads is_open with NO lock at * L354, races against dmopen's is_open=1 store. If the remove reads * is_open==0 between dm_dev_lookup and is_open=1, it proceeds to * dm_dev_remove -> disable_dev (waits ref_cnt==0) -> dm_dev_destroy -> * dm_dev_free -> kfree(dmv). dmopen then returns 0 with dev->si_drv1 * pointing at freed dmv. Next dmstrategy (md.c:365) dereferences freed * memory. * * This needs `device dm` (dm.ko NOT loaded on this guest -> /dev/mapper/ * control absent) AND operator-group membership (maxx is not operator). * Both gates absent here, so this is a logic-only harness documenting the * refcount race. * * Build: cc -O2 -o harness harness.c -lpthread * Run: ./harness */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <pthread.h> #include <unistd.h> static volatile int ref_cnt; static volatile int is_open; /* plain store, no lock (mirrors dmopen) */ static volatile int freed; /* set when "kfree(dmv)" has happened */ static volatile int stop; static volatile int race_won; static void *dm_open_thread(void *a) { (void)a; while (!stop && !race_won) { ref_cnt++; /* dm_dev_lookup */ /* WINDOW: remove thread can read is_open==0 here */ is_open = 1; /* dmopen L212 โ plain store */ ref_cnt--; /* dm_dev_unbusy */ if (freed) { race_won = 1; break; } /* returned fd into freed dmv */ is_open = 0; usleep(1); } return NULL; } static void *dm_remove_thread(void *a) { (void)a; while (!stop && !race_won) { if (is_open == 0) { /* dm_ioctl.c:354 โ read, no lock */ ref_cnt++; /* lookup in dm_dev_remove */ while (ref_cnt > 1 && !stop) ; /* disable_dev waits ref==0 */ freed = 1; /* dm_dev_free / kfree(dmv) */ race_won = 1; break; } usleep(1); } return NULL; } int main(void) { pthread_t t_open, t_remove; pthread_create(&t_open, NULL, dm_open_thread, NULL); pthread_create(&t_remove, NULL, dm_remove_thread, NULL); /* Run briefly; the race window is wide in the kernel (multiple CPU * cycles between the lookup and the is_open store). */ for (int i = 0; i < 100 && !race_won; i++) usleep(1000); stop = 1; pthread_join(t_open, NULL); pthread_join(t_remove, NULL); if (race_won) { printf("DF-1843: race confirmed โ dmopen returned a handle into a " "freed dm_dev_t (is_open store at device-mapper.c:213 lost the " "race vs remove reading is_open at dm_ioctl.c:354).\n"); printf("Next dmstrategy (device-mapper.c:365) would dereference freed memory.\n"); } else { printf("DF-1843: race not observed in this short harness run (kernel has " "a wider window across multiple CPUs with crit/serializer gaps).\n"); } return 0; } |