policycoreutils: introduce unsetfiles

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 <cgzones@googlemail.com>
Acked-by: James Carter <jwcart2@gmail.com>
This commit is contained in:
Christian Göttsche 2024-11-05 19:33:11 +01:00 committed by James Carter
parent be11f48b7a
commit 0faf3433e8
5 changed files with 257 additions and 1 deletions

View File

@ -9,4 +9,5 @@ setfiles/restorecon
setfiles/restorecon_xattr
setfiles/setfiles
setsebool/setsebool
unsetfiles/unsetfiles
hll/pp/pp

View File

@ -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 \

View File

@ -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

View File

@ -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)

View File

@ -0,0 +1,183 @@
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <linux/magic.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/xattr.h>
#include <unistd.h>
#include <selinux/selinux.h>
#define XATTR_NAME_SELINUX "security.selinux"
static void usage(const char *progname)
{
fprintf(stderr, "usage: %s [-nrvx] <path>\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;
}