Hi, I have attached information for three different bugs. Thank you for an awesome OS.
Cheers, Claes M Nyberg
OpenBSD fail to validate file handle for symbolic links Reproduce: Create a symbolik link in an exported directory to .. ln -s .. updir Then do a listing in the directory to get the filehandle for the symbolic link Access the symbolic link directly using the file handle with READDIRPLUS to cause a panic: ffs_read: short symlink Create a symbolik link in an exported directory ln -s /etc/passwd pwd Then do a listing in the directory to get the filehandle for the symbolic link Access the symbolic link directly using the file handle with READDIRPLUS to cause a panic: ffs_read: short symlink
OpenBSD suffers from lack of validation of the length variable when processing the NFSv3 LOOKUP Call, resulting in a pool(9) (heap) buffer overflow. Verified Releases: 7.5 amd64, 7.2 amd64, 6.0 amd64, 5.6 amd64, 5.0 amd64 While processing the NFSv3 LOOKUP Call, the function nfs_namei, located at sys/nfs/nfs_subs.c:1178 is called. In this call we control the variable len, which is the LOOKUP Call length variable, and the fromcp variable which points to the name in the LOOKUP Call. The result is that we control how many bytes (32bit) that are copied from the name, i.e. fromcp into the pool buffer tocp returned from pool_get() on line 1191. The pool is initiated with MAXPATHLEN buffers, which is 1024 on OpenBSD 7.2. sys/nfs/nfs_subs.c 1178 int 1179 nfs_namei(struct nameidata *ndp, fhandle_t *fhp, int len, 1180 struct nfssvc_sock *slp, struct mbuf *nam, struct mbuf **mdp, 1181 caddr_t *dposp, struct vnode **retdirp, struct proc *p) 1182 { 1183 int i, rem; 1184 struct mbuf *md; 1185 char *fromcp, *tocp; 1186 struct vnode *dp; 1187 int error, rdonly; 1188 struct componentname *cnp = &ndp->ni_cnd; 1189 1190 *retdirp = NULL; 1191 cnp->cn_pnbuf = pool_get(&namei_pool, PR_WAITOK); 1192 /* 1193 * Copy the name from the mbuf list to ndp->ni_pnbuf 1194 * and set the various ndp fields appropriately. 1195 */ 1196 fromcp = *dposp; 1197 tocp = cnp->cn_pnbuf; 1198 md = *mdp; 1199 rem = mtod(md, caddr_t) + md->m_len - fromcp; 1200 for (i = 0; i < len; i++) { 1201 while (rem == 0) { 1202 md = md->m_next; 1203 if (md == NULL) { 1204 error = EBADRPC; 1205 goto out; 1206 } 1207 fromcp = mtod(md, caddr_t); 1208 rem = md->m_len; 1209 } 1210 if (*fromcp == '\0' || *fromcp == '/') { 1211 error = EACCES; 1212 goto out; 1213 } 1214 *tocp++ = *fromcp++; 1215 rem--; 1216 } 1217 *tocp = '\0'; 1218 *mdp = md; 1219 *dposp = fromcp; 1220 len = nfsm_padlen(len); 1221 if (len > 0) { 1222 if (rem >= len) 1223 *dposp += len; 1224 else if ((error = nfs_adv(mdp, dposp, len, rem)) != 0) 1225 goto out; 1226 } Further on, pool buffers uses canary values (which was using a static value up to version 5.6, check out subr_poison.c) in between which causes an 8 byte overflow to raise a panic while the pool is traversed in pool_p_free or pool_do_get in kern/subr_pool.c, causing this bug to require a leak of the canary before even attempting to exploit in later versions. An interesting observation though, is that pool buffer need the PR_ZERO flag to be set when calling pool_get() to zero out the buffer. if (ISSET(flags, PR_ZERO)) memset(v, 0, pp->pr_size); This is not set by the NFS server, making pool buffers available for heap spraying by flooding with LOOKUP Calls that fill the buffers with shellcode to the maximum of 1024 bytes, increasing the probability to hit valid code in a jmp/call instruction.
OpenBSD suffers from lack of validation of the verifier length variable, resulting in a dereference of an invalid address Verified Releases: 7.5 amd64, 7.2 amd64 While processing an NFSv3 RPC reply, the verifier length is used to advance in the response buffer. Providing a value bigger than zero result in the line commented with "Should not happen" being executed. /usr/src/sys/nfs/nfs_socket.c 846 */ 847 int 848 nfs_request(struct vnode *vp, int procnum, struct nfsm_info *infop) 849 { [...] 980 /* 981 * Since we only support RPCAUTH_UNIX atm we step over the 982 * reply verifer type, and in the (error) case that there really 983 * is any data in it, we advance over it. 984 */ 985 tl++; /* Step over verifer type */ 986 i = fxdr_unsigned(int32_t, *tl); 987 if (i > 0) 988 nfsm_adv(nfsm_rndup(i)); /* Should not happen */ The macros for nfsm_adv(nfsm_rndup(i)); are defined as follows: #define nfsm_rndup(a) (((a)+3)&(~0x3)) #define nfsm_adv(s) { \ t1 = mtod(info.nmi_md, caddr_t) + info.nmi_md->m_len - \ info.nmi_dpos; \ if (t1 >= (s)) { \ info.nmi_dpos += (s); \ } else if ((t1 = nfs_adv(&info.nmi_md, &info.nmi_dpos, \ (s), t1)) != 0) { \ error = t1; \ m_freem(info.nmi_mrep); \ goto nfsmout; \ } \ } If the verifier length value is large enough, nfs_adv() will fail because a short mbuf chain, resulting the the mbuf being free'd (m_freem(info.nmi_mrep)). When the mbuf is free'd by the m_free function, it is placed in a pool for available mbuf's and the next mbuf and next packet pointer inside the mbuf is converted to magic values used by the pool datastructure. When an mbuf is double-free'd, the next pointer which now holds the pool magic value is referenced, resulting in a kernel panic since it is not a valid address. There are multiple RPC reply's affected by this nfs_request, using for example nfs_mkdir(), the mbuf is freed again at the end of the function: /usr/src/sys/nfs/nfs_vnops.c 1883 /* 1884 * nfs make dir call 1885 */ 1886 int 1887 nfs_mkdir(void *v) ... 1932 m_freem(info.nmi_mrep); The overwritten pointer to the next mbuf is then dereferenced when the chain is traversed inside m_freem(), causing a kernel panic.