diff mbox

9pfs: disallow / in path components

Message ID 147204811781.25757.13905475486785615296.stgit@bahia.lan (mailing list archive)
State New, archived
Headers show

Commit Message

Greg Kurz Aug. 24, 2016, 2:29 p.m. UTC
At various places in 9pfs, full paths are created by concatenating a guest
originated string to the export path. A malicious guest could forge a
relative path and access files outside the export path.

A tentative fix was sent recently by Prasad J Pandit, but it was only
focused on the local backend and did not get a positive review. This patch
tries to address the issue more globally, based on the official 9P spec.

The walk request described in the 9P spec [1] clearly shows that the client
is supposed to send individual path components: the official linux client
never sends portions of path containing the / character for example.

Moreover, the 9P spec [2] also states that a system can decide to restrict
the set of supported characters used in path components, with an explicit
mention "to remove slashes from name components".

This patch introduces a new name_has_illegal_characters() helper that looks
for such unwanted characters in strings sent by the client. Since 9pfs is
only supported on linux hosts, only the / character is checked at the
moment. When support for other hosts (AKA. win32) is added, other chars
may need to be blacklisted as well.

If a client sends a path component with an illegal character, the request
will fail and EINVAL is returned to the client.

For the sake of simplicity and consistency, the check is done at top-level
for all affected 9P requests:

- xattrwalk
- xattrcreate
- mknod
- rename
- renameat
- unlinkat
- mkdir
- walk
- link
- symlink
- create
- lcreate

[1] http://man.cat-v.org/plan_9/5/walk
[2] http://man.cat-v.org/plan_9/5/intro

Reported-by: Felix Wilhelm <fwilhelm@ernw.de>
Suggested-by: Peter Maydell <peter.maydell@linaro.org>
Signed-off-by: Greg Kurz <groug@kaod.org>
---

Since the linux client does not send / in path components and I don't
have enough time to write an appropriate qtest, I choosed to do manual
testing of the mkdir request with GDB:

[greg@vm66 host]$ mkdir ...foo
(then turning ...foo into ../foo in QEMU with GDB)
mkdir: cannot create directory ‘...foo’: Invalid argument

I also could run the POSIX file system test suite from TUXERA:

http://www.tuxera.com/community/open-source-posix/

and did not observe any regression with this patch.

 hw/9pfs/9p.c |   67 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 67 insertions(+)

Comments

Peter Maydell Aug. 24, 2016, 3 p.m. UTC | #1
On 24 August 2016 at 15:29, Greg Kurz <groug@kaod.org> wrote:
> At various places in 9pfs, full paths are created by concatenating a guest
> originated string to the export path. A malicious guest could forge a
> relative path and access files outside the export path.
>
> A tentative fix was sent recently by Prasad J Pandit, but it was only
> focused on the local backend and did not get a positive review. This patch
> tries to address the issue more globally, based on the official 9P spec.
>
> The walk request described in the 9P spec [1] clearly shows that the client
> is supposed to send individual path components: the official linux client
> never sends portions of path containing the / character for example.
>
> Moreover, the 9P spec [2] also states that a system can decide to restrict
> the set of supported characters used in path components, with an explicit
> mention "to remove slashes from name components".
>
> This patch introduces a new name_has_illegal_characters() helper that looks
> for such unwanted characters in strings sent by the client. Since 9pfs is
> only supported on linux hosts, only the / character is checked at the
> moment. When support for other hosts (AKA. win32) is added, other chars
> may need to be blacklisted as well.

Do we also need ".." and "." to be illegal names (for at least most
operations)?

thanks
-- PMM
Michael S. Tsirkin Aug. 24, 2016, 3:46 p.m. UTC | #2
On Wed, Aug 24, 2016 at 04:00:24PM +0100, Peter Maydell wrote:
> On 24 August 2016 at 15:29, Greg Kurz <groug@kaod.org> wrote:
> > At various places in 9pfs, full paths are created by concatenating a guest
> > originated string to the export path. A malicious guest could forge a
> > relative path and access files outside the export path.
> >
> > A tentative fix was sent recently by Prasad J Pandit, but it was only
> > focused on the local backend and did not get a positive review. This patch
> > tries to address the issue more globally, based on the official 9P spec.
> >
> > The walk request described in the 9P spec [1] clearly shows that the client
> > is supposed to send individual path components: the official linux client
> > never sends portions of path containing the / character for example.
> >
> > Moreover, the 9P spec [2] also states that a system can decide to restrict
> > the set of supported characters used in path components, with an explicit
> > mention "to remove slashes from name components".
> >
> > This patch introduces a new name_has_illegal_characters() helper that looks
> > for such unwanted characters in strings sent by the client. Since 9pfs is
> > only supported on linux hosts, only the / character is checked at the
> > moment. When support for other hosts (AKA. win32) is added, other chars
> > may need to be blacklisted as well.
> 
> Do we also need ".." and "." to be illegal names (for at least most
> operations)?
> 
> thanks
> -- PMM

I agree, and I think this implies name_is_legal would be a better function name.
Greg Kurz Aug. 24, 2016, 4:40 p.m. UTC | #3
On Wed, 24 Aug 2016 16:00:24 +0100
Peter Maydell <peter.maydell@linaro.org> wrote:

> On 24 August 2016 at 15:29, Greg Kurz <groug@kaod.org> wrote:
> > At various places in 9pfs, full paths are created by concatenating a guest
> > originated string to the export path. A malicious guest could forge a
> > relative path and access files outside the export path.
> >
> > A tentative fix was sent recently by Prasad J Pandit, but it was only
> > focused on the local backend and did not get a positive review. This patch
> > tries to address the issue more globally, based on the official 9P spec.
> >
> > The walk request described in the 9P spec [1] clearly shows that the client
> > is supposed to send individual path components: the official linux client
> > never sends portions of path containing the / character for example.
> >
> > Moreover, the 9P spec [2] also states that a system can decide to restrict
> > the set of supported characters used in path components, with an explicit
> > mention "to remove slashes from name components".
> >
> > This patch introduces a new name_has_illegal_characters() helper that looks
> > for such unwanted characters in strings sent by the client. Since 9pfs is
> > only supported on linux hosts, only the / character is checked at the
> > moment. When support for other hosts (AKA. win32) is added, other chars
> > may need to be blacklisted as well.  
> 
> Do we also need ".." and "." to be illegal names (for at least most
> operations)?
> 
> thanks
> -- PMM

I understand how ".." could be an issue, but I don't for "."... can you
please elaborate ?

The spec says:

          Directories are created by create with DMDIR set in the per-
          missions argument (see stat(5)). The members of a directory
          can be found with read(5). All directories must support
          walks to the directory .. (dot-dot) meaning parent direc-
          tory, although by convention directories contain no explicit
          entry for .. or . (dot).  The parent of the root directory
          of a server's tree is itself.

So I don't think we should boldly make ".." an illegal name, but
rather ignore it. Pretty much like doing chdir("..") when the current
directory is /.

All operations except walk take an existing fid and a single path component.
A possible fix would be to convert ".." to "" when the fid points to the root
of the export path. Makes sense ?

Since the walk operation takes an array of path components, we would need
to do extend the above check to each component.

Cheers.

--
Greg
Greg Kurz Aug. 24, 2016, 4:41 p.m. UTC | #4
On Wed, 24 Aug 2016 18:46:10 +0300
"Michael S. Tsirkin" <mst@redhat.com> wrote:

> On Wed, Aug 24, 2016 at 04:00:24PM +0100, Peter Maydell wrote:
> > On 24 August 2016 at 15:29, Greg Kurz <groug@kaod.org> wrote:  
> > > At various places in 9pfs, full paths are created by concatenating a guest
> > > originated string to the export path. A malicious guest could forge a
> > > relative path and access files outside the export path.
> > >
> > > A tentative fix was sent recently by Prasad J Pandit, but it was only
> > > focused on the local backend and did not get a positive review. This patch
> > > tries to address the issue more globally, based on the official 9P spec.
> > >
> > > The walk request described in the 9P spec [1] clearly shows that the client
> > > is supposed to send individual path components: the official linux client
> > > never sends portions of path containing the / character for example.
> > >
> > > Moreover, the 9P spec [2] also states that a system can decide to restrict
> > > the set of supported characters used in path components, with an explicit
> > > mention "to remove slashes from name components".
> > >
> > > This patch introduces a new name_has_illegal_characters() helper that looks
> > > for such unwanted characters in strings sent by the client. Since 9pfs is
> > > only supported on linux hosts, only the / character is checked at the
> > > moment. When support for other hosts (AKA. win32) is added, other chars
> > > may need to be blacklisted as well.  
> > 
> > Do we also need ".." and "." to be illegal names (for at least most
> > operations)?
> > 
> > thanks
> > -- PMM  
> 
> I agree, and I think this implies name_is_legal would be a better function name.
> 

No I think this is a different issue that calls for a followup patch (see my
other mail).
Michael S. Tsirkin Aug. 24, 2016, 6:23 p.m. UTC | #5
On Wed, Aug 24, 2016 at 06:41:45PM +0200, Greg Kurz wrote:
> On Wed, 24 Aug 2016 18:46:10 +0300
> "Michael S. Tsirkin" <mst@redhat.com> wrote:
> 
> > On Wed, Aug 24, 2016 at 04:00:24PM +0100, Peter Maydell wrote:
> > > On 24 August 2016 at 15:29, Greg Kurz <groug@kaod.org> wrote:  
> > > > At various places in 9pfs, full paths are created by concatenating a guest
> > > > originated string to the export path. A malicious guest could forge a
> > > > relative path and access files outside the export path.
> > > >
> > > > A tentative fix was sent recently by Prasad J Pandit, but it was only
> > > > focused on the local backend and did not get a positive review. This patch
> > > > tries to address the issue more globally, based on the official 9P spec.
> > > >
> > > > The walk request described in the 9P spec [1] clearly shows that the client
> > > > is supposed to send individual path components: the official linux client
> > > > never sends portions of path containing the / character for example.
> > > >
> > > > Moreover, the 9P spec [2] also states that a system can decide to restrict
> > > > the set of supported characters used in path components, with an explicit
> > > > mention "to remove slashes from name components".
> > > >
> > > > This patch introduces a new name_has_illegal_characters() helper that looks
> > > > for such unwanted characters in strings sent by the client. Since 9pfs is
> > > > only supported on linux hosts, only the / character is checked at the
> > > > moment. When support for other hosts (AKA. win32) is added, other chars
> > > > may need to be blacklisted as well.  
> > > 
> > > Do we also need ".." and "." to be illegal names (for at least most
> > > operations)?
> > > 
> > > thanks
> > > -- PMM  
> > 
> > I agree, and I think this implies name_is_legal would be a better function name.
> > 
> 
> No I think this is a different issue that calls for a followup patch (see my
> other mail).

