Skip to content

Commit dab17c1

Browse files
committed
afs: Fix directory read/modify race
Because parsing of the directory wasn't being done under any sort of lock, the pages holding the directory content can get invalidated whilst the parsing is ongoing. Further, the directory page check function gets called outside of the page lock, so if the page gets cleared or updated, this may return reports of bad magic numbers in the directory page. Also, the directory may change size whilst checking and parsing are ongoing, so more care needs to be taken here. Fix this by: (1) Perform the page check from the page filling function before we set PageUptodate and drop the page lock. (2) Check for the file having shrunk and the page having been abandoned before checking the page contents. (3) Lock the page whilst parsing it for the directory iterator. Whilst we're at it, add a tracepoint to report check failure. Signed-off-by: David Howells <[email protected]>
1 parent 2c09901 commit dab17c1

File tree

4 files changed

+46
-8
lines changed

4 files changed

+46
-8
lines changed

fs/afs/dir.c

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,11 @@ struct afs_lookup_cookie {
130130
/*
131131
* check that a directory page is valid
132132
*/
133-
static inline bool afs_dir_check_page(struct inode *dir, struct page *page)
133+
bool afs_dir_check_page(struct inode *dir, struct page *page)
134134
{
135135
struct afs_dir_page *dbuf;
136-
loff_t latter;
136+
struct afs_vnode *vnode = AFS_FS_I(dir);
137+
loff_t latter, i_size, off;
137138
int tmp, qty;
138139

139140
#if 0
@@ -150,8 +151,15 @@ static inline bool afs_dir_check_page(struct inode *dir, struct page *page)
150151
}
151152
#endif
152153

153-
/* determine how many magic numbers there should be in this page */
154-
latter = dir->i_size - page_offset(page);
154+
/* Determine how many magic numbers there should be in this page, but
155+
* we must take care because the directory may change size under us.
156+
*/
157+
off = page_offset(page);
158+
i_size = i_size_read(dir);
159+
if (i_size <= off)
160+
goto checked;
161+
162+
latter = i_size - off;
155163
if (latter >= PAGE_SIZE)
156164
qty = PAGE_SIZE;
157165
else
@@ -162,13 +170,15 @@ static inline bool afs_dir_check_page(struct inode *dir, struct page *page)
162170
dbuf = page_address(page);
163171
for (tmp = 0; tmp < qty; tmp++) {
164172
if (dbuf->blocks[tmp].pagehdr.magic != AFS_DIR_MAGIC) {
165-
printk("kAFS: %s(%lu): bad magic %d/%d is %04hx\n",
173+
printk("kAFS: %s(%lx): bad magic %d/%d is %04hx\n",
166174
__func__, dir->i_ino, tmp, qty,
167175
ntohs(dbuf->blocks[tmp].pagehdr.magic));
176+
trace_afs_dir_check_failed(vnode, off, i_size);
168177
goto error;
169178
}
170179
}
171180

181+
checked:
172182
SetPageChecked(page);
173183
return true;
174184

@@ -183,6 +193,7 @@ static inline bool afs_dir_check_page(struct inode *dir, struct page *page)
183193
static inline void afs_dir_put_page(struct page *page)
184194
{
185195
kunmap(page);
196+
unlock_page(page);
186197
put_page(page);
187198
}
188199

@@ -197,9 +208,10 @@ static struct page *afs_dir_get_page(struct inode *dir, unsigned long index,
197208

198209
page = read_cache_page(dir->i_mapping, index, afs_page_filler, key);
199210
if (!IS_ERR(page)) {
211+
lock_page(page);
200212
kmap(page);
201213
if (unlikely(!PageChecked(page))) {
202-
if (PageError(page) || !afs_dir_check_page(dir, page))
214+
if (PageError(page))
203215
goto fail;
204216
}
205217
}
@@ -384,8 +396,7 @@ static int afs_dir_iterate(struct inode *dir, struct dir_context *ctx,
384396
*/
385397
static int afs_readdir(struct file *file, struct dir_context *ctx)
386398
{
387-
return afs_dir_iterate(file_inode(file),
388-
ctx, file->private_data);
399+
return afs_dir_iterate(file_inode(file), ctx, file->private_data);
389400
}
390401

391402
/*

fs/afs/file.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,11 @@ int afs_page_filler(void *data, struct page *page)
232232
* page */
233233
ret = afs_fetch_data(vnode, key, req);
234234
afs_put_read(req);
235+
236+
if (ret >= 0 && S_ISDIR(inode->i_mode) &&
237+
!afs_dir_check_page(inode, page))
238+
ret = -EIO;
239+
235240
if (ret < 0) {
236241
if (ret == -ENOENT) {
237242
_debug("got NOENT from server"

fs/afs/internal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,7 @@ extern bool afs_cm_incoming_call(struct afs_call *);
622622
/*
623623
* dir.c
624624
*/
625+
extern bool afs_dir_check_page(struct inode *, struct page *);
625626
extern const struct inode_operations afs_dir_inode_operations;
626627
extern const struct dentry_operations afs_fs_dentry_operations;
627628
extern const struct file_operations afs_dir_file_operations;

include/trace/events/afs.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,27 @@ TRACE_EVENT(afs_sent_pages,
381381
__entry->cursor, __entry->ret)
382382
);
383383

384+
TRACE_EVENT(afs_dir_check_failed,
385+
TP_PROTO(struct afs_vnode *vnode, loff_t off, loff_t i_size),
386+
387+
TP_ARGS(vnode, off, i_size),
388+
389+
TP_STRUCT__entry(
390+
__field(struct afs_vnode *, vnode )
391+
__field(loff_t, off )
392+
__field(loff_t, i_size )
393+
),
394+
395+
TP_fast_assign(
396+
__entry->vnode = vnode;
397+
__entry->off = off;
398+
__entry->i_size = i_size;
399+
),
400+
401+
TP_printk("vn=%p %llx/%llx",
402+
__entry->vnode, __entry->off, __entry->i_size)
403+
);
404+
384405
#endif /* _TRACE_AFS_H */
385406

386407
/* This part must be outside protection */

0 commit comments

Comments
 (0)