From 0faf3433e87219b679e11a16d2483b1375e33510 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= Date: Tue, 5 Nov 2024 19:33:11 +0100 Subject: [PATCH] policycoreutils: introduce unsetfiles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce a helper to remove SELinux file security contexts. Mainly for testing label operations, and only for SELinux disabled systems, since removing file contexts is not supported by SELinux. Signed-off-by: Christian Göttsche Acked-by: James Carter --- policycoreutils/.gitignore | 1 + policycoreutils/Makefile | 2 +- policycoreutils/unsetfiles/Makefile | 26 ++++ policycoreutils/unsetfiles/unsetfiles.1 | 46 ++++++ policycoreutils/unsetfiles/unsetfiles.c | 183 ++++++++++++++++++++++++ 5 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 policycoreutils/unsetfiles/Makefile create mode 100644 policycoreutils/unsetfiles/unsetfiles.1 create mode 100644 policycoreutils/unsetfiles/unsetfiles.c diff --git a/policycoreutils/.gitignore b/policycoreutils/.gitignore index 47c9cc52..33e7414c 100644 --- a/policycoreutils/.gitignore +++ b/policycoreutils/.gitignore @@ -9,4 +9,5 @@ setfiles/restorecon setfiles/restorecon_xattr setfiles/setfiles setsebool/setsebool +unsetfiles/unsetfiles hll/pp/pp diff --git a/policycoreutils/Makefile b/policycoreutils/Makefile index b930b297..32ad0201 100644 --- a/policycoreutils/Makefile +++ b/policycoreutils/Makefile @@ -1,4 +1,4 @@ -SUBDIRS = setfiles load_policy newrole run_init secon sestatus semodule setsebool scripts po man hll +SUBDIRS = setfiles load_policy newrole run_init secon sestatus semodule setsebool scripts po man hll unsetfiles all install relabel clean indent: @for subdir in $(SUBDIRS); do \ diff --git a/policycoreutils/unsetfiles/Makefile b/policycoreutils/unsetfiles/Makefile new file mode 100644 index 00000000..9e5edc04 --- /dev/null +++ b/policycoreutils/unsetfiles/Makefile @@ -0,0 +1,26 @@ +PREFIX ?= /usr +SBINDIR ?= $(PREFIX)/sbin +MANDIR ?= $(PREFIX)/share/man + +override CFLAGS += -D_GNU_SOURCE +override LDLIBS += -lselinux + + +all: unsetfiles + +unsetfiles: unsetfiles.o + +install: all + test -d $(DESTDIR)$(SBINDIR) || install -m 755 -d $(DESTDIR)$(SBINDIR) + test -d $(DESTDIR)$(MANDIR)/man1 || install -m 755 -d $(DESTDIR)$(MANDIR)/man1 + install -m 755 unsetfiles $(DESTDIR)$(SBINDIR) + install -m 644 unsetfiles.1 $(DESTDIR)$(MANDIR)/man1/ + +clean: + -rm -f unsetfiles *.o + +indent: + ../../scripts/Lindent $(wildcard *.[ch]) + +relabel: install + /sbin/restorecon $(DESTDIR)$(SBINDIR)/unsetfiles diff --git a/policycoreutils/unsetfiles/unsetfiles.1 b/policycoreutils/unsetfiles/unsetfiles.1 new file mode 100644 index 00000000..49d0c821 --- /dev/null +++ b/policycoreutils/unsetfiles/unsetfiles.1 @@ -0,0 +1,46 @@ +.TH UNSETFILES "1" "December 2023" "Security Enhanced Linux" +.SH NAME +unsetfiles \- Remove SELinux file security contexts. +.SH SYNOPSIS +.B unsetfiles +.RB [ \-hnrvx ] +.IR pathname \ ... + +.SH DESCRIPTION +.P +This program removes the SELinux file security contexts of files. It can help +cleaning extended file attributes after disabling SELinux. +.P +.B unsetfiles +will only work on SELinux disabled systems, since removing file security +contexts is not supported by SELinux. + +.SH OPTIONS +.TP +.B \-h +Show usage information and exit. +.TP +.B \-n +Do not actually remove any SELinux file security contexts. +.TP +.B \-r +Remove SELinux file security contexts recursive. +.TP +.B \-v +Be verbose about performed actions. +.TP +.B \-x +Do not cross filesystem boundaries. + +.SH ARGUMENTS +.TP +.IR pathname \ ... +One or more path names to operate on. + +.SH SEE ALSO +.BR restorecon (8), +.BR setfiles (8) + +.SH AUTHORS +.nf +Christian Göttsche (cgzones@googlemail.com) diff --git a/policycoreutils/unsetfiles/unsetfiles.c b/policycoreutils/unsetfiles/unsetfiles.c new file mode 100644 index 00000000..6293d00f --- /dev/null +++ b/policycoreutils/unsetfiles/unsetfiles.c @@ -0,0 +1,183 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +#define XATTR_NAME_SELINUX "security.selinux" + + +static void usage(const char *progname) +{ + fprintf(stderr, "usage: %s [-nrvx] \n\n" + "Options:\n" + "\t-n\tdon't remove any file labels\n" + "\t-r\tremove labels recursive\n" + "\t-v\tbe verbose\n" + "\t-x\tdo not cross filesystem boundaries\n", + progname); +} + +static void unset(int atfd, const char *path, const char *fullpath, + bool dry_run, bool recursive, bool verbose, + dev_t root_dev) +{ + ssize_t ret; + int fd, rc; + DIR *dir; + + ret = lgetxattr(fullpath, XATTR_NAME_SELINUX, NULL, 0); + if (ret <= 0) { + if (errno != ENODATA && errno != ENOTSUP) + fprintf(stderr, "Failed to get SELinux label of %s: %m\n", fullpath); + else if (verbose) + printf("Failed to get SELinux label of %s: %m\n", fullpath); + } else { + if (dry_run) { + printf("Would remove SELinux label of %s\n", fullpath); + } else { + if (verbose) + printf("Removing label of %s\n", fullpath); + + rc = lremovexattr(fullpath, XATTR_NAME_SELINUX); + if (rc < 0) + fprintf(stderr, "Failed to remove SELinux label of %s: %m\n", fullpath); + } + } + + if (!recursive) + return; + + fd = openat(atfd, path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); + if (fd < 0) { + if (errno != ENOTDIR) + fprintf(stderr, "Failed to open %s: %m\n", fullpath); + return; + } + + if (root_dev != (dev_t)-1) { + struct stat sb; + + rc = fstat(fd, &sb); + if (rc == -1) { + fprintf(stderr, "Failed to stat directory %s: %m\n", fullpath); + close(fd); + return; + } + + if (sb.st_dev != root_dev) { + if (verbose) + printf("Skipping directory %s due to filesystem boundary\n", fullpath); + + close(fd); + return; + } + } + + dir = fdopendir(fd); + if (!dir) { + fprintf(stderr, "Failed to open directory %s: %m\n", fullpath); + close(fd); + return; + } + + while (true) { + const struct dirent *entry; + char *nextfullpath; + + errno = 0; + entry = readdir(dir); + if (!entry) { + if (errno) + fprintf(stderr, "Failed to iterate directory %s: %m\n", fullpath); + break; + } + + if (entry->d_name[0] == '.' && (entry->d_name[1] == '\0' || (entry->d_name[1] == '.' && entry->d_name[2] == '\0'))) + continue; + + rc = asprintf(&nextfullpath, "%s/%s", strcmp(fullpath, "/") == 0 ? "" : fullpath, entry->d_name); + if (rc < 0) { + fprintf(stderr, "Out of memory!\n"); + closedir(dir); + return; + } + + unset(dirfd(dir), entry->d_name, nextfullpath, dry_run, recursive, verbose, root_dev); + + free(nextfullpath); + } + + closedir(dir); +} + + +int main(int argc, char *argv[]) +{ + bool dry_run = false, recursive = false, verbose = false, same_dev = false; + int c; + + while ((c = getopt(argc, argv, "hnrvx")) != -1) { + switch (c) { + case 'h': + usage(argv[0]); + return EXIT_SUCCESS; + case 'n': + dry_run = true; + break; + case 'r': + recursive = true; + break; + case 'v': + verbose = true; + break; + case 'x': + same_dev = true; + break; + default: + usage(argv[0]); + return EXIT_FAILURE; + } + } + + if (optind >= argc) { + usage(argv[0]); + return EXIT_FAILURE; + } + + if (is_selinux_enabled()) { + fprintf(stderr, "Removing SELinux attributes on a SELinux enabled system is not supported!\n"); + return EXIT_FAILURE; + } + + for (int index = optind; index < argc; index++) { + dev_t root_dev = (dev_t)-1; + + if (same_dev) { + struct stat sb; + int rc; + + rc = stat(argv[index], &sb); + if (rc == -1) { + fprintf(stderr, "Failed to stat %s: %m\n", argv[index]); + continue; + } + + root_dev = sb.st_dev; + } + unset(AT_FDCWD, argv[index], argv[index], dry_run, recursive, verbose, root_dev); + } + + return EXIT_SUCCESS; +}