[PATCH] fuse: fix Oops in lookup
Fix bug in certain error paths of lookup routines. The request object was reused for sending FORGET, which is illegal. This bug could cause an Oops in 2.6.18. In earlier versions it might silently corrupt memory, but this is very unlikely. These error paths are never triggered by libfuse, so this wasn't noticed even with the 2.6.18 kernel, only with a filesystem using the raw kernel interface. Thanks to Russ Cox for the bug report and test filesystem. Signed-off-by: Miklos Szeredi <miklos@szeredi.hu> Cc: <stable@kernel.org> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
This commit is contained in:
parent
a26d79ca81
commit
2d51013ed2
1 changed files with 38 additions and 14 deletions
|
@ -138,6 +138,7 @@ static int fuse_dentry_revalidate(struct dentry *entry, struct nameidata *nd)
|
|||
struct fuse_entry_out outarg;
|
||||
struct fuse_conn *fc;
|
||||
struct fuse_req *req;
|
||||
struct fuse_req *forget_req;
|
||||
struct dentry *parent;
|
||||
|
||||
/* Doesn't hurt to "reset" the validity timeout */
|
||||
|
@ -152,25 +153,33 @@ static int fuse_dentry_revalidate(struct dentry *entry, struct nameidata *nd)
|
|||
if (IS_ERR(req))
|
||||
return 0;
|
||||
|
||||
forget_req = fuse_get_req(fc);
|
||||
if (IS_ERR(forget_req)) {
|
||||
fuse_put_request(fc, req);
|
||||
return 0;
|
||||
}
|
||||
|
||||
parent = dget_parent(entry);
|
||||
fuse_lookup_init(req, parent->d_inode, entry, &outarg);
|
||||
request_send(fc, req);
|
||||
dput(parent);
|
||||
err = req->out.h.error;
|
||||
fuse_put_request(fc, req);
|
||||
/* Zero nodeid is same as -ENOENT */
|
||||
if (!err && !outarg.nodeid)
|
||||
err = -ENOENT;
|
||||
if (!err) {
|
||||
struct fuse_inode *fi = get_fuse_inode(inode);
|
||||
if (outarg.nodeid != get_node_id(inode)) {
|
||||
fuse_send_forget(fc, req, outarg.nodeid, 1);
|
||||
fuse_send_forget(fc, forget_req,
|
||||
outarg.nodeid, 1);
|
||||
return 0;
|
||||
}
|
||||
spin_lock(&fc->lock);
|
||||
fi->nlookup ++;
|
||||
spin_unlock(&fc->lock);
|
||||
}
|
||||
fuse_put_request(fc, req);
|
||||
fuse_put_request(fc, forget_req);
|
||||
if (err || (outarg.attr.mode ^ inode->i_mode) & S_IFMT)
|
||||
return 0;
|
||||
|
||||
|
@ -221,6 +230,7 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry,
|
|||
struct inode *inode = NULL;
|
||||
struct fuse_conn *fc = get_fuse_conn(dir);
|
||||
struct fuse_req *req;
|
||||
struct fuse_req *forget_req;
|
||||
|
||||
if (entry->d_name.len > FUSE_NAME_MAX)
|
||||
return ERR_PTR(-ENAMETOOLONG);
|
||||
|
@ -229,9 +239,16 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry,
|
|||
if (IS_ERR(req))
|
||||
return ERR_PTR(PTR_ERR(req));
|
||||
|
||||
forget_req = fuse_get_req(fc);
|
||||
if (IS_ERR(forget_req)) {
|
||||
fuse_put_request(fc, req);
|
||||
return ERR_PTR(PTR_ERR(forget_req));
|
||||
}
|
||||
|
||||
fuse_lookup_init(req, dir, entry, &outarg);
|
||||
request_send(fc, req);
|
||||
err = req->out.h.error;
|
||||
fuse_put_request(fc, req);
|
||||
/* Zero nodeid is same as -ENOENT, but with valid timeout */
|
||||
if (!err && outarg.nodeid &&
|
||||
(invalid_nodeid(outarg.nodeid) || !valid_mode(outarg.attr.mode)))
|
||||
|
@ -240,11 +257,11 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry,
|
|||
inode = fuse_iget(dir->i_sb, outarg.nodeid, outarg.generation,
|
||||
&outarg.attr);
|
||||
if (!inode) {
|
||||
fuse_send_forget(fc, req, outarg.nodeid, 1);
|
||||
fuse_send_forget(fc, forget_req, outarg.nodeid, 1);
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
}
|
||||
fuse_put_request(fc, req);
|
||||
fuse_put_request(fc, forget_req);
|
||||
if (err && err != -ENOENT)
|
||||
return ERR_PTR(err);
|
||||
|
||||
|
@ -388,6 +405,13 @@ static int create_new_entry(struct fuse_conn *fc, struct fuse_req *req,
|
|||
struct fuse_entry_out outarg;
|
||||
struct inode *inode;
|
||||
int err;
|
||||
struct fuse_req *forget_req;
|
||||
|
||||
forget_req = fuse_get_req(fc);
|
||||
if (IS_ERR(forget_req)) {
|
||||
fuse_put_request(fc, req);
|
||||
return PTR_ERR(forget_req);
|
||||
}
|
||||
|
||||
req->in.h.nodeid = get_node_id(dir);
|
||||
req->out.numargs = 1;
|
||||
|
@ -395,24 +419,24 @@ static int create_new_entry(struct fuse_conn *fc, struct fuse_req *req,
|
|||
req->out.args[0].value = &outarg;
|
||||
request_send(fc, req);
|
||||
err = req->out.h.error;
|
||||
if (err) {
|
||||
fuse_put_request(fc, req);
|
||||
return err;
|
||||
}
|
||||
fuse_put_request(fc, req);
|
||||
if (err)
|
||||
goto out_put_forget_req;
|
||||
|
||||
err = -EIO;
|
||||
if (invalid_nodeid(outarg.nodeid))
|
||||
goto out_put_request;
|
||||
goto out_put_forget_req;
|
||||
|
||||
if ((outarg.attr.mode ^ mode) & S_IFMT)
|
||||
goto out_put_request;
|
||||
goto out_put_forget_req;
|
||||
|
||||
inode = fuse_iget(dir->i_sb, outarg.nodeid, outarg.generation,
|
||||
&outarg.attr);
|
||||
if (!inode) {
|
||||
fuse_send_forget(fc, req, outarg.nodeid, 1);
|
||||
fuse_send_forget(fc, forget_req, outarg.nodeid, 1);
|
||||
return -ENOMEM;
|
||||
}
|
||||
fuse_put_request(fc, req);
|
||||
fuse_put_request(fc, forget_req);
|
||||
|
||||
if (S_ISDIR(inode->i_mode)) {
|
||||
struct dentry *alias;
|
||||
|
@ -434,8 +458,8 @@ static int create_new_entry(struct fuse_conn *fc, struct fuse_req *req,
|
|||
fuse_invalidate_attr(dir);
|
||||
return 0;
|
||||
|
||||
out_put_request:
|
||||
fuse_put_request(fc, req);
|
||||
out_put_forget_req:
|
||||
fuse_put_request(fc, forget_req);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue