selinux.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Stephen Smalley <stephen.smalley.work@gmail.com>
To: Rahul Sandhu <nvraxn@gmail.com>
Cc: jwcart2@gmail.com, selinux@vger.kernel.org
Subject: Re: [PATCH v4] seunshare: fix the frail tmpdir cleanup
Date: Mon, 6 Oct 2025 12:31:49 -0400	[thread overview]
Message-ID: <CAEjxPJ6NsHyLwF2PeUTmm6noxoMcB+v2eBQjsVff5iyDyhvkCQ@mail.gmail.com> (raw)
In-Reply-To: <CAEjxPJ6S0o_Rmzt4nd+0jE0mHVP0sp-DA+=QrvJP5XjnAwTVSw@mail.gmail.com>

On Mon, Oct 6, 2025 at 11:57 AM Stephen Smalley
<stephen.smalley.work@gmail.com> wrote:
>
> On Thu, Sep 25, 2025 at 1:38 AM Rahul Sandhu <nvraxn@gmail.com> wrote:
> >
> > For some reason, rm is invoked via system (3) to cleanup the runtime
> > temp directory.  This really isn't all that robust, *especially* given
> > that seunshare is supposed to be a security boundary.  Instead do this
> > using libc, the API designed to be used within C programs.
> >
> > Also, don't bother trying to delete the contents of the tmpdir and then
> > trying to delete the parent directory with rmdir later - for some...
> > undocumented reason currently we attempt to delete the contents of the
> > dir with dropped perms, *then* elevate perms, *then* delete the tmpdir.
>
> I think this was for security/safety reasons, to prevent unlinking any
> files linked from the tmpdir that weren't actually owned by the user.
> However, in theory, your rewrite makes that obsolete.
>
> >
> > This doesn't really make all that much sense as far as I can tell.  We
> > should be the only ones using the tmpdir, so we may as well just delete
> > the entire thing using the rm_rf () function with elevated permissions.
> >
> > Signed-off-by: Rahul Sandhu <nvraxn@gmail.com>
> > ---
> >  sandbox/seunshare.c | 77 ++++++++++++++++++++++++++++++++++++++-------
> >  1 file changed, 65 insertions(+), 12 deletions(-)
> >
> > v2: don't use else after return
> > v3: don't follow symlinks in rm_rf ().  This is pretty important as we
> >     we are operating on an untrusted directory, which may have symlinks
> >     pointed to privileged content.  However, as we only really need to
> >     operate on the contents of the tmpdir, we can ignore symlinks.
> > v4: fix spelling in commit message
> >
> > diff --git a/sandbox/seunshare.c b/sandbox/seunshare.c
> > index 106f625f..c46db90f 100644
> > --- a/sandbox/seunshare.c
> > +++ b/sandbox/seunshare.c
> > @@ -403,6 +403,66 @@ err:
> >         return rc;
> >  }
> >
> > +/*
> > + * Recursively delete a directory.
> > + * SAFTEY: This function will NOT follow symbolic links (AT_SYMLINK_NOFOLLOW).
>
> Spelling here too: SAFETY
>
> > + *         As a result, this function can be run safely on a directory owned by
> > + *         a non-root user: symbolic links to root paths (such as /root) will
> > + *         not be followed.
> > + */
> > +static bool rm_rf(int targetfd, const char *path) {
> > +       struct stat statbuf;
> > +
> > +       if (fstatat(targetfd, path, &statbuf, AT_SYMLINK_NOFOLLOW) < 0) {
> > +               if (errno == ENOENT) {
> > +                       return true;
> > +               }
> > +               perror("fstatat");
> > +               return false;
> > +       }
> > +
> > +       if (S_ISDIR(statbuf.st_mode)) {
> > +               const int newfd = openat(targetfd, path, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
> > +               if (newfd < 0) {
> > +                       perror("openat");
> > +                       return false;
> > +               }
> > +
> > +               DIR *dir = fdopendir(newfd);
> > +               if (!dir) {
> > +                       perror("fdopendir");
> > +                       close(newfd);
> > +                       return false;
> > +               }
> > +
> > +               struct dirent *entry;
> > +               int rc = true;
> > +               while ((entry = readdir(dir)) != NULL) {
> > +                       if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
> > +                               continue;
> > +                       }
> > +
> > +                       if (rm_rf(dirfd(dir), entry->d_name) != 0) {
> > +                               rc = false;
> > +                       }
> > +               }
> > +
> > +               closedir(dir);
> > +
> > +               if (unlinkat(targetfd, path, AT_REMOVEDIR) < 0) {
> > +                       perror("unlinkat");
> > +                       rc = false;
> > +               }
> > +
> > +               return rc;
> > +       }
> > +       if (unlinkat(targetfd, path, 0) < 0) {
> > +               perror("unlinkat");
> > +               return false;
> > +       }
> > +       return true;
> > +}
> > +
> >  /**
> >   * Clean up runtime temporary directory.  Returns 0 if no problem was detected,
> >   * >0 if some error was detected, but errors here are treated as non-fatal and
> > @@ -428,24 +488,17 @@ static int cleanup_tmpdir(const char *tmpdir, const char *src,
> >                 free(cmdbuf); cmdbuf = NULL;
> >         }
> >
> > -       /* remove files from the runtime temporary directory */
> > -       if (asprintf(&cmdbuf, "/bin/rm -r '%s/' 2>/dev/null", tmpdir) == -1) {
> > -               fprintf(stderr, _("Out of memory\n"));
> > -               cmdbuf = NULL;
> > +       if ((uid_t)setfsuid(0) != 0) {
> > +               /* setfsuid does not return error, but this check makes code checkers happy */

I'm a little confused by this - further down there is a
setfsuid(pwd->pw_uid) call which seems to suggest that we are already
running as uid-0 at this point? If so, why do we need this setfsuid(0)
call?

> >                 rc++;
> >         }
> > -       /* this may fail if there's root-owned file left in the runtime tmpdir */
> > -       if (cmdbuf && spawn_command(cmdbuf, pwd->pw_uid) != 0) rc++;
> > -       free(cmdbuf); cmdbuf = NULL;
> >
> > -       /* remove runtime temporary directory */
> > -       if ((uid_t)setfsuid(0) != 0) {
> > -               /* setfsuid does not return error, but this check makes code checkers happy */
> > +       /* Recursively remove the runtime temp directory.  */
> > +       if (!rm_rf(AT_FDCWD, tmpdir)) {
> > +               fprintf(stderr, _("Failed to recursively remove directory %s\n"), tmpdir);
> >                 rc++;
> >         }
> >
> > -       if (pwd->pw_uid != 0 && rmdir(tmpdir) == -1)
> > -               fprintf(stderr, _("Failed to remove directory %s: %s\n"), tmpdir, strerror(errno));
> >         if ((uid_t)setfsuid(pwd->pw_uid) != 0) {
> >                 fprintf(stderr, _("unable to switch back to user after clearing tmp dir\n"));
> >                 rc++;
> > --
> > 2.50.1
> >

  reply	other threads:[~2025-10-06 16:32 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <CAEjxPJ6QGZcAO zjdajOFTaDc tuh56Vao3niO37udEr=UfAgg@mail.gmail.com>
2025-08-01 18:21 ` [PATCH v3] seunshare: fix the frail tmpdir cleanup Rahul Sandhu
2025-08-01 18:29   ` Stephen Smalley
2025-08-14 17:47     ` James Carter
2025-09-25  5:38       ` [PATCH v4] " Rahul Sandhu
2025-10-06 15:57         ` Stephen Smalley
2025-10-06 16:31           ` Stephen Smalley [this message]
2025-10-06 18:40             ` Stephen Smalley
2025-10-07  9:29               ` [PATCH v5] " Rahul Sandhu
2025-10-07 12:46                 ` Stephen Smalley
2025-10-07 14:09                   ` Stephen Smalley
2025-10-07 16:59                     ` Rahul Sandhu

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=CAEjxPJ6NsHyLwF2PeUTmm6noxoMcB+v2eBQjsVff5iyDyhvkCQ@mail.gmail.com \
    --to=stephen.smalley.work@gmail.com \
    --cc=jwcart2@gmail.com \
    --cc=nvraxn@gmail.com \
    --cc=selinux@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).