Skip to content

Commit

Permalink
Add a new plugin to enable Linux-specific namespace functionality
Browse files Browse the repository at this point in the history
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).

Suggested-by: Johannes Segitz <jsegitz@suse.de>

Fixes: #2632
Fixes: #2665
  • Loading branch information
pmatilai committed Sep 15, 2023
1 parent 1120c8c commit 17718d9
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 0 deletions.
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)
5 changes: 5 additions & 0 deletions macros.in
Original file line number Diff line number Diff line change
Expand Up @@ -1194,6 +1194,11 @@ Supplements: (%{name} = %{version}-%{release} and langpacks-%{1})\
%__transaction_prioreset %{__plugindir}/prioreset.so
%__transaction_audit %{__plugindir}/audit.so
%__transaction_dbus_announce %{__plugindir}/dbus_announce.so
%__transaction_unshare %{__plugindir}/unshare.so

# Unshare specific configuration
%__transaction_unshare_paths /tmp:/home
%__transaction_unshare_nonet 1

#------------------------------------------------------------------------------
# Macros for further automated spec %setup and patch application
Expand Down
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(plugindir ${CMAKE_INSTALL_FULL_LIBDIR}/rpm-plugins)

get_property(plugins DIRECTORY PROPERTY BUILDSYSTEM_TARGETS)
Expand Down
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,
};

0 comments on commit 17718d9

Please sign in to comment.