OK, that's fine.
Michael S. Tsirkin Aug. 24, 2016, 6:23 p.m. UTC | #6
On Wed, Aug 24, 2016 at 04:29:07PM +0200, Greg Kurz wrote:
> At various places in 9pfs, full paths are created by concatenating a guest
> originated string to the export path. A malicious guest could forge a
> relative path and access files outside the export path.
> 
> A tentative fix was sent recently by Prasad J Pandit, but it was only
> focused on the local backend and did not get a positive review. This patch
> tries to address the issue more globally, based on the official 9P spec.
> 
> The walk request described in the 9P spec [1] clearly shows that the client
> is supposed to send individual path components: the official linux client
> never sends portions of path containing the / character for example.
> 
> Moreover, the 9P spec [2] also states that a system can decide to restrict
> the set of supported characters used in path components, with an explicit
> mention "to remove slashes from name components".
> 
> This patch introduces a new name_has_illegal_characters() helper that looks
> for such unwanted characters in strings sent by the client. Since 9pfs is
> only supported on linux hosts, only the / character is checked at the
> moment. When support for other hosts (AKA. win32) is added, other chars
> may need to be blacklisted as well.
> 
> If a client sends a path component with an illegal character, the request
> will fail and EINVAL is returned to the client.
> 
> For the sake of simplicity and consistency, the check is done at top-level
> for all affected 9P requests:
> 
> - xattrwalk
> - xattrcreate
> - mknod
> - rename
> - renameat
> - unlinkat
> - mkdir
> - walk
> - link
> - symlink
> - create
> - lcreate
> 
> [1] http://man.cat-v.org/plan_9/5/walk
> [2] http://man.cat-v.org/plan_9/5/intro
> 
> Reported-by: Felix Wilhelm <fwilhelm@ernw.de>
> Suggested-by: Peter Maydell <peter.maydell@linaro.org>
> Signed-off-by: Greg Kurz <groug@kaod.org>


Acked-by: Michael S. Tsirkin <mst@redhat.com>

