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
> >
next prev parent 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).