From ba413ab2ccc49e6aa1ebc3523a75635b5e4a0494 Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Tue, 21 Aug 2012 09:20:49 +0200 Subject: [PATCH 1/7] fs/9p: avoid debug OOPS when reading a long symlink Reading a symlink longer than the given buffer, a p9_debug use would try to print the link name (not NUL-terminated) using a %s format. Use %.*s instead, and replace the strncpy+strnlen with functionally equivalent strlen+memcpy. Signed-off-by: Jim Meyering Signed-off-by: Eric Van Hensbergen --- fs/9p/vfs_inode.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index cbf9dbb1b2a..890bed538f9 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -1276,12 +1276,12 @@ static int v9fs_readlink(struct dentry *dentry, char *buffer, int buflen) } /* copy extension buffer into buffer */ - strncpy(buffer, st->extension, buflen); + retval = min(strlen(st->extension)+1, (size_t)buflen); + memcpy(buffer, st->extension, retval); - p9_debug(P9_DEBUG_VFS, "%s -> %s (%s)\n", - dentry->d_name.name, st->extension, buffer); + p9_debug(P9_DEBUG_VFS, "%s -> %s (%.*s)\n", + dentry->d_name.name, st->extension, buflen, buffer); - retval = strnlen(buffer, buflen); done: p9stat_free(st); kfree(st); From 43def35c1030d91a7414936c7c1b416828b20afb Mon Sep 17 00:00:00 2001 From: Simon Derr Date: Fri, 10 Aug 2012 15:52:06 +0200 Subject: [PATCH 2/7] net/9p: Check errno validity While working on a modified server I had the Linux clients crash a few times. This lead me to find this: Some error codes are directly extracted from the server replies. A malformed server reply could contain an invalid error code, with a very large value. If this value is then passed to ERR_PTR() it will not be properly detected as an error code by IS_ERR() and as a result the kernel will dereference an invalid pointer. This patch tries to avoid this. Signed-off-by: Simon Derr Signed-off-by: Eric Van Hensbergen --- net/9p/client.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/net/9p/client.c b/net/9p/client.c index 8260f132b32..34d41767093 100644 --- a/net/9p/client.c +++ b/net/9p/client.c @@ -76,6 +76,20 @@ inline int p9_is_proto_dotu(struct p9_client *clnt) } EXPORT_SYMBOL(p9_is_proto_dotu); +/* + * Some error codes are taken directly from the server replies, + * make sure they are valid. + */ +static int safe_errno(int err) +{ + if ((err > 0) || (err < -MAX_ERRNO)) { + p9_debug(P9_DEBUG_ERROR, "Invalid error code %d\n", err); + return -EPROTO; + } + return err; +} + + /* Interpret mount option for protocol version */ static int get_protocol_version(char *s) { @@ -782,7 +796,7 @@ again: return req; reterr: p9_free_req(c, req); - return ERR_PTR(err); + return ERR_PTR(safe_errno(err)); } /** @@ -865,7 +879,7 @@ static struct p9_req_t *p9_client_zc_rpc(struct p9_client *c, int8_t type, return req; reterr: p9_free_req(c, req); - return ERR_PTR(err); + return ERR_PTR(safe_errno(err)); } static struct p9_fid *p9_fid_create(struct p9_client *clnt) From e549c1337133e85a6d6bc868d2d141a61a80975c Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Fri, 7 Sep 2012 07:55:25 -0400 Subject: [PATCH 3/7] 9p: don't use __getname/__putname for uname/aname These are generally very small strings. We don't need an entire 4k allocation for each. Instead, just free and reallocate them on an as-needed basis. Note: This patch is untested since I don't have a 9p server available at the moment. It's mainly something I noticed while doing some getname/putname cleanup work. Signed-off-by: Jeff Layton Signed-off-by: Eric Van Hensbergen --- fs/9p/v9fs.c | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/fs/9p/v9fs.c b/fs/9p/v9fs.c index b85efa77394..b20af746578 100644 --- a/fs/9p/v9fs.c +++ b/fs/9p/v9fs.c @@ -184,10 +184,20 @@ static int v9fs_parse_options(struct v9fs_session_info *v9ses, char *opts) v9ses->afid = option; break; case Opt_uname: - match_strlcpy(v9ses->uname, &args[0], PATH_MAX); + kfree(v9ses->uname); + v9ses->uname = match_strdup(&args[0]); + if (!v9ses->uname) { + ret = -ENOMEM; + goto free_and_return; + } break; case Opt_remotename: - match_strlcpy(v9ses->aname, &args[0], PATH_MAX); + kfree(v9ses->aname); + v9ses->aname = match_strdup(&args[0]); + if (!v9ses->aname) { + ret = -ENOMEM; + goto free_and_return; + } break; case Opt_nodevmap: v9ses->nodev = 1; @@ -287,21 +297,21 @@ struct p9_fid *v9fs_session_init(struct v9fs_session_info *v9ses, struct p9_fid *fid; int rc; - v9ses->uname = __getname(); + v9ses->uname = kstrdup(V9FS_DEFUSER, GFP_KERNEL); if (!v9ses->uname) return ERR_PTR(-ENOMEM); - v9ses->aname = __getname(); + v9ses->aname = kstrdup(V9FS_DEFANAME, GFP_KERNEL); if (!v9ses->aname) { - __putname(v9ses->uname); + kfree(v9ses->uname); return ERR_PTR(-ENOMEM); } init_rwsem(&v9ses->rename_sem); rc = bdi_setup_and_register(&v9ses->bdi, "9p", BDI_CAP_MAP_COPY); if (rc) { - __putname(v9ses->aname); - __putname(v9ses->uname); + kfree(v9ses->aname); + kfree(v9ses->uname); return ERR_PTR(rc); } @@ -309,8 +319,6 @@ struct p9_fid *v9fs_session_init(struct v9fs_session_info *v9ses, list_add(&v9ses->slist, &v9fs_sessionlist); spin_unlock(&v9fs_sessionlist_lock); - strcpy(v9ses->uname, V9FS_DEFUSER); - strcpy(v9ses->aname, V9FS_DEFANAME); v9ses->uid = ~0; v9ses->dfltuid = V9FS_DEFUID; v9ses->dfltgid = V9FS_DEFGID; @@ -412,8 +420,8 @@ void v9fs_session_close(struct v9fs_session_info *v9ses) kfree(v9ses->cachetag); } #endif - __putname(v9ses->uname); - __putname(v9ses->aname); + kfree(v9ses->uname); + kfree(v9ses->aname); bdi_destroy(&v9ses->bdi); From 0462194d358c2e040282d4d1a4fd1aab84417e42 Mon Sep 17 00:00:00 2001 From: Simon Derr Date: Mon, 17 Sep 2012 15:16:28 +0200 Subject: [PATCH 4/7] 9P: Fix race in p9_read_work() Race scenario between p9_read_work() and p9_poll_mux() Data arrive, Rworksched is set, p9_read_work() is called. thread A thread B p9_read_work() . reads data . checks if new data ready. No. . gets preempted . More data arrive, p9_poll_mux() is called. . . . p9_poll_mux() . . if (!test_and_set_bit(Rworksched, . &m->wsched)) { . schedule_work(&m->rq); . } . . -> does not schedule work because . Rworksched is set . . clear_bit(Rworksched, &m->wsched); return; No work has been scheduled, and yet data are waiting. Currently p9_read_work() checks if there is data to read, and if not, it clears Rworksched. I think it should clear Rworksched first, and then check if there is data to read. Signed-off-by: Simon Derr Signed-off-by: Eric Van Hensbergen --- net/9p/trans_fd.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/net/9p/trans_fd.c b/net/9p/trans_fd.c index 6449bae1570..de1bbad0c7d 100644 --- a/net/9p/trans_fd.c +++ b/net/9p/trans_fd.c @@ -316,8 +316,7 @@ static void p9_read_work(struct work_struct *work) m->rsize - m->rpos); p9_debug(P9_DEBUG_TRANS, "mux %p got %d bytes\n", m, err); if (err == -EAGAIN) { - clear_bit(Rworksched, &m->wsched); - return; + goto end_clear; } if (err <= 0) @@ -379,19 +378,20 @@ static void p9_read_work(struct work_struct *work) m->req = NULL; } +end_clear: + clear_bit(Rworksched, &m->wsched); + if (!list_empty(&m->req_list)) { if (test_and_clear_bit(Rpending, &m->wsched)) n = POLLIN; else n = p9_fd_poll(m->client, NULL); - if (n & POLLIN) { + if ((n & POLLIN) && !test_and_set_bit(Rworksched, &m->wsched)) { p9_debug(P9_DEBUG_TRANS, "sched read work %p\n", m); schedule_work(&m->rq); - } else - clear_bit(Rworksched, &m->wsched); - } else - clear_bit(Rworksched, &m->wsched); + } + } return; error: From 1957b3a86f8eb5ceab32e3aae99e2822258aa530 Mon Sep 17 00:00:00 2001 From: Simon Derr Date: Mon, 17 Sep 2012 15:16:29 +0200 Subject: [PATCH 5/7] 9P: fix test at the end of p9_write_work() At the end of p9_write_work() we want to test if there is still data to send. This means: - either the current request still has data to send (wsize != 0) - or there are requests in the unsent queue Signed-off-by: Simon Derr Signed-off-by: Eric Van Hensbergen --- net/9p/trans_fd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/9p/trans_fd.c b/net/9p/trans_fd.c index de1bbad0c7d..7088a94b260 100644 --- a/net/9p/trans_fd.c +++ b/net/9p/trans_fd.c @@ -492,7 +492,7 @@ static void p9_write_work(struct work_struct *work) if (m->wpos == m->wsize) m->wpos = m->wsize = 0; - if (m->wsize == 0 && !list_empty(&m->unsent_req_list)) { + if (m->wsize || !list_empty(&m->unsent_req_list)) { if (test_and_clear_bit(Wpending, &m->wsched)) n = POLLOUT; else From 584a8c13d58423462680907d4cc40d9929c9030a Mon Sep 17 00:00:00 2001 From: Simon Derr Date: Mon, 17 Sep 2012 15:16:30 +0200 Subject: [PATCH 6/7] 9P: Fix race in p9_write_work() See previous commit about p9_read_work() for details. This fixes a similar race between p9_write_work() and p9_poll_mux() Signed-off-by: Simon Derr Signed-off-by: Eric Van Hensbergen --- net/9p/trans_fd.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/net/9p/trans_fd.c b/net/9p/trans_fd.c index 7088a94b260..b2c308fffb8 100644 --- a/net/9p/trans_fd.c +++ b/net/9p/trans_fd.c @@ -476,10 +476,9 @@ static void p9_write_work(struct work_struct *work) clear_bit(Wpending, &m->wsched); err = p9_fd_write(m->client, m->wbuf + m->wpos, m->wsize - m->wpos); p9_debug(P9_DEBUG_TRANS, "mux %p sent %d bytes\n", m, err); - if (err == -EAGAIN) { - clear_bit(Wworksched, &m->wsched); - return; - } + if (err == -EAGAIN) + goto end_clear; + if (err < 0) goto error; @@ -492,19 +491,21 @@ static void p9_write_work(struct work_struct *work) if (m->wpos == m->wsize) m->wpos = m->wsize = 0; +end_clear: + clear_bit(Wworksched, &m->wsched); + if (m->wsize || !list_empty(&m->unsent_req_list)) { if (test_and_clear_bit(Wpending, &m->wsched)) n = POLLOUT; else n = p9_fd_poll(m->client, NULL); - if (n & POLLOUT) { + if ((n & POLLOUT) && + !test_and_set_bit(Wworksched, &m->wsched)) { p9_debug(P9_DEBUG_TRANS, "sched write work %p\n", m); schedule_work(&m->wq); - } else - clear_bit(Wworksched, &m->wsched); - } else - clear_bit(Wworksched, &m->wsched); + } + } return; From 759f42987f98915764bad922ee123acb0eadbe33 Mon Sep 17 00:00:00 2001 From: Simon Derr Date: Mon, 17 Sep 2012 15:16:31 +0200 Subject: [PATCH 7/7] 9P: Fix race between p9_write_work() and p9_fd_request() Race scenario: thread A thread B p9_write_work() p9_fd_request() if (list_empty (&m->unsent_req_list)) ... spin_lock(&client->lock); req->status = REQ_STATUS_UNSENT; list_add_tail(..., &m->unsent_req_list); spin_unlock(&client->lock); .... if (n & POLLOUT && !test_and_set_bit(Wworksched, &m->wsched) schedule_work(&m->wq); --> not done because Wworksched is set clear_bit(Wworksched, &m->wsched); return; --> nobody will take care of sending the new request. This is not very likely to happen though, because p9_write_work() being called with an empty unsent_req_list is not frequent. But this also means that taking the lock earlier will not be costly. Signed-off-by: Simon Derr Signed-off-by: Eric Van Hensbergen --- net/9p/trans_fd.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/9p/trans_fd.c b/net/9p/trans_fd.c index b2c308fffb8..0031a8cf145 100644 --- a/net/9p/trans_fd.c +++ b/net/9p/trans_fd.c @@ -453,12 +453,13 @@ static void p9_write_work(struct work_struct *work) } if (!m->wsize) { + spin_lock(&m->client->lock); if (list_empty(&m->unsent_req_list)) { clear_bit(Wworksched, &m->wsched); + spin_unlock(&m->client->lock); return; } - spin_lock(&m->client->lock); req = list_entry(m->unsent_req_list.next, struct p9_req_t, req_list); req->status = REQ_STATUS_SENT;