> ---
> 
> Since the linux client does not send / in path components and I don't
> have enough time to write an appropriate qtest, I choosed to do manual
> testing of the mkdir request with GDB:
> 
> [greg@vm66 host]$ mkdir ...foo
> (then turning ...foo into ../foo in QEMU with GDB)
> mkdir: cannot create directory ‘...foo’: Invalid argument
> 
> I also could run the POSIX file system test suite from TUXERA:
> 
> http://www.tuxera.com/community/open-source-posix/
> 
> and did not observe any regression with this patch.
> 
>  hw/9pfs/9p.c |   67 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 67 insertions(+)
> 
> diff --git a/hw/9pfs/9p.c b/hw/9pfs/9p.c
> index b6b02b46a9da..1c008814509c 100644
> --- a/hw/9pfs/9p.c
> +++ b/hw/9pfs/9p.c
> @@ -1256,6 +1256,11 @@ static int v9fs_walk_marshal(V9fsPDU *pdu, uint16_t nwnames, V9fsQID *qids)
>      return offset;
>  }
>  
> +static bool name_has_illegal_characters(const char *name)
> +{
> +    return strchr(name, '/') != NULL;
> +}
> +
>  static void v9fs_walk(void *opaque)
>  {
>      int name_idx;
> @@ -1289,6 +1294,10 @@ static void v9fs_walk(void *opaque)
>              if (err < 0) {
>                  goto out_nofid;
>              }
> +            if (name_has_illegal_characters(wnames[i].data)) {
> +                err = -EINVAL;
> +                goto out_nofid;
> +            }
>              offset += err;
>          }
>      } else if (nwnames > P9_MAXWELEM) {
> @@ -1483,6 +1492,11 @@ static void v9fs_lcreate(void *opaque)
>      }
>      trace_v9fs_lcreate(pdu->tag, pdu->id, dfid, flags, mode, gid);
>  
> +    if (name_has_illegal_characters(name.data)) {
> +        err = -EINVAL;
> +        goto out_nofid;
> +    }
> +
>      fidp = get_fid(pdu, dfid);
>      if (fidp == NULL) {
>          err = -ENOENT;
> @@ -2077,6 +2091,11 @@ static void v9fs_create(void *opaque)
>      }
>      trace_v9fs_create(pdu->tag, pdu->id, fid, name.data, perm, mode);
>  
> +    if (name_has_illegal_characters(name.data)) {
> +        err = -EINVAL;
> +        goto out_nofid;
> +    }
> +
>      fidp = get_fid(pdu, fid);
>      if (fidp == NULL) {
>          err = -EINVAL;
> @@ -2242,6 +2261,11 @@ static void v9fs_symlink(void *opaque)
>      }
>      trace_v9fs_symlink(pdu->tag, pdu->id, dfid, name.data, symname.data, gid);
>  
> +    if (name_has_illegal_characters(symname.data)) {
> +        err = -EINVAL;
> +        goto out_nofid;
> +    }
> +
>      dfidp = get_fid(pdu, dfid);
>      if (dfidp == NULL) {
>          err = -EINVAL;
> @@ -2316,6 +2340,11 @@ static void v9fs_link(void *opaque)
>      }
>      trace_v9fs_link(pdu->tag, pdu->id, dfid, oldfid, name.data);
>  
> +    if (name_has_illegal_characters(name.data)) {
> +        err = -EINVAL;
> +        goto out_nofid;
> +    }
> +
>      dfidp = get_fid(pdu, dfid);
>      if (dfidp == NULL) {
>          err = -ENOENT;
> @@ -2398,6 +2427,12 @@ static void v9fs_unlinkat(void *opaque)
>      if (err < 0) {
>          goto out_nofid;
>      }
> +
> +    if (name_has_illegal_characters(name.data)) {
> +        err = -EINVAL;
> +        goto out_nofid;
> +    }
> +
>      dfidp = get_fid(pdu, dfid);
>      if (dfidp == NULL) {
>          err = -EINVAL;
> @@ -2504,6 +2539,12 @@ static void v9fs_rename(void *opaque)
>      if (err < 0) {
>          goto out_nofid;
>      }
> +
> +    if (name_has_illegal_characters(name.data)) {
> +        err = -EINVAL;
> +        goto out_nofid;
> +    }
> +
>      fidp = get_fid(pdu, fid);
>      if (fidp == NULL) {
>          err = -ENOENT;
> @@ -2616,6 +2657,12 @@ static void v9fs_renameat(void *opaque)
>          goto out_err;
>      }
>  
> +    if (name_has_illegal_characters(old_name.data) ||
> +        name_has_illegal_characters(new_name.data)) {
> +        err = -EINVAL;
> +        goto out_err;
> +    }
> +
>      v9fs_path_write_lock(s);
>      err = v9fs_complete_renameat(pdu, olddirfid,
>                                   &old_name, newdirfid, &new_name);
> @@ -2826,6 +2873,11 @@ static void v9fs_mknod(void *opaque)
>      }
>      trace_v9fs_mknod(pdu->tag, pdu->id, fid, mode, major, minor);
>  
> +    if (name_has_illegal_characters(name.data)) {
> +        err = -EINVAL;
> +        goto out_nofid;
> +    }
> +
>      fidp = get_fid(pdu, fid);
>      if (fidp == NULL) {
>          err = -ENOENT;
> @@ -2977,6 +3029,11 @@ static void v9fs_mkdir(void *opaque)
>      }
>      trace_v9fs_mkdir(pdu->tag, pdu->id, fid, name.data, mode, gid);
>  
> +    if (name_has_illegal_characters(name.data)) {
> +        err = -EINVAL;
> +        goto out_nofid;
> +    }
> +
>      fidp = get_fid(pdu, fid);
>      if (fidp == NULL) {
>          err = -ENOENT;
> @@ -3020,6 +3077,11 @@ static void v9fs_xattrwalk(void *opaque)
>      }
>      trace_v9fs_xattrwalk(pdu->tag, pdu->id, fid, newfid, name.data);
>  
> +    if (name_has_illegal_characters(name.data)) {
> +        err = -EINVAL;
> +        goto out_nofid;
> +    }
> +
>      file_fidp = get_fid(pdu, fid);
>      if (file_fidp == NULL) {
>          err = -ENOENT;
> @@ -3126,6 +3188,11 @@ static void v9fs_xattrcreate(void *opaque)
>      }
>      trace_v9fs_xattrcreate(pdu->tag, pdu->id, fid, name.data, size, flags);
>  
> +    if (name_has_illegal_characters(name.data)) {
> +        err = -EINVAL;
> +        goto out_nofid;
> +    }
> +
>      file_fidp = get_fid(pdu, fid);
>      if (file_fidp == NULL) {
>          err = -EINVAL;
Peter Maydell Aug. 24, 2016, 7:23 p.m. UTC | #7
On 24 August 2016 at 17:40, Greg Kurz <groug@kaod.org> wrote:
> On Wed, 24 Aug 2016 16:00:24 +0100
> Peter Maydell <peter.maydell@linaro.org> wrote:
>> Do we also need ".." and "." to be illegal names (for at least most
>> operations)?

> I understand how ".." could be an issue, but I don't for "."... can you
> please elaborate ?

If you try to create, open, etc a file named "." then it won't create a
file named "."; it'll do something wrong instead. So we shouldn't
permit attempts to treat "." as an ordinary filename.

(Basically the rationale is that for Linux the constraints on
file and pathnames are only
 * no NULs
 * no /
 * "." is special
 * ".." is special
 * can't be the empty string
We should reflect that in our error checking.)

> The spec says:
>
>           Directories are created by create with DMDIR set in the per-
>           missions argument (see stat(5)). The members of a directory
>           can be found with read(5). All directories must support
>           walks to the directory .. (dot-dot) meaning parent direc-
>           tory, although by convention directories contain no explicit
>           entry for .. or . (dot).  The parent of the root directory
>           of a server's tree is itself.

Yes, walk is the one special case that needs to handle "." and ".."
(because for this operation they have a defined meaning that doesn't
mean "just pass them through to the libc functions").

> So I don't think we should boldly make ".." an illegal name, but
> rather ignore it. Pretty much like doing chdir("..") when the current
> directory is /.
>
> All operations except walk take an existing fid and a single path component.
> A possible fix would be to convert ".." to "" when the fid points to the root
> of the export path. Makes sense ?

What did you want the empty string to mean? (We should probably
also define the empty string as an illegal name).

thanks
-- PMM
Greg Kurz Aug. 25, 2016, 2:24 p.m. UTC | #8
On Wed, 24 Aug 2016 20:23:22 +0100
Peter Maydell <peter.maydell@linaro.org> wrote:

> On 24 August 2016 at 17:40, Greg Kurz <groug@kaod.org> wrote:
> > On Wed, 24 Aug 2016 16:00:24 +0100
> > Peter Maydell <peter.maydell@linaro.org> wrote:  
> >> Do we also need ".." and "." to be illegal names (for at least most
> >> operations)?  
> 
> > I understand how ".." could be an issue, but I don't for "."... can you
> > please elaborate ?  
> 
> If you try to create, open, etc a file named "." then it won't create a
> file named "."; it'll do something wrong instead. So we shouldn't
> permit attempts to treat "." as an ordinary filename.

FWIW, on UNIX filesystems, "." cannot be created, nor deleted, nor symlinked
to:

mkdir(".", 0777)                        = -1 EEXIST (File exists)
open(".", O_WRONLY|O_CREAT|O_TRUNC, 0666) = -1 EISDIR (Is a directory)
rmdir(".")                              = -1 EINVAL (Invalid argument)
symlink("foo", ".")                      = -1 EEXIST (File exists)

I get the very same behaviour with the 9pfs local backend in QEMU (using
the linux client and turning strings into "." with GDB). So I still don't
get what "something wrong" refers to... but I'm not opposed to handling
"." and ".." differently (see below).

> 
> (Basically the rationale is that for Linux the constraints on
> file and pathnames are only
>  * no NULs

I have a separate patch for this as the spec forbids NUL for all strings,
not only file and pathnames, as said in http://man.cat-v.org/plan_9/5/intro.

>  * no /
>  * "." is special
>  * ".." is special

Indeed, that's what http://man.cat-v.org/plan_9/5/open says about the
create request:

The names . and .. are special; it is illegal to create files with these
names.

I hence agree these should be checked in the middle layer for create and
lcreate. And even if they are not described in the spec, it probably also
makes sense for all other operations except walk.

I'll make it a separate patch since it is conceptually different than
blacklisting illegal characters like /, which applies to all requests,
including walk.

>  * can't be the empty string

I could not find it in the spec but it makes sense indeed. Maybe this
should be generalized as I could not find a valid use of the empty
string in the 9P spec.

> We should reflect that in our error checking.)
> 
> > The spec says:
> >
> >           Directories are created by create with DMDIR set in the per-
> >           missions argument (see stat(5)). The members of a directory
> >           can be found with read(5). All directories must support
> >           walks to the directory .. (dot-dot) meaning parent direc-
> >           tory, although by convention directories contain no explicit
> >           entry for .. or . (dot).  The parent of the root directory
> >           of a server's tree is itself.  
> 
> Yes, walk is the one special case that needs to handle "." and ".."
> (because for this operation they have a defined meaning that doesn't
> mean "just pass them through to the libc functions").
> 
> > So I don't think we should boldly make ".." an illegal name, but
> > rather ignore it. Pretty much like doing chdir("..") when the current
> > directory is /.
> >
> > All operations except walk take an existing fid and a single path component.
> > A possible fix would be to convert ".." to "" when the fid points to the root
> > of the export path. Makes sense ?  
> 
> What did you want the empty string to mean? (We should probably
> also define the empty string as an illegal name).
> 

You can ignore this remark since ".." and "." will be specifically ignored for
all operations except walk.

> thanks
> -- PMM

Thanks for your valuable help, Peter !

I'll try to come up with a new series ASAP. But since I'm still on holidays until
the end of the week, don't feel forced to hold 2.7 for this if you don't hear from
me.

Cheers.

--
Greg
diff mbox

Patch

diff --git a/hw/9pfs/9p.c b/hw/9pfs/9p.c
index b6b02b46a9da..1c008814509c 100644
--- a/hw/9pfs/9p.c
+++ b/hw/9pfs/9p.c
@@ -1256,6 +1256,11 @@  static int v9fs_walk_marshal(V9fsPDU *pdu, uint16_t nwnames, V9fsQID *qids)
     return offset;
 }
 
+static bool name_has_illegal_characters(const char *name)
+{
+    return strchr(name, '/') != NULL;
+}
+
 static void v9fs_walk(void *opaque)
 {
     int name_idx;
@@ -1289,6 +1294,10 @@  static void v9fs_walk(void *opaque)
             if (err < 0) {
                 goto out_nofid;
             }
+            if (name_has_illegal_characters(wnames[i].data)) {
+                err = -EINVAL;
+                goto out_nofid;
+            }
             offset += err;
         }
     } else if (nwnames > P9_MAXWELEM) {
@@ -1483,6 +1492,11 @@  static void v9fs_lcreate(void *opaque)
     }
     trace_v9fs_lcreate(pdu->tag, pdu->id, dfid, flags, mode, gid);
 
