MOKB-15-11-2006

Bug details
Title: Linux 2.6.x gfs2 init_journal denial of service
Description: Linux 2.6.x gfs2 filesystem code fails to properly handle corrupted data structures, leading to an exploitable denial of service issue when a crafted stream is being mounted. This particular vulnerability is caused by a NULL pointer dereference in the init_journal function.
Author/Contributor:
References:
Proof of concept or exploit: The following gfs2 filesystem image can be used to reproduce the bug: MOKB-15-11-2006.img.bz2
Use a loopback device to mount it: bunzip2 MOKB-15-11-2006.img.bz2 && mount -t gfs2 -o loop MOKB-15-11-2006.img /media/test
Debugging information:

The bug has been found using the Linux version of fsfuzzer on a Fedora Core 6 installation, with up to date packages as of 14-11-2006. No operation except mount itself is necessary to trigger the bug. The architecture used to conduct the tests is IA32/x86, SMP enabled. As of 2.6.18, gfs2 is not supported by the mainline kernel (not yet merged).

[root@fedora ~]# uname -a
Linux fedora 2.6.18-1.2798.fc6 #1 SMP Mon Oct 16 14:37:32 EDT 2006 i686 i686 i386 GNU/Linux

GFS2 (built Oct 16 2006 14:39:08) installed
BUG: unable to handle kernel NULL pointer dereference at virtual address 000002a c
 printing eip:
