Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a new plugin to enable Linux-specific namespace functionality #2666

Merged
merged 1 commit into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/man/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ if (ENABLE_PLUGINS)
if (WITH_SELINUX)
list(APPEND manuals rpm-plugin-selinux.8)
endif()
if (HAVE_UNSHARE)
list(APPEND manuals rpm-plugin-unshare.8)
endif()
endif()

foreach(man ${manuals})
Expand Down
40 changes: 40 additions & 0 deletions docs/man/rpm-plugin-unshare.8.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
date: 15 Sep 2023
section: 8
title: 'RPM-UNSHARE'
---

NAME
====

rpm-plugin-unshare - Unshare plugin for the RPM Package Manager

Description
===========

This plugin allows using various Linux-specific namespace-related
technologies inside transactions, such as to harden and limit
scriptlet access to resources.

Configuration
=============

This plugin implements the following configurables:

`%__transaction_unshare_paths`

: A colon-separated list of paths to privately mount during scriptlet
execution. Typical examples would be `/tmp` to protect against
insecure temporary file usage inside scriptlets, and `/home` to
prevent scriptlets from accessing user home directories.

`%__transaction_unshare_nonet`

: Non-zero value disables network access during scriptlet execution.

See **rpm-plugins**(8) on how to control plugins in general.

SEE ALSO
========

*dbus-monitor*(1) *rpm-plugins*(8)
4 changes: 4 additions & 0 deletions plugins/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ if(WITH_FSVERITY)
target_include_directories(fsverity PRIVATE ${CMAKE_SOURCE_DIR}/sign)
endif()

if (HAVE_UNSHARE)
add_library(unshare MODULE unshare.c)
endif()

set(RPM_PLUGINDIR ${CMAKE_INSTALL_FULL_LIBDIR}/rpm-plugins
CACHE PATH "rpm plugin directory")

Expand Down
3 changes: 3 additions & 0 deletions plugins/macros.transaction_unshare
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
%__transaction_unshare %{__plugindir}/unshare.so
%__transaction_unshare_paths /tmp:/home
%__transaction_unshare_nonet 1
74 changes: 74 additions & 0 deletions plugins/unshare.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#include "system.h"

#include <sched.h>
#include <sys/mount.h>
#include <errno.h>
#include <string.h>

#include <rpm/rpmts.h>
#include <rpm/rpmplugin.h>
#include <rpm/rpmlog.h>
#include <rpm/rpmmacro.h>

#include "debug.h"

static ARGV_t private_mounts = NULL;
static int unshare_flags = 0;

static rpmRC unshare_init(rpmPlugin plugin, rpmts ts)
{
char *paths = rpmExpand("%{?__transaction_unshare_paths}", NULL);
private_mounts = argvSplitString(paths, ":", ARGV_SKIPEMPTY);
if (private_mounts)
unshare_flags |= CLONE_NEWNS;
free(paths);

if (rpmExpandNumeric("%{?__transaction_unshare_nonet}"))
unshare_flags |= CLONE_NEWNET;

return RPMRC_OK;
}

static void unshare_cleanup(rpmPlugin plugin)
{
/* ensure clean state for possible next transaction */
private_mounts = argvFree(private_mounts);
unshare_flags = 0;
}

static rpmRC unshare_scriptlet_fork_post(rpmPlugin plugin,
const char *path, int type)
{
rpmRC rc = RPMRC_FAIL;

if (unshare_flags && (unshare(unshare_flags) == -1)) {
rpmlog(RPMLOG_ERR, _("unshare with flags x%x failed: %s\n"),
unshare_flags, strerror(errno));
goto exit;
}

if (private_mounts) {
if (mount("/", "/", NULL, MS_REC | MS_PRIVATE, NULL) == -1) {
rpmlog(RPMLOG_ERR, _("failed to mount private %s: %s\n"),
"/", strerror(errno));
goto exit;
}
for (ARGV_t mnt = private_mounts; mnt && *mnt; mnt++) {
if (mount("none", *mnt, "tmpfs", 0, NULL) == -1) {
rpmlog(RPMLOG_ERR, _("failed to mount private %s: %s\n"),
*mnt, strerror(errno));
goto exit;
}
}
}
rc = RPMRC_OK;

exit:
return rc;
}

struct rpmPluginHooks_s unshare_hooks = {
.init = unshare_init,
.cleanup = unshare_cleanup,
.scriptlet_fork_post = unshare_scriptlet_fork_post,
};
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ if (WITH_INTERNAL_OPENPGP)
else()
set(PGP sequoia)
endif()
set(HAVE_UNSHARE ${HAVE_UNSHARE})

set(TESTSUITE_AT
rpmtests.at
Expand Down
1 change: 1 addition & 0 deletions tests/atlocal.in
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ RPMTREE=/
RPMLIBDIR="@CMAKE_INSTALL_FULL_LIBDIR@"
export RPMLIBDIR

HAVE_UNSHARE=@HAVE_UNSHARE@
PYTHON=@PYTHON@
PGP=@PGP@

Expand Down
18 changes: 18 additions & 0 deletions tests/data/SPECS/scriptwrite.spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Name: scriptwrite
Version: 1.0
Release: 1
Summary: Testing script running environment
Group: Testing
License: GPL
BuildArch: noarch

%description
%{summary}

%files

%pre
echo "%{name}-%{version} pre" > /tmp/%{name}.log

%post
echo "%{name}-%{version} post" >> /tmp/%{name}.log
29 changes: 29 additions & 0 deletions tests/rpmscript.at
Original file line number Diff line number Diff line change
Expand Up @@ -391,3 +391,32 @@ ERASE:
[])
RPMTEST_CLEANUP

AT_SETUP([script running environment])
AT_KEYWORDS([script])
AT_SKIP_IF([test ${HAVE_UNSHARE} = 0])
RPMDB_INIT
runroot rpmbuild -bb --quiet \
/data/SPECS/fakeshell.spec \
/data/SPECS/scriptwrite.spec

RPMTEST_CHECK([
RPMDB_INIT
runroot_other rm -f /tmp/scriptwrite.log
runroot rpm -U /build/RPMS/noarch/fakeshell-1.0-1.noarch.rpm /build/RPMS/noarch/scriptwrite-1.0-1.noarch.rpm
runroot_other test -f /tmp/scriptwrite.log
],
[1],
[],
[])

RPMTEST_CHECK([
RPMDB_INIT
runroot_other rm -f /tmp/scriptwrite.log
runroot rpm -U --noplugins /build/RPMS/noarch/fakeshell-1.0-1.noarch.rpm /build/RPMS/noarch/scriptwrite-1.0-1.noarch.rpm
runroot_other test -f /tmp/scriptwrite.log
],
[0],
[],
[])
RPMTEST_CLEANUP