+    if (name_has_illegal_characters(name.data)) {
+        err = -EINVAL;
+        goto out_nofid;
+    }
+
     fidp = get_fid(pdu, dfid);
     if (fidp == NULL) {
         err = -ENOENT;
@@ -2077,6 +2091,11 @@  static void v9fs_create(void *opaque)
     }
     trace_v9fs_create(pdu->tag, pdu->id, fid, name.data, perm, mode);
 
+    if (name_has_illegal_characters(name.data)) {
+        err = -EINVAL;
+        goto out_nofid;
+    }
+
     fidp = get_fid(pdu, fid);
     if (fidp == NULL) {
         err = -EINVAL;
@@ -2242,6 +2261,11 @@  static void v9fs_symlink(void *opaque)
     }
     trace_v9fs_symlink(pdu->tag, pdu->id, dfid, name.data, symname.data, gid);
 
+    if (name_has_illegal_characters(symname.data)) {
+        err = -EINVAL;
+        goto out_nofid;
+    }
+
     dfidp = get_fid(pdu, dfid);
     if (dfidp == NULL) {
         err = -EINVAL;
@@ -2316,6 +2340,11 @@  static void v9fs_link(void *opaque)
     }
     trace_v9fs_link(pdu->tag, pdu->id, dfid, oldfid, name.data);
 
