DF-0185 / acl_leak.c
/* DF-0185 — Latent uninitialized-stack leak via vacl_get_acl. * * sys/kern/kern_acl.c: vacl_get_acl() declares * struct acl inkernelacl; // NOT zeroed (line 92) * and after VOP_GETACL writes acl_cnt entries, copyout() copies the * ENTIRE struct acl (line 99) -- including entries [acl_cnt..ACL_MAX_ENTRIES] * which remain uninitialized kernel stack. * * LATENT: no in-tree filesystem overrides VOP_GETACL. vfs_default.c:89 * sets .vop_getacl = vop_eopnotsupp, which returns EOPNOTSUPP and the * `if (error == 0) copyout(...)` guard at kern_acl.c:98 prevents the * leak from firing. The bug becomes live the moment an ACL-capable * filesystem is added. * * This PoC simply demonstrates the latent state: acl_get_file returns * EOPNOTSUPP on every filesystem on this kernel (no FS implements * VOP_GETACL). The fix is to zero `inkernelacl` before the VOP call. */ #include <sys/types.h> #include <sys/acl.h> #include <sys/syscall.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <unistd.h> int main(void) { struct acl a; int rc, e; memset(&a, 0, sizeof a); /* sys___acl_get_file(path, type, aclp) -- the direct syscall, which is what acl_get_file() in libacl wraps. */ rc = syscall(SYS___acl_get_file, "/etc/passwd", ACL_TYPE_ACCESS, &a); e = errno; printf("DF-0185: __acl_get_file(/etc/passwd, ACL_TYPE_ACCESS) = %d errno=%d (%s)\n", rc, e, strerror(e)); rc = syscall(SYS___acl_get_file, "/tmp", ACL_TYPE_DEFAULT, &a); e = errno; printf("DF-0185: __acl_get_file(/tmp, ACL_TYPE_DEFAULT) = %d errno=%d (%s)\n", rc, e, strerror(e)); if (e == EOPNOTSUPP) { printf("DF-0185: VOP_GETACL returned EOPNOTSUPP (vfs_default.c:89) --\n" " copyout at kern_acl.c:99 NOT reached; leak is LATENT.\n" " Fix is still warranted (zero `inkernelacl` before the call).\n"); } else if (rc == 0) { printf("DF-0185: WARNING -- VOP_GETACL succeeded! Leak path is LIVE.\n"); printf(" struct acl returned: acl_cnt=%d\n", a.acl_cnt); } return 0; } |