d0be45a9
*pde = 00000000
Oops: 0000 [#1]
SMP
last sysfs file: /block/loop3/range
Modules linked in: lock_nolock gfs2 hfs loop ipv6 sunrpc ip_conntrack_netbios_ns  ipt_REJECT
xt_state ip_conntrack nfnetlink xt_tcpudp iptable_filter ip_tables x _tables
video sbs i2c_ec button battery asus_acpi ac parport_pc lp parport snd_ens1371 g ameport snd_rawmidi
snd_ac97_codec snd_ac97_bus snd_seq_dummy snd_seq_oss snd_seq_midi_event snd_seq sg snd_seq_device
snd_pcm_oss snd_mixer_oss snd_pcm v mxnet(U) snd_timer floppy i2c_piix4 snd pcnet32 i2c_core ide_cd cdrom
soundcore mii serio_raw snd_page_alloc pcspkr dm_snapshot dm_zero dm_mirror dm_mod ext3 jbd mp tspi
scsi_transport_spi mptscsih sd_mod scsi_mod mptbase
CPU:    0
EIP:    0060:[]    Tainted: P      VLI
EFLAGS: 00010207   (2.6.18-1.2798.fc6 #1)
EIP is at init_journal+0x57/0x3f5 [gfs2]
eax: 00000000   ebx: 00000000   ecx: 00000001   edx: 00000000
esi: ca9f3000   edi: ca82b028   ebp: ca9f3000   esp: ca87bc94
ds: 007b   es: 007b   ss: 0068
Process mount.gfs2 (pid: 1929, ti=ca87b000 task=cfe232c0 task.ti=ca87b000)
Stack: ca9f3000 d0bef9c0 cfe232c0 ca82b2d8 caae352c caae352c d0bd9ae2 caae352c
       00000000 0000004d ca9f3000 00000000 00000003 ca82b1b4 00000003 ca82b028
       d0bdba52 ca82b2d8 00000001 ca87bce4 caae352c 00000000 ca9f3000 ca82b028
Call Trace:
 [] init_inodes+0x54/0x1da [gfs2]
 [] fill_super+0x50e/0x632 [gfs2]
 [] get_sb_bdev+0xce/0x11c
 [] gfs2_get_sb+0x21/0x3e [gfs2]
 [] vfs_kern_mount+0x83/0xf6
 [] do_kern_mount+0x2d/0x3e
 [] do_mount+0x5fa/0x66d
 [] sys_mount+0x77/0xae
 [] syscall_call+0x7/0xb
DWARF2 unwinder stuck at syscall_call+0x7/0xb
Leftover inexact backtrace:
 =======================
Code: 76 29 8d 85 9c 08 00 00 c7 44 24 08 00 00 00 00 89 44 24 04 c7 04 24 2a 21  bf d0 e8 f0 11 84 ef 8b
bd 20 04 00 00 e9 94 03 00 00 <8b> 80 ac 02 00 00 90 0f  ba 68 08
02 8d 54 24 14 89 e8 e8 92 89
EIP: [] init_journal+0x57/0x3f5 [gfs2] SS:ESP 0068:ca87bc94

http://people.redhat.com/mingo/gfs2-patches/patches/gfs2-04.patch
+static int init_journal(struct gfs2_sbd *sdp, int undo)
+{
+	struct gfs2_holder ji_gh;
+	struct task_struct *p;
+	int jindex = 1;
+	int error = 0;
+
+	if (undo) {
+		jindex = 0;
+		goto fail_recoverd;
+	}
+
+	error = gfs2_lookup_simple(sdp->sd_master_dir, "jindex",
+				   &sdp->sd_jindex);
+	if (error) {
+		fs_err(sdp, "can't lookup journal index: %d\n", error);
+		return error;
+	}
+	set_bit(GLF_STICKY, &sdp->sd_jindex->i_gl->gl_flags);
+
+	/* Load in the journal index special file */
+
+	error = gfs2_jindex_hold(sdp, &ji_gh);
+	if (error) {
+		fs_err(sdp, "can't read journal index: %d\n", error);
+		goto fail;
+	}
+
+	error = -EINVAL;
+	if (!gfs2_jindex_size(sdp)) {
+		fs_err(sdp, "no journals!\n");
+		goto fail_jindex;		
+	}
+
+	if (sdp->sd_args.ar_spectator) {
+		sdp->sd_jdesc = gfs2_jdesc_find(sdp, 0);
+		sdp->sd_log_blks_free = sdp->sd_jdesc->jd_blocks;
+	} else {
+		if (sdp->sd_lockstruct.ls_jid >= gfs2_jindex_size(sdp)) {
+			fs_err(sdp, "can't mount journal #%u\n",
+			       sdp->sd_lockstruct.ls_jid);
+			fs_err(sdp, "there are only %u journals (0 - %u)\n",
+			       gfs2_jindex_size(sdp),
+			       gfs2_jindex_size(sdp) - 1);
+			goto fail_jindex;
+		}
+		sdp->sd_jdesc = gfs2_jdesc_find(sdp, sdp->sd_lockstruct.ls_jid);
+
+		error = gfs2_glock_nq_num(sdp,
+					  sdp->sd_lockstruct.ls_jid,
+					  &gfs2_journal_glops,
+					  LM_ST_EXCLUSIVE, LM_FLAG_NOEXP,
+					  &sdp->sd_journal_gh);
+		if (error) {
+			fs_err(sdp, "can't acquire journal glock: %d\n", error);
+			goto fail_jindex;
+		}
+
+		error = gfs2_glock_nq_init(sdp->sd_jdesc->jd_inode->i_gl,
+					   LM_ST_SHARED,
+					   LM_FLAG_NOEXP | GL_EXACT,
+					   &sdp->sd_jinode_gh);
+		if (error) {
+			fs_err(sdp, "can't acquire journal inode glock: %d\n",
+			       error);
+			goto fail_journal_gh;
+		}
+
+		error = gfs2_jdesc_check(sdp->sd_jdesc);
+		if (error) {
+			fs_err(sdp, "my journal (%u) is bad: %d\n",
+			       sdp->sd_jdesc->jd_jid, error);
+			goto fail_jinode_gh;
+		}
+		sdp->sd_log_blks_free = sdp->sd_jdesc->jd_blocks;
+	}
+
+	if (sdp->sd_lockstruct.ls_first) {
+		unsigned int x;
+		for (x = 0; x < sdp->sd_journals; x++) {
+			error = gfs2_recover_journal(gfs2_jdesc_find(sdp, x),
+						     WAIT);
+			if (error) {
+				fs_err(sdp, "error recovering journal %u: %d\n",
+				       x, error);
+				goto fail_jinode_gh;
+			}
+		}
+
+		gfs2_lm_others_may_mount(sdp);
+	} else if (!sdp->sd_args.ar_spectator) {
+		error = gfs2_recover_journal(sdp->sd_jdesc, WAIT);
+		if (error) {
+			fs_err(sdp, "error recovering my journal: %d\n", error);
+			goto fail_jinode_gh;
+		}
+	}
+
+	set_bit(SDF_JOUNA_CHECKED, &sdp->sd_flags);
+	gfs2_glock_dq_uninit(&ji_gh);
+	jindex = 0;
+
+	/* Disown my Journal glock */
+
+	sdp->sd_journal_gh.gh_owner = NULL;
+	sdp->sd_jinode_gh.gh_owner = NULL;
+
+	p = kthread_run(gfs2_recoverd, sdp, "gfs2_recoverd");
+	error = IS_ERR(p);
+	if (error) {
+		fs_err(sdp, "can't start recoverd thread: %d\n", error);
+		goto fail_jinode_gh;
+	}
+	sdp->sd_recoverd_process = p;
+
+	return 0;
+
+ fail_recoverd:
+	kthread_stop(sdp->sd_recoverd_process);
+
+ fail_jinode_gh:
+	if (!sdp->sd_args.ar_spectator)
+		gfs2_glock_dq_uninit(&sdp->sd_jinode_gh);
+
+ fail_journal_gh:
+	if (!sdp->sd_args.ar_spectator)
+		gfs2_glock_dq_uninit(&sdp->sd_journal_gh);
+
+ fail_jindex:
+	gfs2_jindex_free(sdp);
+	if (jindex)
+		gfs2_glock_dq_uninit(&ji_gh);
+
+ fail:
+	gfs2_inode_put(sdp->sd_jindex);
+
+	return error;
+}