+    if (name_has_illegal_characters(name.data)) {
+        err = -EINVAL;
+        goto out_nofid;
+    }
+
     dfidp = get_fid(pdu, dfid);
     if (dfidp == NULL) {
         err = -ENOENT;
@@ -2398,6 +2427,12 @@  static void v9fs_unlinkat(void *opaque)
     if (err < 0) {
         goto out_nofid;
     }
+
+    if (name_has_illegal_characters(name.data)) {
+        err = -EINVAL;
+        goto out_nofid;
+    }
+
     dfidp = get_fid(pdu, dfid);
     if (dfidp == NULL) {
         err = -EINVAL;
@@ -2504,6 +2539,12 @@  static void v9fs_rename(void *opaque)
     if (err < 0) {
         goto out_nofid;
     }
+
+    if (name_has_illegal_characters(name.data)) {
+        err = -EINVAL;
+        goto out_nofid;
+    }
+
     fidp = get_fid(pdu, fid);
     if (fidp == NULL) {
         err = -ENOENT;
@@ -2616,6 +2657,12 @@  static void v9fs_renameat(void *opaque)
         goto out_err;
     }
 
+    if (name_has_illegal_characters(old_name.data) ||
+        name_has_illegal_characters(new_name.data)) {
+        err = -EINVAL;
+        goto out_err;
+    }
+
     v9fs_path_write_lock(s);
     err = v9fs_complete_renameat(pdu, olddirfid,
                                  &old_name, newdirfid, &new_name);
@@ -2826,6 +2873,11 @@  static void v9fs_mknod(void *opaque)
     }
     trace_v9fs_mknod(pdu->tag, pdu->id, fid, mode, major, minor);
 
+    if (name_has_illegal_characters(name.data)) {
+        err = -EINVAL;
+        goto out_nofid;
+    }
+
     fidp = get_fid(pdu, fid);
     if (fidp == NULL) {
         err = -ENOENT;
@@ -2977,6 +3029,11 @@  static void v9fs_mkdir(void *opaque)
     }
     trace_v9fs_mkdir(pdu->tag, pdu->id, fid, name.data, mode, gid);
 
+    if (name_has_illegal_characters(name.data)) {
+        err = -EINVAL;
+        goto out_nofid;
+    }
+
     fidp = get_fid(pdu, fid);
     if (fidp == NULL) {
         err = -ENOENT;
@@ -3020,6 +3077,11 @@  static void v9fs_xattrwalk(void *opaque)
     }
     trace_v9fs_xattrwalk(pdu->tag, pdu->id, fid, newfid, name.data);
 
+    if (name_has_illegal_characters(name.data)) {
+        err = -EINVAL;
+        goto out_nofid;
+    }
+
     file_fidp = get_fid(pdu, fid);
     if (file_fidp == NULL) {
         err = -ENOENT;
@@ -3126,6 +3188,11 @@  static void v9fs_xattrcreate(void *opaque)
     }
     trace_v9fs_xattrcreate(pdu->tag, pdu->id, fid, name.data, size, flags);
 
+    if (name_has_illegal_characters(name.data)) {
+        err = -EINVAL;
+        goto out_nofid;
+    }
+
     file_fidp = get_fid(pdu, fid);
     if (file_fidp == NULL) {
         err = -EINVAL;