* [PATCH v3] seunshare: fix the frail tmpdir cleanup
2025-07-31 19:33 [PATCH v2] " Rahul Sandhu
@ 2025-07-31 21:18 ` Rahul Sandhu
2025-08-01 18:12 ` Stephen Smalley
0 siblings, 1 reply; 5+ messages in thread
From: Rahul Sandhu @ 2025-07-31 21:18 UTC (permalink / raw)
To: nvraxn; +Cc: selinux
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 boundry. 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.
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.
diff --git a/sandbox/seunshare.c b/sandbox/seunshare.c
index 97430535..8b1af609 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).
+ * 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 */
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
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v3] seunshare: fix the frail tmpdir cleanup
2025-07-31 21:18 ` [PATCH v3] " Rahul Sandhu
@ 2025-08-01 18:12 ` Stephen Smalley
0 siblings, 0 replies; 5+ messages in thread
From: Stephen Smalley @ 2025-08-01 18:12 UTC (permalink / raw)
To: Rahul Sandhu; +Cc: selinux
On Thu, Jul 31, 2025 at 5:19 PM 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 boundry. Instead do this
spelling (boundry->boundary)
> using libc, the API designed to be used within C programs.
I would think the idiomatic C way of doing this would be to use fts(3)
or nftw(3).
setfiles/restorecon originally used nftw(3) but later switched to
fts(3) for reasons I don't recall.
>
> 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.
>
> 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.
>
> diff --git a/sandbox/seunshare.c b/sandbox/seunshare.c
> index 97430535..8b1af609 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).
> + * 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 */
> 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
>
>
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v3] seunshare: fix the frail tmpdir cleanup
[not found] <CAEjxPJ6QGZcAO zjdajOFTaDc tuh56Vao3niO37udEr=UfAgg@mail.gmail.com>
@ 2025-08-01 18:21 ` Rahul Sandhu
2025-08-01 18:29 ` Stephen Smalley
0 siblings, 1 reply; 5+ messages in thread
From: Rahul Sandhu @ 2025-08-01 18:21 UTC (permalink / raw)
To: stephen.smalley.work; +Cc: nvraxn, selinux
> I would think the idiomatic C way of doing this would be to use fts(3)
> or nftw(3).
> setfiles/restorecon originally used nftw(3) but later switched to
> fts(3) for reasons I don't recall.
It's worth noting that fts (3) is not actually mandated by POSIX from a
portability perspective, however we do already depend on it for other
parts of the userspace[1] (as you mentioned), and shims exist for other
libcs, for example musl[2], so I don't think this is a huge deal. I
can't speak for setfiles and restorecon, but nftw's api is pretty crap
compared to fts, so, like them, I'd rather use fts for this than nftw
even though nftw is part of posix[3].
I think dirfds are fine to use here, however I'm happy to send a v2
using fts if you wish - would you like me to do so?
[1] https://github.com/SELinuxProject/selinux/blob/main/libselinux/src/selinux_restorecon.c#L17
[2] https://github.com/void-linux/musl-fts
[3] https://pubs.opengroup.org/onlinepubs/9699919799/functions/nftw.html
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v3] seunshare: fix the frail tmpdir cleanup
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
0 siblings, 1 reply; 5+ messages in thread
From: Stephen Smalley @ 2025-08-01 18:29 UTC (permalink / raw)
To: Rahul Sandhu; +Cc: selinux
On Fri, Aug 1, 2025 at 2:21 PM Rahul Sandhu <nvraxn@gmail.com> wrote:
>
> > I would think the idiomatic C way of doing this would be to use fts(3)
> > or nftw(3).
> > setfiles/restorecon originally used nftw(3) but later switched to
> > fts(3) for reasons I don't recall.
>
> It's worth noting that fts (3) is not actually mandated by POSIX from a
> portability perspective, however we do already depend on it for other
> parts of the userspace[1] (as you mentioned), and shims exist for other
> libcs, for example musl[2], so I don't think this is a huge deal. I
> can't speak for setfiles and restorecon, but nftw's api is pretty crap
> compared to fts, so, like them, I'd rather use fts for this than nftw
> even though nftw is part of posix[3].
>
> I think dirfds are fine to use here, however I'm happy to send a v2
> using fts if you wish - would you like me to do so?
Maybe wait and see what other SELinux userspace maintainers think - I
am just one opinion here.
>
> [1] https://github.com/SELinuxProject/selinux/blob/main/libselinux/src/selinux_restorecon.c#L17
> [2] https://github.com/void-linux/musl-fts
> [3] https://pubs.opengroup.org/onlinepubs/9699919799/functions/nftw.html
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v3] seunshare: fix the frail tmpdir cleanup
2025-08-01 18:29 ` Stephen Smalley
@ 2025-08-14 17:47 ` James Carter
0 siblings, 0 replies; 5+ messages in thread
From: James Carter @ 2025-08-14 17:47 UTC (permalink / raw)
To: Stephen Smalley; +Cc: Rahul Sandhu, selinux
On Fri, Aug 1, 2025 at 2:29 PM Stephen Smalley
<stephen.smalley.work@gmail.com> wrote:
>
> On Fri, Aug 1, 2025 at 2:21 PM Rahul Sandhu <nvraxn@gmail.com> wrote:
> >
> > > I would think the idiomatic C way of doing this would be to use fts(3)
> > > or nftw(3).
> > > setfiles/restorecon originally used nftw(3) but later switched to
> > > fts(3) for reasons I don't recall.
> >
> > It's worth noting that fts (3) is not actually mandated by POSIX from a
> > portability perspective, however we do already depend on it for other
> > parts of the userspace[1] (as you mentioned), and shims exist for other
> > libcs, for example musl[2], so I don't think this is a huge deal. I
> > can't speak for setfiles and restorecon, but nftw's api is pretty crap
> > compared to fts, so, like them, I'd rather use fts for this than nftw
> > even though nftw is part of posix[3].
> >
> > I think dirfds are fine to use here, however I'm happy to send a v2
> > using fts if you wish - would you like me to do so?
>
> Maybe wait and see what other SELinux userspace maintainers think - I
> am just one opinion here.
>
Since we use both fts and dirfds in other places, I am ok with either
one. I don't think you need to redo the patch just to use fts.
Jim
> >
> > [1] https://github.com/SELinuxProject/selinux/blob/main/libselinux/src/selinux_restorecon.c#L17
> > [2] https://github.com/void-linux/musl-fts
> > [3] https://pubs.opengroup.org/onlinepubs/9699919799/functions/nftw.html
>
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2025-08-14 17:47 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[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-07-31 19:33 [PATCH v2] " Rahul Sandhu
2025-07-31 21:18 ` [PATCH v3] " Rahul Sandhu
2025-08-01 18:12 ` Stephen Smalley
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).