Skip to content

Commit 788d513

Browse files
committed
Add a new plugin to enable Linux-specific namespace functionality
A plugin is a convenient place to hide Linux-specific functionality. Implemented in this initial version are: - Optional private mounts during scriptlet execution, useful for protecting the system from scriptlets (eg /home) and the scriptlets from themselves (eg insecure /tmp usage) - Optionally disable network access during scriptlet execution Note that at this time, scriplets executed with the embedded Lua interpreter are not covered by this because they run inside the main rpm process instead of forking (#2635). Add a testcase for private /tmp Suggested-by: Johannes Segitz <[email protected]> Fixes: #2632 Fixes: #2665
1 parent 5802192 commit 788d513

File tree

8 files changed

+157
-1
lines changed

8 files changed

+157
-1
lines changed

docs/man/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ if (ENABLE_PLUGINS)
2828
if (WITH_SELINUX)
2929
list(APPEND manuals rpm-plugin-selinux.8)
3030
endif()
31+
if (HAVE_UNSHARE)
32+
list(APPEND manuals rpm-plugin-unshare.8)
33+
endif()
3134
endif()
3235

3336
foreach(man ${manuals})

docs/man/rpm-plugin-unshare.8.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
date: 15 Sep 2023
3+
section: 8
4+
title: 'RPM-UNSHARE'
5+
---
6+
7+
NAME
8+
====
9+
10+
rpm-plugin-unshare - Unshare plugin for the RPM Package Manager
11+
12+
Description
13+
===========
14+
15+
This plugin allows using various Linux-specific namespace-related
16+
technologies inside transactions, such as to harden and limit
17+
scriptlet access to resources.
18+
19+
Configuration
20+
=============
21+
22+
This plugin implements the following configurables:
23+
24+
`%__transaction_unshare_paths`
25+
26+
: A colon-separated list of paths to privately mount during scriptlet
27+
execution. Typical examples would be `/tmp` to protect against
28+
insecure temporary file usage inside scriptlets, and `/home` to
29+
prevent scriptlets from accessing user home directories.
30+
31+
`%__transaction_unshare_nonet`
32+
33+
: Non-zero value disables network access during scriptlet execution.
34+
35+
See **rpm-plugins**(8) on how to control plugins in general.
36+
37+
SEE ALSO
38+
========
39+
40+
*dbus-monitor*(1) *rpm-plugins*(8)

macros.in

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,6 +1194,11 @@ Supplements: (%{name} = %{version}-%{release} and langpacks-%{1})\
11941194
%__transaction_prioreset %{__plugindir}/prioreset.so
11951195
%__transaction_audit %{__plugindir}/audit.so
11961196
%__transaction_dbus_announce %{__plugindir}/dbus_announce.so
1197+
%__transaction_unshare %{__plugindir}/unshare.so
1198+
1199+
# Unshare specific configuration
1200+
%__transaction_unshare_paths /tmp:/home
1201+
%__transaction_unshare_nonet 1
11971202

11981203
#------------------------------------------------------------------------------
11991204
# Macros for further automated spec %setup and patch application

plugins/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ if(WITH_FSVERITY)
3838
target_include_directories(fsverity PRIVATE ${CMAKE_SOURCE_DIR}/sign)
3939
endif()
4040

41+
if (HAVE_UNSHARE)
42+
add_library(unshare MODULE unshare.c)
43+
endif()
44+
4145
set(plugindir ${CMAKE_INSTALL_FULL_LIBDIR}/rpm-plugins)
4246

4347
get_property(plugins DIRECTORY PROPERTY BUILDSYSTEM_TARGETS)

plugins/unshare.c

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#include "system.h"
2+
3+
#include <sched.h>
4+
#include <sys/mount.h>
5+
#include <errno.h>
6+
#include <string.h>
7+
8+
#include <rpm/rpmts.h>
9+
#include <rpm/rpmplugin.h>
10+
#include <rpm/rpmlog.h>
11+
#include <rpm/rpmmacro.h>
12+
13+
#include "debug.h"
14+
15+
static ARGV_t private_mounts = NULL;
16+
static int unshare_flags = 0;
17+
18+
static rpmRC unshare_init(rpmPlugin plugin, rpmts ts)
19+
{
20+
char *paths = rpmExpand("%{?__transaction_unshare_paths}", NULL);
21+
private_mounts = argvSplitString(paths, ":", ARGV_SKIPEMPTY);
22+
if (private_mounts)
23+
unshare_flags |= CLONE_NEWNS;
24+
free(paths);
25+
26+
if (rpmExpandNumeric("%{?__transaction_unshare_nonet}"))
27+
unshare_flags |= CLONE_NEWNET;
28+
29+
return RPMRC_OK;
30+
}
31+
32+
static void unshare_cleanup(rpmPlugin plugin)
33+
{
34+
/* ensure clean state for possible next transaction */
35+
private_mounts = argvFree(private_mounts);
36+
unshare_flags = 0;
37+
}
38+
39+
static rpmRC unshare_scriptlet_fork_post(rpmPlugin plugin,
40+
const char *path, int type)
41+
{
42+
rpmRC rc = RPMRC_FAIL;
43+
44+
if (unshare_flags && (unshare(unshare_flags) == -1)) {
45+
rpmlog(RPMLOG_ERR, _("unshare with flags x%x failed: %s\n"),
46+
unshare_flags, strerror(errno));
47+
goto exit;
48+
}
49+
50+
if (private_mounts) {
51+
if (mount("/", "/", NULL, MS_REC | MS_PRIVATE, NULL) == -1) {
52+
rpmlog(RPMLOG_ERR, _("failed to mount private %s: %s\n"),
53+
"/", strerror(errno));
54+
goto exit;
55+
}
56+
for (ARGV_t mnt = private_mounts; mnt && *mnt; mnt++) {
57+
if (mount("none", *mnt, "tmpfs", 0, NULL) == -1) {
58+
rpmlog(RPMLOG_ERR, _("failed to mount private %s: %s\n"),
59+
*mnt, strerror(errno));
60+
goto exit;
61+
}
62+
}
63+
}
64+
rc = RPMRC_OK;
65+
66+
exit:
67+
return rc;
68+
}
69+
70+
struct rpmPluginHooks_s unshare_hooks = {
71+
.init = unshare_init,
72+
.cleanup = unshare_cleanup,
73+
.scriptlet_fork_post = unshare_scriptlet_fork_post,
74+
};

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ if (${WITH_INTERNAL_OPENPGP})
2525
else()
2626
set(CRYPTO sequoia)
2727
endif()
28+
set(HAVE_UNSHARE ${HAVE_UNSHARE})
2829

2930
set(TESTSUITE_AT
3031
rpmtests.at

tests/atlocal.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ RPMTREE=/
33
RPMLIBDIR="@CMAKE_INSTALL_FULL_LIBDIR@"
44
export RPMLIBDIR
55

6+
HAVE_UNSHARE=@HAVE_UNSHARE@
67
PYTHON=@PYTHON@
7-
88
CRYPTO=@CRYPTO@
99
export CRYPTO
1010
if test x$CRYPTO = xlibgcrypt

tests/rpmscript.at

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,3 +391,32 @@ ERASE:
391391
[])
392392
RPMTEST_CLEANUP
393393

394+
AT_SETUP([script running environment])
395+
AT_KEYWORDS([script])
396+
AT_SKIP_IF([test ${HAVE_UNSHARE} = 0])
397+
RPMDB_INIT
398+
runroot rpmbuild -bb --quiet \
399+
/data/SPECS/fakeshell.spec \
400+
/data/SPECS/scriptwrite.spec
401+
402+
RPMTEST_CHECK([
403+
RPMDB_INIT
404+
runroot_other rm -f /tmp/scriptwrite.log
405+
runroot rpm -U /build/RPMS/noarch/fakeshell-1.0-1.noarch.rpm /build/RPMS/noarch/scriptwrite-1.0-1.noarch.rpm
406+
runroot_other test -f /tmp/scriptwrite.log
407+
],
408+
[1],
409+
[],
410+
[])
411+
412+
RPMTEST_CHECK([
413+
RPMDB_INIT
414+
runroot_other rm -f /tmp/scriptwrite.log
415+
runroot rpm -U --noplugins /build/RPMS/noarch/fakeshell-1.0-1.noarch.rpm /build/RPMS/noarch/scriptwrite-1.0-1.noarch.rpm
416+
runroot_other test -f /tmp/scriptwrite.log
417+
],
418+
[0],
419+
[],
420+
[])
421+
RPMTEST_CLEANUP
422+

0 commit comments

Comments
 (0)