The byte_swap_sbin() function, one of the UFS byte swapping routines (this code isn't present in FreeBSD and it's Mac OS X XNU-specific; used for compatibility of filesystem streams between little and big-endian systems) is affected by a integer overflow vulnerability, leading to an exploitable denial of service condition.

Abuse of this issue for arbitrary code execution seems unlikely, please read the 'Debugging information' section for notes and related source code.

Affected versions

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

Proof of concept, exploit or instructions to reproduce

The provided proof of concept will cause a so-called kernel panic when trying to access an invalid memory address.

$ gunzip MOAB-11-01-2007.dmg.gz
$ hdiutil attach MOAB-11-01-2007.dmg

Note: Safari will do it automatically unless the 'Open safe files' option is disabled in your preferences (which is strongly recommended), allowing remote exploitation of this issue.

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) back
#0  Debugger (message=0x3c9540 "panic") at
#1  0x00128d1f in panic (str=0x3cf1f4)
#2  0x001a3135 in kernel_trap (state=0x1401b9c0)
#3  0x0019a8d4 in trap_from_kernel ()
#4  0x002e45a4 in byte_swap_sbin (sb=0x29e21000) at
#5  0x002e15c4 in ffs_mountfs (devvp=0x2ec8318, mp=0x2ea6d00, context=0x1401bf40) at
#6  0x002e2172 in ffs_mount (mp=0x2ea6d00, devvp=0x2ec8318, data=3221221904,
                   context=0x1401bf40) at 
#7  0x001e6147 in VFS_MOUNT (mp=0x2ea6d00, devvp=0x2ec8318, data=3221221904,
                  context=0x1401bf40) at /SourceCache/xnu/xnu-792.13.8/bsd/vfs/kpi_vfs.c:211
#8  0x001d394c in mount (p=0x2cf4bb8, uap=0x2ddfd48, retval=0x2ddfd8c) at
#9  0x00378337 in unix_syscall (state=0x25ce3fc) at
#10 0x0019acae in lo_unix_scall ()
Cannot access memory at address 0xbffff22c

(gdb) paniclog
panic(cpu 0 caller 0x001A3135): Unresolved kernel trap (CPU 0, Type 14=page fault),
CR0: 0x8001003b, CR2: 0x29e5e000, CR3: 0x00d72000, CR4: 0x000006e0
EAX: 0x29e5e000, EBX: 0x29e2135c, ECX: 0x00d20008, EDX: 0x0001e652
CR2: 0x29e5e000, EBP: 0x1401ba08, ESI: 0x00002000, EDI: 0x1401bf40
EFL: 0x00010293, EIP: 0x002e4119, CS:  0x00000004, DS:  0x25aa000c

Backtrace, Format - Frame : Return Address (4 potential args on stack) 
0x1401b858 : 0x128d1f (0x3c9540 0x1401b87c 0x131df4 0x0) 
0x1401b898 : 0x1a3135 (0x3cf1f4 0x0 0xe 0x3cea24) 
0x1401b9a8 : 0x19a8d4 (0x1401b9c0 0x0 0x1401b9f8 0x344fcc) 
0x1401ba08 : 0x2e45a4 (0x29e2135c 0xd20008 0x0 0x1) 
0x1401ba28 : 0x2e15c4 (0x29e21000 0x1 0x0 0x2000) 
0x1401baa8 : 0x2e2172 (0x2ec8318 0x2ea6d00 0x1401bf40 0x0) 
0x1401bae8 : 0x1e6147 (0x2ea6d00 0x2ec8318 0xbffff210 0x0) 
0x1401bb38 : 0x1d394c (0x2ea6d00 0x2ec8318 0xbffff210 0x0) 
0x1401bf68 : 0x378337 (0x2cf4bb8 0x2ddfd48 0x2ddfd8c 0x0) 
0x1401bfc8 : 0x19acae (0x25ce3fc 0x0 0x19d0b5 0x25ce3fc)
No mapping exists for frame pointer
Backtrace terminated-invalid frame pointer 0xbffff228
         kmod scan stopped due to missing kmod page: 00000000

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

