diff options
author | Linus Torvalds <torvalds@g5.osdl.org> | 2005-08-19 21:02:56 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@g5.osdl.org> | 2005-08-19 21:02:56 -0400 |
commit | cc314eef0128a807e50fa03baf2d0abc0647952c (patch) | |
tree | 8e38db1be28006894915273b3f3cb3beaa6efda3 /fs | |
parent | 2fb1e3086df9b454538491fba8121298da37cd23 (diff) |
Fix nasty ncpfs symlink handling bug.
This bug could cause oopses and page state corruption, because ncpfs
used the generic page-cache symlink handlign functions. But those
functions only work if the page cache is guaranteed to be "stable", ie a
page that was installed when the symlink walk was started has to still
be installed in the page cache at the end of the walk.
We could have fixed ncpfs to not use the generic helper routines, but it
is in many ways much cleaner to instead improve on the symlink walking
helper routines so that they don't require that absolute stability.
We do this by allowing "follow_link()" to return a error-pointer as a
cookie, which is fed back to the cleanup "put_link()" routine. This
also simplifies NFS symlink handling.
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'fs')
-rw-r--r-- | fs/autofs/symlink.c | 5 | ||||
-rw-r--r-- | fs/cifs/cifsfs.h | 4 | ||||
-rw-r--r-- | fs/cifs/link.c | 6 | ||||
-rw-r--r-- | fs/ext2/symlink.c | 4 | ||||
-rw-r--r-- | fs/ext3/symlink.c | 4 | ||||
-rw-r--r-- | fs/namei.c | 40 | ||||
-rw-r--r-- | fs/nfs/symlink.c | 37 | ||||
-rw-r--r-- | fs/sysfs/symlink.c | 6 |
8 files changed, 44 insertions, 62 deletions
diff --git a/fs/autofs/symlink.c b/fs/autofs/symlink.c index f028396f1383..52e8772b066e 100644 --- a/fs/autofs/symlink.c +++ b/fs/autofs/symlink.c | |||
@@ -12,11 +12,12 @@ | |||
12 | 12 | ||
13 | #include "autofs_i.h" | 13 | #include "autofs_i.h" |
14 | 14 | ||
15 | static int autofs_follow_link(struct dentry *dentry, struct nameidata *nd) | 15 | /* Nothing to release.. */ |
16 | static void *autofs_follow_link(struct dentry *dentry, struct nameidata *nd) | ||
16 | { | 17 | { |
17 | char *s=((struct autofs_symlink *)dentry->d_inode->u.generic_ip)->data; | 18 | char *s=((struct autofs_symlink *)dentry->d_inode->u.generic_ip)->data; |
18 | nd_set_link(nd, s); | 19 | nd_set_link(nd, s); |
19 | return 0; | 20 | return NULL; |
20 | } | 21 | } |
21 | 22 | ||
22 | struct inode_operations autofs_symlink_inode_operations = { | 23 | struct inode_operations autofs_symlink_inode_operations = { |
diff --git a/fs/cifs/cifsfs.h b/fs/cifs/cifsfs.h index 78af5850c558..1fd21f66f243 100644 --- a/fs/cifs/cifsfs.h +++ b/fs/cifs/cifsfs.h | |||
@@ -83,8 +83,8 @@ extern int cifs_dir_notify(struct file *, unsigned long arg); | |||
83 | extern struct dentry_operations cifs_dentry_ops; | 83 | extern struct dentry_operations cifs_dentry_ops; |
84 | 84 | ||
85 | /* Functions related to symlinks */ | 85 | /* Functions related to symlinks */ |
86 | extern int cifs_follow_link(struct dentry *direntry, struct nameidata *nd); | 86 | extern void *cifs_follow_link(struct dentry *direntry, struct nameidata *nd); |
87 | extern void cifs_put_link(struct dentry *direntry, struct nameidata *nd); | 87 | extern void cifs_put_link(struct dentry *direntry, struct nameidata *nd, void *); |
88 | extern int cifs_readlink(struct dentry *direntry, char __user *buffer, | 88 | extern int cifs_readlink(struct dentry *direntry, char __user *buffer, |
89 | int buflen); | 89 | int buflen); |
90 | extern int cifs_symlink(struct inode *inode, struct dentry *direntry, | 90 | extern int cifs_symlink(struct inode *inode, struct dentry *direntry, |
diff --git a/fs/cifs/link.c b/fs/cifs/link.c index bde0fabfece0..ab925ef4f863 100644 --- a/fs/cifs/link.c +++ b/fs/cifs/link.c | |||
@@ -92,7 +92,7 @@ cifs_hl_exit: | |||
92 | return rc; | 92 | return rc; |
93 | } | 93 | } |
94 | 94 | ||
95 | int | 95 | void * |
96 | cifs_follow_link(struct dentry *direntry, struct nameidata *nd) | 96 | cifs_follow_link(struct dentry *direntry, struct nameidata *nd) |
97 | { | 97 | { |
98 | struct inode *inode = direntry->d_inode; | 98 | struct inode *inode = direntry->d_inode; |
@@ -148,7 +148,7 @@ out: | |||
148 | out_no_free: | 148 | out_no_free: |
149 | FreeXid(xid); | 149 | FreeXid(xid); |
150 | nd_set_link(nd, target_path); | 150 | nd_set_link(nd, target_path); |
151 | return 0; | 151 | return NULL; /* No cookie */ |
152 | } | 152 | } |
153 | 153 | ||
154 | int | 154 | int |
@@ -330,7 +330,7 @@ cifs_readlink(struct dentry *direntry, char __user *pBuffer, int buflen) | |||
330 | return rc; | 330 | return rc; |
331 | } | 331 | } |
332 | 332 | ||
333 | void cifs_put_link(struct dentry *direntry, struct nameidata *nd) | 333 | void cifs_put_link(struct dentry *direntry, struct nameidata *nd, void *cookie) |
334 | { | 334 | { |
335 | char *p = nd_get_link(nd); | 335 | char *p = nd_get_link(nd); |
336 | if (!IS_ERR(p)) | 336 | if (!IS_ERR(p)) |
diff --git a/fs/ext2/symlink.c b/fs/ext2/symlink.c index 9f7bac01d557..1e67d87cfa91 100644 --- a/fs/ext2/symlink.c +++ b/fs/ext2/symlink.c | |||
@@ -21,11 +21,11 @@ | |||
21 | #include "xattr.h" | 21 | #include "xattr.h" |
22 | #include <linux/namei.h> | 22 | #include <linux/namei.h> |
23 | 23 | ||
24 | static int ext2_follow_link(struct dentry *dentry, struct nameidata *nd) | 24 | static void *ext2_follow_link(struct dentry *dentry, struct nameidata *nd) |
25 | { | 25 | { |
26 | struct ext2_inode_info *ei = EXT2_I(dentry->d_inode); | 26 | struct ext2_inode_info *ei = EXT2_I(dentry->d_inode); |
27 | nd_set_link(nd, (char *)ei->i_data); | 27 | nd_set_link(nd, (char *)ei->i_data); |
28 | return 0; | 28 | return NULL; |
29 | } | 29 | } |
30 | 30 | ||
31 | struct inode_operations ext2_symlink_inode_operations = { | 31 | struct inode_operations ext2_symlink_inode_operations = { |
diff --git a/fs/ext3/symlink.c b/fs/ext3/symlink.c index 8c3e72818fb0..4f79122cde67 100644 --- a/fs/ext3/symlink.c +++ b/fs/ext3/symlink.c | |||
@@ -23,11 +23,11 @@ | |||
23 | #include <linux/namei.h> | 23 | #include <linux/namei.h> |
24 | #include "xattr.h" | 24 | #include "xattr.h" |
25 | 25 | ||
26 | static int ext3_follow_link(struct dentry *dentry, struct nameidata *nd) | 26 | static void * ext3_follow_link(struct dentry *dentry, struct nameidata *nd) |
27 | { | 27 | { |
28 | struct ext3_inode_info *ei = EXT3_I(dentry->d_inode); | 28 | struct ext3_inode_info *ei = EXT3_I(dentry->d_inode); |
29 | nd_set_link(nd, (char*)ei->i_data); | 29 | nd_set_link(nd, (char*)ei->i_data); |
30 | return 0; | 30 | return NULL; |
31 | } | 31 | } |
32 | 32 | ||
33 | struct inode_operations ext3_symlink_inode_operations = { | 33 | struct inode_operations ext3_symlink_inode_operations = { |
diff --git a/fs/namei.c b/fs/namei.c index b85f158aef0c..6ec1f0fefc5b 100644 --- a/fs/namei.c +++ b/fs/namei.c | |||
@@ -501,6 +501,7 @@ struct path { | |||
501 | static inline int __do_follow_link(struct path *path, struct nameidata *nd) | 501 | static inline int __do_follow_link(struct path *path, struct nameidata *nd) |
502 | { | 502 | { |
503 | int error; | 503 | int error; |
504 | void *cookie; | ||
504 | struct dentry *dentry = path->dentry; | 505 | struct dentry *dentry = path->dentry; |
505 | 506 | ||
506 | touch_atime(path->mnt, dentry); | 507 | touch_atime(path->mnt, dentry); |
@@ -508,13 +509,15 @@ static inline int __do_follow_link(struct path *path, struct nameidata *nd) | |||
508 | 509 | ||
509 | if (path->mnt == nd->mnt) | 510 | if (path->mnt == nd->mnt) |
510 | mntget(path->mnt); | 511 | mntget(path->mnt); |
511 | error = dentry->d_inode->i_op->follow_link(dentry, nd); | 512 | cookie = dentry->d_inode->i_op->follow_link(dentry, nd); |
512 | if (!error) { | 513 | error = PTR_ERR(cookie); |
514 | if (!IS_ERR(cookie)) { | ||
513 | char *s = nd_get_link(nd); | 515 | char *s = nd_get_link(nd); |
516 | error = 0; | ||
514 | if (s) | 517 | if (s) |
515 | error = __vfs_follow_link(nd, s); | 518 | error = __vfs_follow_link(nd, s); |
516 | if (dentry->d_inode->i_op->put_link) | 519 | if (dentry->d_inode->i_op->put_link) |
517 | dentry->d_inode->i_op->put_link(dentry, nd); | 520 | dentry->d_inode->i_op->put_link(dentry, nd, cookie); |
518 | } | 521 | } |
519 | dput(dentry); | 522 | dput(dentry); |
520 | mntput(path->mnt); | 523 | mntput(path->mnt); |
@@ -2344,15 +2347,17 @@ out: | |||
2344 | int generic_readlink(struct dentry *dentry, char __user *buffer, int buflen) | 2347 | int generic_readlink(struct dentry *dentry, char __user *buffer, int buflen) |
2345 | { | 2348 | { |
2346 | struct nameidata nd; | 2349 | struct nameidata nd; |
2347 | int res; | 2350 | void *cookie; |
2351 | |||
2348 | nd.depth = 0; | 2352 | nd.depth = 0; |
2349 | res = dentry->d_inode->i_op->follow_link(dentry, &nd); | 2353 | cookie = dentry->d_inode->i_op->follow_link(dentry, &nd); |
2350 | if (!res) { | 2354 | if (!IS_ERR(cookie)) { |
2351 | res = vfs_readlink(dentry, buffer, buflen, nd_get_link(&nd)); | 2355 | int res = vfs_readlink(dentry, buffer, buflen, nd_get_link(&nd)); |
2352 | if (dentry->d_inode->i_op->put_link) | 2356 | if (dentry->d_inode->i_op->put_link) |
2353 | dentry->d_inode->i_op->put_link(dentry, &nd); | 2357 | dentry->d_inode->i_op->put_link(dentry, &nd, cookie); |
2358 | cookie = ERR_PTR(res); | ||
2354 | } | 2359 | } |
2355 | return res; | 2360 | return PTR_ERR(cookie); |
2356 | } | 2361 | } |
2357 | 2362 | ||
2358 | int vfs_follow_link(struct nameidata *nd, const char *link) | 2363 | int vfs_follow_link(struct nameidata *nd, const char *link) |
@@ -2395,23 +2400,20 @@ int page_readlink(struct dentry *dentry, char __user *buffer, int buflen) | |||
2395 | return res; | 2400 | return res; |
2396 | } | 2401 | } |
2397 | 2402 | ||
2398 | int page_follow_link_light(struct dentry *dentry, struct nameidata *nd) | 2403 | void *page_follow_link_light(struct dentry *dentry, struct nameidata *nd) |
2399 | { | 2404 | { |
2400 | struct page *page; | 2405 | struct page *page = NULL; |
2401 | nd_set_link(nd, page_getlink(dentry, &page)); | 2406 | nd_set_link(nd, page_getlink(dentry, &page)); |
2402 | return 0; | 2407 | return page; |
2403 | } | 2408 | } |
2404 | 2409 | ||
2405 | void page_put_link(struct dentry *dentry, struct nameidata *nd) | 2410 | void page_put_link(struct dentry *dentry, struct nameidata *nd, void *cookie) |
2406 | { | 2411 | { |
2407 | if (!IS_ERR(nd_get_link(nd))) { | 2412 | struct page *page = cookie; |
2408 | struct page *page; | 2413 | |
2409 | page = find_get_page(dentry->d_inode->i_mapping, 0); | 2414 | if (page) { |
2410 | if (!page) | ||
2411 | BUG(); | ||
2412 | kunmap(page); | 2415 | kunmap(page); |
2413 | page_cache_release(page); | 2416 | page_cache_release(page); |
2414 | page_cache_release(page); | ||
2415 | } | 2417 | } |
2416 | } | 2418 | } |
2417 | 2419 | ||
diff --git a/fs/nfs/symlink.c b/fs/nfs/symlink.c index 35f106599144..18dc95b0b646 100644 --- a/fs/nfs/symlink.c +++ b/fs/nfs/symlink.c | |||
@@ -27,26 +27,14 @@ | |||
27 | 27 | ||
28 | /* Symlink caching in the page cache is even more simplistic | 28 | /* Symlink caching in the page cache is even more simplistic |
29 | * and straight-forward than readdir caching. | 29 | * and straight-forward than readdir caching. |
30 | * | ||
31 | * At the beginning of the page we store pointer to struct page in question, | ||
32 | * simplifying nfs_put_link() (if inode got invalidated we can't find the page | ||
33 | * to be freed via pagecache lookup). | ||
34 | * The NUL-terminated string follows immediately thereafter. | ||
35 | */ | 30 | */ |
36 | 31 | ||
37 | struct nfs_symlink { | ||
38 | struct page *page; | ||
39 | char body[0]; | ||
40 | }; | ||
41 | |||
42 | static int nfs_symlink_filler(struct inode *inode, struct page *page) | 32 | static int nfs_symlink_filler(struct inode *inode, struct page *page) |
43 | { | 33 | { |
44 | const unsigned int pgbase = offsetof(struct nfs_symlink, body); | ||
45 | const unsigned int pglen = PAGE_SIZE - pgbase; | ||
46 | int error; | 34 | int error; |
47 | 35 | ||
48 | lock_kernel(); | 36 | lock_kernel(); |
49 | error = NFS_PROTO(inode)->readlink(inode, page, pgbase, pglen); | 37 | error = NFS_PROTO(inode)->readlink(inode, page, 0, PAGE_SIZE); |
50 | unlock_kernel(); | 38 | unlock_kernel(); |
51 | if (error < 0) | 39 | if (error < 0) |
52 | goto error; | 40 | goto error; |
@@ -60,11 +48,10 @@ error: | |||
60 | return -EIO; | 48 | return -EIO; |
61 | } | 49 | } |
62 | 50 | ||
63 | static int nfs_follow_link(struct dentry *dentry, struct nameidata *nd) | 51 | static void *nfs_follow_link(struct dentry *dentry, struct nameidata *nd) |
64 | { | 52 | { |
65 | struct inode *inode = dentry->d_inode; | 53 | struct inode *inode = dentry->d_inode; |
66 | struct page *page; | 54 | struct page *page; |
67 | struct nfs_symlink *p; | ||
68 | void *err = ERR_PTR(nfs_revalidate_inode(NFS_SERVER(inode), inode)); | 55 | void *err = ERR_PTR(nfs_revalidate_inode(NFS_SERVER(inode), inode)); |
69 | if (err) | 56 | if (err) |
70 | goto read_failed; | 57 | goto read_failed; |
@@ -78,28 +65,20 @@ static int nfs_follow_link(struct dentry *dentry, struct nameidata *nd) | |||
78 | err = ERR_PTR(-EIO); | 65 | err = ERR_PTR(-EIO); |
79 | goto getlink_read_error; | 66 | goto getlink_read_error; |
80 | } | 67 | } |
81 | p = kmap(page); | 68 | nd_set_link(nd, kmap(page)); |
82 | p->page = page; | 69 | return page; |
83 | nd_set_link(nd, p->body); | ||
84 | return 0; | ||
85 | 70 | ||
86 | getlink_read_error: | 71 | getlink_read_error: |
87 | page_cache_release(page); | 72 | page_cache_release(page); |
88 | read_failed: | 73 | read_failed: |
89 | nd_set_link(nd, err); | 74 | nd_set_link(nd, err); |
90 | return 0; | 75 | return NULL; |
91 | } | 76 | } |
92 | 77 | ||
93 | static void nfs_put_link(struct dentry *dentry, struct nameidata *nd) | 78 | static void nfs_put_link(struct dentry *dentry, struct nameidata *nd, void *cookie) |
94 | { | 79 | { |
95 | char *s = nd_get_link(nd); | 80 | if (cookie) { |
96 | if (!IS_ERR(s)) { | 81 | struct page *page = cookie; |
97 | struct nfs_symlink *p; | ||
98 | struct page *page; | ||
99 | |||
100 | p = container_of(s, struct nfs_symlink, body[0]); | ||
101 | page = p->page; | ||
102 | |||
103 | kunmap(page); | 82 | kunmap(page); |
104 | page_cache_release(page); | 83 | page_cache_release(page); |
105 | } | 84 | } |
diff --git a/fs/sysfs/symlink.c b/fs/sysfs/symlink.c index fae57c83a722..de402fa915f2 100644 --- a/fs/sysfs/symlink.c +++ b/fs/sysfs/symlink.c | |||
@@ -151,17 +151,17 @@ static int sysfs_getlink(struct dentry *dentry, char * path) | |||
151 | 151 | ||
152 | } | 152 | } |
153 | 153 | ||
154 | static int sysfs_follow_link(struct dentry *dentry, struct nameidata *nd) | 154 | static void *sysfs_follow_link(struct dentry *dentry, struct nameidata *nd) |
155 | { | 155 | { |
156 | int error = -ENOMEM; | 156 | int error = -ENOMEM; |
157 | unsigned long page = get_zeroed_page(GFP_KERNEL); | 157 | unsigned long page = get_zeroed_page(GFP_KERNEL); |
158 | if (page) | 158 | if (page) |
159 | error = sysfs_getlink(dentry, (char *) page); | 159 | error = sysfs_getlink(dentry, (char *) page); |
160 | nd_set_link(nd, error ? ERR_PTR(error) : (char *)page); | 160 | nd_set_link(nd, error ? ERR_PTR(error) : (char *)page); |
161 | return 0; | 161 | return NULL; |
162 | } | 162 | } |
163 | 163 | ||
164 | static void sysfs_put_link(struct dentry *dentry, struct nameidata *nd) | 164 | static void sysfs_put_link(struct dentry *dentry, struct nameidata *nd, void *cookie) |
165 | { | 165 | { |
166 | char *page = nd_get_link(nd); | 166 | char *page = nd_get_link(nd); |
167 | if (!IS_ERR(page)) | 167 | if (!IS_ERR(page)) |