diff options
| author | Thomas Betker <thomas.betker@freenet.de> | 2012-10-17 16:59:30 -0400 |
|---|---|---|
| committer | Artem Bityutskiy <artem.bityutskiy@linux.intel.com> | 2012-11-09 10:02:50 -0500 |
| commit | 5ffd3412ae5536a4c57469cb8ea31887121dcb2e (patch) | |
| tree | 33c9d89eabf70c49e683a36be800be3b4582a358 /fs/jffs2 | |
| parent | 0131950ebd146b5e31508233352d6f4625af25b1 (diff) | |
jffs2: Fix lock acquisition order bug in jffs2_write_begin
jffs2_write_begin() first acquires the page lock, then f->sem. This
causes an AB-BA deadlock with jffs2_garbage_collect_live(), which first
acquires f->sem, then the page lock:
jffs2_garbage_collect_live
mutex_lock(&f->sem) (A)
jffs2_garbage_collect_dnode
jffs2_gc_fetch_page
read_cache_page_async
do_read_cache_page
lock_page(page) (B)
jffs2_write_begin
grab_cache_page_write_begin
find_lock_page
lock_page(page) (B)
mutex_lock(&f->sem) (A)
We fix this by restructuring jffs2_write_begin() to take f->sem before
the page lock. However, we make sure that f->sem is not held when
calling jffs2_reserve_space(), as this is not permitted by the locking
rules.
The deadlock above was observed multiple times on an SoC with a dual
ARMv7 (Cortex-A9), running the long-term 3.4.11 kernel; it occurred
when using scp to copy files from a host system to the ARM target
system. The fix was heavily tested on the same target system.
Cc: stable@vger.kernel.org
Signed-off-by: Thomas Betker <thomas.betker@rohde-schwarz.com>
Acked-by: Joakim Tjernlund <Joakim.Tjernlund@transmode.se>
Signed-off-by: Artem Bityutskiy <artem.bityutskiy@linux.intel.com>
Diffstat (limited to 'fs/jffs2')
| -rw-r--r-- | fs/jffs2/file.c | 39 |
1 files changed, 21 insertions, 18 deletions
diff --git a/fs/jffs2/file.c b/fs/jffs2/file.c index 60ef3fb707ff..1506673c087e 100644 --- a/fs/jffs2/file.c +++ b/fs/jffs2/file.c | |||
| @@ -138,33 +138,39 @@ static int jffs2_write_begin(struct file *filp, struct address_space *mapping, | |||
| 138 | struct page *pg; | 138 | struct page *pg; |
| 139 | struct inode *inode = mapping->host; | 139 | struct inode *inode = mapping->host; |
| 140 | struct jffs2_inode_info *f = JFFS2_INODE_INFO(inode); | 140 | struct jffs2_inode_info *f = JFFS2_INODE_INFO(inode); |
| 141 | struct jffs2_sb_info *c = JFFS2_SB_INFO(inode->i_sb); | ||
| 142 | struct jffs2_raw_inode ri; | ||
| 143 | uint32_t alloc_len = 0; | ||
| 141 | pgoff_t index = pos >> PAGE_CACHE_SHIFT; | 144 | pgoff_t index = pos >> PAGE_CACHE_SHIFT; |
| 142 | uint32_t pageofs = index << PAGE_CACHE_SHIFT; | 145 | uint32_t pageofs = index << PAGE_CACHE_SHIFT; |
| 143 | int ret = 0; | 146 | int ret = 0; |
| 144 | 147 | ||
| 148 | jffs2_dbg(1, "%s()\n", __func__); | ||
| 149 | |||
| 150 | if (pageofs > inode->i_size) { | ||
| 151 | ret = jffs2_reserve_space(c, sizeof(ri), &alloc_len, | ||
| 152 | ALLOC_NORMAL, JFFS2_SUMMARY_INODE_SIZE); | ||
| 153 | if (ret) | ||
| 154 | return ret; | ||
| 155 | } | ||
| 156 | |||
| 157 | mutex_lock(&f->sem); | ||
| 145 | pg = grab_cache_page_write_begin(mapping, index, flags); | 158 | pg = grab_cache_page_write_begin(mapping, index, flags); |
| 146 | if (!pg) | 159 | if (!pg) { |
| 160 | if (alloc_len) | ||
| 161 | jffs2_complete_reservation(c); | ||
| 162 | mutex_unlock(&f->sem); | ||
| 147 | return -ENOMEM; | 163 | return -ENOMEM; |
| 164 | } | ||
| 148 | *pagep = pg; | 165 | *pagep = pg; |
| 149 | 166 | ||
| 150 | jffs2_dbg(1, "%s()\n", __func__); | 167 | if (alloc_len) { |
| 151 | |||
| 152 | if (pageofs > inode->i_size) { | ||
| 153 | /* Make new hole frag from old EOF to new page */ | 168 | /* Make new hole frag from old EOF to new page */ |
| 154 | struct jffs2_sb_info *c = JFFS2_SB_INFO(inode->i_sb); | ||
| 155 | struct jffs2_raw_inode ri; | ||
| 156 | struct jffs2_full_dnode *fn; | 169 | struct jffs2_full_dnode *fn; |
| 157 | uint32_t alloc_len; | ||
| 158 | 170 | ||
| 159 | jffs2_dbg(1, "Writing new hole frag 0x%x-0x%x between current EOF and new page\n", | 171 | jffs2_dbg(1, "Writing new hole frag 0x%x-0x%x between current EOF and new page\n", |
| 160 | (unsigned int)inode->i_size, pageofs); | 172 | (unsigned int)inode->i_size, pageofs); |
| 161 | 173 | ||
| 162 | ret = jffs2_reserve_space(c, sizeof(ri), &alloc_len, | ||
| 163 | ALLOC_NORMAL, JFFS2_SUMMARY_INODE_SIZE); | ||
| 164 | if (ret) | ||
| 165 | goto out_page; | ||
| 166 | |||
| 167 | mutex_lock(&f->sem); | ||
| 168 | memset(&ri, 0, sizeof(ri)); | 174 | memset(&ri, 0, sizeof(ri)); |
| 169 | 175 | ||
| 170 | ri.magic = cpu_to_je16(JFFS2_MAGIC_BITMASK); | 176 | ri.magic = cpu_to_je16(JFFS2_MAGIC_BITMASK); |
| @@ -191,7 +197,6 @@ static int jffs2_write_begin(struct file *filp, struct address_space *mapping, | |||
| 191 | if (IS_ERR(fn)) { | 197 | if (IS_ERR(fn)) { |
| 192 | ret = PTR_ERR(fn); | 198 | ret = PTR_ERR(fn); |
| 193 | jffs2_complete_reservation(c); | 199 | jffs2_complete_reservation(c); |
| 194 | mutex_unlock(&f->sem); | ||
| 195 | goto out_page; | 200 | goto out_page; |
| 196 | } | 201 | } |
| 197 | ret = jffs2_add_full_dnode_to_inode(c, f, fn); | 202 | ret = jffs2_add_full_dnode_to_inode(c, f, fn); |
| @@ -206,12 +211,10 @@ static int jffs2_write_begin(struct file *filp, struct address_space *mapping, | |||
| 206 | jffs2_mark_node_obsolete(c, fn->raw); | 211 | jffs2_mark_node_obsolete(c, fn->raw); |
| 207 | jffs2_free_full_dnode(fn); | 212 | jffs2_free_full_dnode(fn); |
| 208 | jffs2_complete_reservation(c); | 213 | jffs2_complete_reservation(c); |
| 209 | mutex_unlock(&f->sem); | ||
| 210 | goto out_page; | 214 | goto out_page; |
| 211 | } | 215 | } |
| 212 | jffs2_complete_reservation(c); | 216 | jffs2_complete_reservation(c); |
| 213 | inode->i_size = pageofs; | 217 | inode->i_size = pageofs; |
| 214 | mutex_unlock(&f->sem); | ||
| 215 | } | 218 | } |
| 216 | 219 | ||
| 217 | /* | 220 | /* |
| @@ -220,18 +223,18 @@ static int jffs2_write_begin(struct file *filp, struct address_space *mapping, | |||
| 220 | * case of a short-copy. | 223 | * case of a short-copy. |
| 221 | */ | 224 | */ |
| 222 | if (!PageUptodate(pg)) { | 225 | if (!PageUptodate(pg)) { |
| 223 | mutex_lock(&f->sem); | ||
| 224 | ret = jffs2_do_readpage_nolock(inode, pg); | 226 | ret = jffs2_do_readpage_nolock(inode, pg); |
| 225 | mutex_unlock(&f->sem); | ||
| 226 | if (ret) | 227 | if (ret) |
| 227 | goto out_page; | 228 | goto out_page; |
| 228 | } | 229 | } |
| 230 | mutex_unlock(&f->sem); | ||
| 229 | jffs2_dbg(1, "end write_begin(). pg->flags %lx\n", pg->flags); | 231 | jffs2_dbg(1, "end write_begin(). pg->flags %lx\n", pg->flags); |
| 230 | return ret; | 232 | return ret; |
| 231 | 233 | ||
| 232 | out_page: | 234 | out_page: |
| 233 | unlock_page(pg); | 235 | unlock_page(pg); |
| 234 | page_cache_release(pg); | 236 | page_cache_release(pg); |
| 237 | mutex_unlock(&f->sem); | ||
| 235 | return ret; | 238 | return ret; |
| 236 | } | 239 | } |
| 237 | 240 | ||