(gdb) x/1x 0x29e5e000
0x29e5e000:     Cannot access memory at address 0x29e5e000
(gdb) x/1x 0x1401ba08
0x1401ba08:     0x1401ba28
(gdb) x/1x 0x1401ba28
0x1401ba28:     0x1401baa8
(gdb) x/1x 0x29e2135c
0x29e2135c:     0xffff0000
(gdb) x/4x 0x29e2135c
0x29e2135c:     0xffff0000      0xffff0100      0xffff0200      0xffff0300
(gdb) disas byte_swap_shorts
Dump of assembler code for function byte_swap_shorts:
0x002e410c <byte_swap_shorts+0>:        push   %ebp
0x002e410d <byte_swap_shorts+1>:        mov    %esp,%ebp
0x002e410f <byte_swap_shorts+3>:        mov    12(%ebp),%ecx
0x002e4112 <byte_swap_shorts+6>:        xor    %edx,%edx
0x002e4114 <byte_swap_shorts+8>:        mov    8(%ebp),%eax
0x002e4117 <byte_swap_shorts+11>:       jmp    0x2e4123 <byte_swap_shorts+23>
0x002e4119 <byte_swap_shorts+13>:       rolw   $0x8,(%eax)
0x002e411d <byte_swap_shorts+17>:       add    $0x1,%edx
0x002e4120 <byte_swap_shorts+20>:       add    $0x2,%eax
0x002e4123 <byte_swap_shorts+23>:       cmp    %ecx,%edx
0x002e4125 <byte_swap_shorts+25>:       jl     0x2e4119 <byte_swap_shorts+13>
0x002e4127 <byte_swap_shorts+27>:       pop    %ebp
0x002e4128 <byte_swap_shorts+28>:       ret    
End of assembler dump.

(gdb) x/1x 0x1401ba08+12
0x1401ba14:     0x00d20008
(gdb) x/1x 0x1401ba08+8 
0x1401ba10:     0x29e2135c
(gdb) x/4 0x29e2135c
0x29e2135c:     0xffff0000      0xffff0100      0xffff0200      0xffff0300

The fault happened inside the byte_swap_shorts() function (eip at 0x002e4119) which is called from byte_swap_sbin(), taking two parameters: a pointer to an array and a integer used to define how many times the for loop will iterate.

---------- bsd/ufs/ufs/ufs_byte_order.c
61 void
62 byte_swap_shorts(short *array, int count)
63 {
64         register int    i;
66         for (i = 0;  i < count;  i++)
67                 byte_swap_short(array[i]);
68 }

70 void
71 byte_swap_sbin(struct fs *sb)
72 {
73         u_int16_t *usptr;
74         unsigned long size;
76         byte_swap_ints(((int32_t *)&sb->fs_firstfield), 52);
77         byte_swap_int(sb->fs_cgrotor);
78         byte_swap_int(sb->fs_cpc);
79         byte_swap_shorts((int16_t *)sb->fs_opostbl,
80                 sizeof(sb->fs_opostbl) / sizeof(int16_t)); 
81         byte_swap_int(sb->fs_avgfilesize);
82         byte_swap_int(sb->fs_avgfpdir);
83         byte_swap_ints((int32_t *)sb->fs_sparecon,
84                 sizeof(sb->fs_sparecon) / sizeof(int32_t));
85         byte_swap_ints((int32_t *)&sb->fs_contigsumsize, 3);
86         byte_swap_longlongs((u_int64_t *)&sb->fs_maxfilesize,3);
87         byte_swap_ints((int32_t *)&sb->fs_state, 6);
89         /* Got these magic numbers from mkfs.c in newfs */
90         if (sb->fs_nrpos != 8 || sb->fs_cpc > 16) {
91                 usptr = (u_int16_t *)((u_int8_t *)(sb) + (sb)->fs_postbloff);
92                 size = sb->fs_cpc * sb->fs_nrpos;
93                 byte_swap_shorts(usptr,size);   /* fs_postbloff */
94         }
95 }

sb->fs_cpc and sb->fs_nrpos values come from the UFS stream encapsulated in the DMG image:

(gdb) p sb  
$2 = (struct fs *) 0x19a760
(gdb) p sb->fs_cpc
$3 = 4229900
(gdb) p sb->fs_nrpos 
$4 = 2015376779
(gdb) p sb->fs_cpc * sb->fs_nrpos
$5 = -2125137020
(gdb) p (u_int16_t *)((u_int8_t *)(sb) + (sb)->fs_postbloff)
$6 = (u_int16_t *) 0x3da4a7ab

sb->fs_postbloff represents the rotation block list head, and sb->fs_nrpos the number of rotational positions. See the fs.h header file for more information about the fs data structure.


Workaround or temporary solution

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

Resistance is futile.