Technology is evil.


A specially crafted HFS+ filesystem in a DMG image can cause the do_hfs_truncate() function to panic the kernel (denial of service), when attempting to remove a file from the mounted filesystem. This issue can't lead to arbitrary code execution, although there's a significant risk of local HFS+ filesystems corruption if the issue is triggered while write operations are being done.

Affected versions

This issue has been verified on Mac OS X 10.4.8 (8L2127) x86. Previous versions might be affected.

Proof of concept, exploit or instructions to reproduce

The provided proof of concept will cause a so-called kernel panic. It's recommended to test it on a machine which doesn't hold any important data, as well as not performing intensive write operations over any local HFS+ filesystem due to potential corruption (observed with Filevault enabled machine).

$ gunzip MOAB-13-01-2007.dmg.gz
$ hdiutil attach MOAB-13-01-2007.dmg
$ rm -rf /Volumes/DaringPhuxball/*

Note: Safari will mount DMG files automatically after download, unless the 'Open safe files' option is disabled in your preferences (which is strongly recommended), allowing remote exploitation of this issue. This issue can't be triggered by simply mounting the image, and requires the user to perform an operation that leads to do_hfs_truncate(), such as removing files).

Debugging information

The following information corresponds to an up-to-date Mac OS X 10.4.8 (8L2127) system, and the provided proof of concept DMG image.

(gdb) source /Volumes/KernelDebugKit/kgmacros 
Loading Kernel GDB Macros package.  Type "help kgm" for more info.
(gdb) paniclog 
panic(cpu 0 caller 0x002FB205): hfs_truncate: invoked on non-UBC object?!
Backtrace, Format - Frame : Return Address (4 potential args on stack) 
0x14033a68 : 0x128d1f (0x3c9540 0x14033a8c 0x131df4 0x0) 
0x14033aa8 : 0x2fb205 (0x3e5264 0xfff03000 0xffffffff 0x2b9c204) 
0x14033b88 : 0x2fb6a6 (0x10 0x1 0x14033f44 0x199c36) 
0x14033bd8 : 0x30ce30 (0x2fb5084 0x0 0x0 0x10) 
0x14033d08 : 0x30ebb2 (0x0 0x0 0x2 0x456f79b3) 
0x14033d48 : 0x1e54fe (0x14033d64 0x0 0x0 0x0) 
0x14033d98 : 0x1d6755 (0x2ec04a4 0x2ed0dec 0x14033f08 0x0) 
0x14033f68 : 0x378337 (0x0 0x25cc188 0x25cc1cc 0x0) 
0x14033fc8 : 0x19acae (0x25d0594 0x19a7f5 0x8 0x207)
No mapping exists for frame pointer
Backtrace terminated-invalid frame pointer 0xbffffa38

Kernel version:
Darwin Kernel Version 8.8.1: Mon Sep 25 19:42:00 PDT 2006;

(gdb) back
#0  Debugger (message=0x3c9540 "panic")
#1  0x00128d1f in panic (str=0x3e5264 "hfs_truncate: invoked on non-UBC object?!")
#2  0x002fb205 in do_hfs_truncate (vp=0x2fb5084, length=0, flags=16, skipsetsize=1,
#3  0x002fb6a6 in hfs_truncate (vp=0x2fb5084, length=0, flags=16, skipsetsize=1,
#4  0x0030ce30 in hfs_removefile (dvp=0x2ec04a4, vp=0x2ed0dec, cnp=0x14033f08, flags=0,
#5  0x0030ebb2 in hfs_vnop_remove (ap=0x14033d64)
#6  0x001e54fe in VNOP_REMOVE (dvp=0x2ec04a4, vp=0x2ed0dec, cnp=0x14033f08, flags=0,
#7  0x001d6755 in _unlink (p=0x0, uap=0x0, retval=0x0, nodelbusy=0)
#8  0x00378337 in unix_syscall (state=0x25d0594)
#9  0x0019acae in lo_unix_scall ()

(gdb) select-frame 3
(gdb) i locals
cp = (struct cnode *) 0x2ec8284
fp = (struct filefork *) 0x2faf8f0
p = (struct proc *) 0x2c391f4
cred = (kauth_cred_t) 0x2b9c204
retval = 0
actualBytesAdded = 4335179952
filebytes = 0
old_filesize = 17437937757178560512
blksize = 45728260
hfsmp = (struct hfsmount *) 0x2be8404
lockflags = 0
context = (vfs_context_t) 0x0

An attempt to remove a file within the rogue HFS+ filesystem contained in the DMG image leads to hfs_removefile(), and finally do_hfs_truncate() which causes the kernel panic (due to an invalid vnode structure containing a null vu_ubcinfo and VLNK type, instead of the expected VREG). The following source code comes from the current xnu-792 sources:

---------- bsd/hfs/hfs_readwrite.c
1795 static int
1796 do_hfs_truncate(struct vnode *vp, off_t length, int flags, int skipsetsize,
                     vfs_context_t context)
1797 {
1798         register struct cnode *cp = VTOC(vp);
1799         struct filefork *fp = VTOF(vp);
1800         struct proc *p = vfs_context_proc(context);;
1801         kauth_cred_t cred = vfs_context_ucred(context);
1802         int retval;
1803         off_t bytesToAdd;
1804         off_t actualBytesAdded;
1805         off_t filebytes;
1806         u_long fileblocks;
1807         int blksize;
1808         struct hfsmount *hfsmp;
1809         int lockflags;
1811         blksize = VTOVCB(vp)->blockSize;
1812         fileblocks = fp->ff_blocks;
1813         filebytes = (off_t)fileblocks * (off_t)blksize;
1921                 if (!(flags & IO_NOZEROFILL)) {
1922                         if (UBCINFOEXISTS(vp) && retval == E_NONE) {
1923                                 struct rl_entry *invalid_range;
1924                                 off_t zero_limit;
1965                         } else {
1966                                 panic("hfs_truncate: invoked on non-UBC object?!");
1967                         };
1968                 }
------------- bsd/hfs/hfs_readwrite.c
------------- bsd/kern/ubc_subr.c
935 int 
936 UBCINFOEXISTS(struct vnode * vp)
937 {
938    return((vp) && ((vp)->v_type == VREG) && ((vp)->v_ubcinfo != UBC_INFO_NULL));
939 }
------------- bsd/kern/ubc_subr.c

The vnode structure contains (note that it's a resource fork file and vnode type is VLNK, that is, a symlink):

(gdb) p vp->v_name
$5 = 0x261c714 'C' <repeats 28 times>, "/..namedfork/rsrc"
(gdb) p (vp)->v_type
$11 = VLNK            (symlink) != VREG
(gdb) p (vp)->v_un   
$12 = {
  vu_mountedhere = 0x0, 
  vu_socket = 0x0, 
  vu_specinfo = 0x0, 
  vu_fifoinfo = 0x0, 
  vu_ubcinfo = 0x0           <---------

Other HFS+ issues might be covered in future MoAB releases.


Workaround or temporary solution

Don't attempt to mount untrusted DMG files, disable Safari 'Open safe files' in it's preferences dialog.

length = fs->(...);
if (length > ...) is futile.