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.