From: Dave Anderson <anderson@redhat.com> Date: Wed, 25 Mar 2009 09:33:14 -0400 Subject: [fs] HFS: mount memory leak Message-id: 49CA329A.6060800@redhat.com O-Subject: [RHEL5.4 PATCH] BZ #488048: HFS mount memory leak Bugzilla: 488048 RH-Acked-by: Larry Woodman <lwoodman@redhat.com> RH-Acked-by: Eugene Teo <eugene@redhat.com> BZ #488048: kernel: hfs: try_to_release_page() null pointer deref [rhel-5.4] https://bugzilla.redhat.com/show_bug.cgi?id=488048 The implementation of the HFS mount procedure leaks a 2-page bitmap upon the subsequent unmount, or as seen in this case, when the mount command fails. The BZ was based upon a panic seen running a QA test consisting of repeated mount commands using a corrupted HFS filesystem image. Each mount command would fail and the 2 pages leaked. Eventually the system becomes starved for memory, and either: (1) kswapd attempts to reclaim a inode page that the mount command is blocked waiting on for the I/O to complete. It calls back into the hfs_releasepage(), which relies upon a pointer that has not been initialized by the blocked mount command, which is still waiting to get the lock on the page. Or (2) the oom-killer kicks in The fix addresses the 2-page memory leak, and also prevents the panic from occurring by making hfs_releasepage() recognize the incomplete bookkeeping and fail the release-page request. Tested by myself, Cai Quan and Eugene Teo. diff --git a/fs/hfs/inode.c b/fs/hfs/inode.c index d05641c..187f031 100644 --- a/fs/hfs/inode.c +++ b/fs/hfs/inode.c @@ -65,6 +65,10 @@ static int hfs_releasepage(struct page *page, gfp_t mask) BUG(); return 0; } + + if (!tree) + return 0; + if (tree->node_size >= PAGE_CACHE_SIZE) { nidx = page->index >> (tree->node_size_shift - PAGE_CACHE_SHIFT); spin_lock(&tree->hash_lock); diff --git a/fs/hfs/mdb.c b/fs/hfs/mdb.c index b4651e1..16b6a0a 100644 --- a/fs/hfs/mdb.c +++ b/fs/hfs/mdb.c @@ -349,6 +349,10 @@ void hfs_mdb_put(struct super_block *sb) if (HFS_SB(sb)->nls_disk) unload_nls(HFS_SB(sb)->nls_disk); + /* free the bitmap page(s) */ + if (HFS_SB(sb)->bitmap) + free_pages((unsigned long)HFS_SB(sb)->bitmap, PAGE_SIZE < 8192 ? 1 : 0); + kfree(HFS_SB(sb)); sb->s_fs_info = NULL; }