DF-0865 / fix.diff
diff --git a/sys/vfs/hpfs/hpfs.h b/sys/vfs/hpfs/hpfs.h --- a/sys/vfs/hpfs/hpfs.h +++ b/sys/vfs/hpfs/hpfs.h @@ -141,6 +141,18 @@ lsn_t d_parent; lsn_t d_self; } dirblk_t; +/* + * DF-0865: Bounds-check a dep pointer against its parent bread buffer. + * The dep-walk loops in hpfs_validateparent (and siblings) advance dep by + * de_reclen (attacker-controlled u16 off disk) with no bound check, so a + * crafted dir block walks dep past the 2 KB (D_BSIZE) buffer. This macro + * must be evaluated BEFORE any field of dep is read so an OOB dep never + * gets dereferenced. + */ +#define HPFS_DE_INBOUNDS(bp, dep) \ + ((caddr_t)(dep) >= (caddr_t)(bp)->b_data && \ + (caddr_t)(dep) + sizeof(hpfsdirent_t) <= \ + (caddr_t)(bp)->b_data + D_BSIZE) /* * Allocation Block (ALBLK) diff --git a/sys/vfs/hpfs/hpfs_subr.c b/sys/vfs/hpfs/hpfs_subr.c --- a/sys/vfs/hpfs/hpfs_subr.c +++ b/sys/vfs/hpfs/hpfs_subr.c @@ -569,13 +569,20 @@ if (olsn) { dprintf(("[restore 0x%x] ", olsn)); - while(!(dep->de_flag & DE_END) ) { + while (HPFS_DE_INBOUNDS(bp, dep) && + dep->de_reclen >= sizeof(hpfsdirent_t) && + !(dep->de_flag & DE_END) ) { if((dep->de_flag & DE_DOWN) && (olsn == DE_DOWNLSN(dep))) break; dep = (hpfsdirent_t *)((caddr_t)dep + dep->de_reclen); } + if (!HPFS_DE_INBOUNDS(bp, dep)) { + kprintf("hpfs_validateparent: dep out of bounds\n"); + error = EINVAL; + goto failed; + } if((dep->de_flag & DE_DOWN) && (olsn == DE_DOWNLSN(dep))) { if (dep->de_flag & DE_END) goto blockdone; @@ -595,7 +602,9 @@ olsn = 0; - while(!(dep->de_flag & DE_END)) { + while (HPFS_DE_INBOUNDS(bp, dep) && + dep->de_reclen >= sizeof(hpfsdirent_t) && + !(dep->de_flag & DE_END)) { if(dep->de_flag & DE_DOWN) { lsn = DE_DOWNLSN(dep); level++; @@ -610,6 +619,11 @@ dep = (hpfsdirent_t *)((caddr_t)dep + dep->de_reclen); } + if (!HPFS_DE_INBOUNDS(bp, dep)) { + kprintf("hpfs_validateparent: dep out of bounds\n"); + error = EINVAL; + goto failed; + } if(dep->de_flag & DE_DOWN) { dprintf(("[enddive] ")); lsn = DE_DOWNLSN(dep); |