9pfs: only allow directories during walk As stated in the 9P spec at http://man.cat-v.org/plan_9/5/walk, the fid argument to the walk request "must represent a directory unless zero path name elements are specified" and the same "restriction" applies to all specified path name elements. This fixes a potential symlink attack from a malicious guest, reported by Jann Horn and described below: Signed-off-by: Greg Kurz --- hw/9pfs/9p.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/hw/9pfs/9p.c b/hw/9pfs/9p.c index fa58877570f6..675e8347aacc 100644 --- a/hw/9pfs/9p.c +++ b/hw/9pfs/9p.c @@ -553,6 +553,11 @@ static void coroutine_fn virtfs_reset(V9fsPDU *pdu) #define P9_QID_TYPE_DIR 0x80 #define P9_QID_TYPE_SYMLINK 0x02 +static bool qid_is_dir(V9fsQID *qidp) +{ + return qidp->type & P9_QID_TYPE_DIR; +} + #define P9_STAT_MODE_DIR 0x80000000 #define P9_STAT_MODE_APPEND 0x40000000 #define P9_STAT_MODE_EXCL 0x20000000 @@ -1331,6 +1336,11 @@ static void coroutine_fn v9fs_walk(void *opaque) goto out; } + if (nwnames && !qid_is_dir(&qid)) { + err = -ENOTDIR; + goto out; + } + /* * Both dpath and path initially poin to fidp. * Needed to handle request with nwnames == 0 @@ -1351,6 +1361,10 @@ static void coroutine_fn v9fs_walk(void *opaque) goto out; } stat_to_qid(&stbuf, &qid); + if ((name_idx + 1) < nwnames && !qid_is_dir(&qid)) { + err = -ENOTDIR; + goto out; + } v9fs_path_copy(&dpath, &path); } memcpy(&qids[name_idx], &qid, sizeof(qid));