[Rpm-maint] [RFC PATCH 1/2] install selinux policies from package header

Steve Lawrence slawrence at tresys.com
Fri Aug 14 17:27:06 UTC 2009


This patch was sent out last month to this list [1]. Only minor changes
have been made to apply to the master branch. We are primarily
interested in comments on patch 2/2, though we are happy to get any
comments on this patch as well.

RPM currently has support for security policies to be stored in an rpm
header but it doesn't currently do anything with the policies. We'd like
to get some feedback on a prototype implementation that adds support for
using those policies in an SELinux environment.

First, a bit of background. SELinux policy is currently installed
through %post scripts. This presents several problems. First, this means
that policy for a given application may not be loaded at the time the
files are written to disk, preventing those files from being labeled
properly, because the symbols used to label files need to be in the
policy loaded into the kernel. Secondly, this means that if multiple
packages install policy, each of their %post scripts will reload the
policy, which is a very expensive operation. Consequently, policy is
generally kept in a single package to avoid this, despite containing
many application specific policy modules that would be more suited to be
included in their application package.

So, what we would like to do is to start including SELinux policy as
part of the rpm and have rpm install all policies together before files
start to hit the disk. To do this, we would like to use the already
supported %policy directive, which stores the policy in the archive
header.

We would then install the policy before pretrans. This policy load would
involve gathering all the policies to be installed from all packages,
writing them to a temporary location, and calling out to semodule to
install the SELinux policy modules.

Obviously I'm glossing over many implementation details that would need
to be worked out. The point of this email is strictly to get feedback on
our approach. Below is a patch that implements the beginnings of what I
describe above. Any and all feedback is appreciated.

Included in this patch is a spec file and sources for creating a test
rpm. When this rpm is installed, a policy module called poltest will be
loaded. To verify that the module is successfully installed, you can run

   # semodule -l | grep poltest

[1] http://lists.rpm.org/pipermail/rpm-maint/2009-July/002452.html
---
 lib/psm.c                                     |   85 +++++++++++++++++++++++++
 lib/psm.h                                     |    6 ++-
 lib/rpmte.c                                   |   10 +++
 lib/rpmte_internal.h                          |    3 +
 lib/transaction.c                             |   83 ++++++++++++++++++++++++
 tests/data/SOURCES/poltest-1.0.tar.bz2        |  Bin 0 -> 384 bytes
 tests/data/SOURCES/poltest-policy-1.0.tar.bz2 |  Bin 0 -> 446 bytes
 tests/data/SPECS/poltest.spec                 |   36 +++++++++++
 8 files changed, 222 insertions(+), 1 deletions(-)
 create mode 100644 tests/data/SOURCES/poltest-1.0.tar.bz2
 create mode 100644 tests/data/SOURCES/poltest-policy-1.0.tar.bz2
 create mode 100644 tests/data/SPECS/poltest.spec

diff --git a/lib/psm.c b/lib/psm.c
index de3b97b..3a9fd86 100644
--- a/lib/psm.c
+++ b/lib/psm.c
@@ -17,6 +17,7 @@
 #include <rpm/argv.h>
 
 #include "rpmio/rpmlua.h"
+#include "rpmio/base64.h"
 #include "lib/cpio.h"
 #include "lib/fsm.h"		/* XXX CPIO_FOO/FSM_FOO constants */
 #include "lib/psm.h"
@@ -60,6 +61,8 @@ struct rpmpsm_s {
     pkgStage nstage;		/*!< Next psm stage. */
 
     int nrefs;			/*!< Reference count. */
+
+    ARGV_t policyFiles;		/*!< Locations of temporary policy files on disk, extracted from this package */
 };
 
 int rpmVersionCompare(Header first, Header second)
@@ -869,6 +872,76 @@ exit:
 }
 
 /**
+ * Write policies from the header to temporary files and store the 
+ * tmp file locations in the psm data
+ * @param psm		package state machine data
+ * @return		rpmRC return code
+ */
+static rpmRC installPolicy(rpmpsm psm)
+{
+	rpmRC rc = RPMRC_OK;
+	Header h = NULL;
+	FD_t fd = NULL;
+	char *tmpFile = NULL;
+	struct rpmtd_s policies;
+	const char *data = NULL;
+	uint8_t *policy = NULL;
+	size_t policylen = 0;
+
+	rpmtdReset(&policies);
+	h = rpmteHeader(psm->te);
+
+	if (h == NULL) {
+		rpmlog(RPMLOG_ERR, _("Failed to get header\n"));
+		rc = RPMRC_FAIL;
+		goto exit;
+	} else if (!headerIsEntry(h, RPMTAG_POLICIES)) {
+		// header does not contain any policy   
+		rc = RPMRC_OK;
+		goto exit;
+	} else if (!headerGet(h, RPMTAG_POLICIES, &policies, HEADERGET_ALLOC)) {
+		rpmlog(RPMLOG_ERR, _("Failed to get RPMTAG_POLICIES header data\n"));
+		rc = RPMRC_FAIL;
+		goto exit;
+	}
+
+	while (rc == RPMRC_OK && (data = rpmtdNextString(&policies))) {
+		if (b64decode(data, (void **)&policy, &policylen) != 0) {
+			rpmlog(RPMLOG_ERR, _("Failed to decode policy\n"));
+			rc = RPMRC_FAIL;
+			goto clean;
+		}
+
+		fd = rpmMkTempFile(NULL, &tmpFile);
+		if (fd == NULL || Ferror(fd)) {
+			rpmlog(RPMLOG_ERR, _("Couldn't create temporary file %s\n"), tmpFile);
+			rc = RPMRC_FAIL;
+			goto clean;
+		}
+
+		if (!Fwrite(policy, sizeof(*policy), policylen, fd)) {
+			rpmlog(RPMLOG_ERR, _("Failed to write policy to tempory file %s\n"), tmpFile);
+			rc = RPMRC_FAIL;
+			goto clean;
+		}
+
+		argvAdd(&psm->policyFiles, tmpFile);
+
+ clean:
+		policy = _free(policy);
+		tmpFile = _free(tmpFile);
+		if (fd) Fclose(fd);
+		fd = NULL;
+	}
+
+ exit:
+	rpmtdFreeData(&policies);
+	headerFree(h);
+
+	return rc;
+}
+
+/**
  * Execute triggers.
  * @todo Trigger on any provides, not just package NVR.
  * @param psm		package state machine data
@@ -1127,6 +1200,8 @@ rpmpsm rpmpsmFree(rpmpsm psm)
 #endif
     psm->ts = rpmtsFree(psm->ts);
 
+    psm->policyFiles = argvFree(psm->policyFiles);
+
     (void) rpmpsmUnlink(psm, RPMDBG_M("rpmpsmFree"));
 
     memset(psm, 0, sizeof(*psm));		/* XXX trash and burn */
@@ -1182,6 +1257,11 @@ static int rpmpsmNext(rpmpsm psm, pkgStage nstage)
 	return rpmsqJoin( rpmsqThread(rpmpsmThread, psm) );
     return rpmpsmStage(psm, psm->nstage);
 }
+			
+ARGV_const_t rpmpsmGetPolicyFiles(rpmpsm psm)
+{
+	return psm->policyFiles;
+}
 
 rpmRC rpmpsmStage(rpmpsm psm, pkgStage stage)
 {
@@ -1612,6 +1692,11 @@ rpmRC rpmpsmStage(rpmpsm psm, pkgStage stage)
     case PSM_SCRIPT:	/* Run current package scriptlets. */
 	rc = runInstScript(psm);
 	break;
+
+    case PSM_POLINSTALL:
+	rc = installPolicy(psm);
+	break;
+
     case PSM_TRIGGERS:
 	/* Run triggers in other package(s) this package sets off. */
 	rc = runTriggers(psm);
diff --git a/lib/psm.h b/lib/psm.h
index 123375d..d2aee16 100644
--- a/lib/psm.h
+++ b/lib/psm.h
@@ -50,8 +50,9 @@ typedef enum pkgStage_e {
     PSM_RPMIO_FLAGS	= 56,
 
     PSM_RPMDB_ADD	= 98,
-    PSM_RPMDB_REMOVE	= 99
+    PSM_RPMDB_REMOVE	= 99,
 
+    PSM_POLINSTALL	= 110
 } pkgStage;
 #undef	_fv
 #undef	_fi
@@ -121,6 +122,9 @@ rpmRC rpmpsmScriptStage(rpmpsm psm, rpmTag scriptTag, rpmTag progTag);
 RPM_GNUC_INTERNAL
 void rpmpsmSetAsync(rpmpsm psm, int async);
 
+RPM_GNUC_INTERNAL
+ARGV_const_t rpmpsmGetPolicyFiles(rpmpsm psm);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/lib/rpmte.c b/lib/rpmte.c
index 9e47514..9508a49 100644
--- a/lib/rpmte.c
+++ b/lib/rpmte.c
@@ -67,6 +67,8 @@ struct rpmte_s {
     int transscripts;		/*!< pre/posttrans script existence */
     int failed;			/*!< (parent) install/erase failed */
 
+    int policies;			/*!< policy existence */ 
+
     rpmfs fs;
 };
 
@@ -299,6 +301,9 @@ static void addTE(rpmts ts, rpmte p, Header h,
 			 headerIsEntry(h, RPMTAG_POSTTRANSPROG)) ?
 			RPMTE_HAVE_POSTTRANS : 0;
 
+    /* Determine if there are policies */
+    p->policies = headerIsEntry(h, RPMTAG_POLICIES);
+
     rpmteColorDS(p, RPMTAG_PROVIDENAME);
     rpmteColorDS(p, RPMTAG_REQUIRENAME);
     return;
@@ -900,6 +905,11 @@ rpmps rpmteProblems(rpmte te)
     return te ? te->probs : NULL;
 }
 
+int rpmteHavePolicies(rpmte te)
+{
+    return (te != NULL ? te->policies : 0);
+}
+
 rpmfs rpmteGetFileStates(rpmte te) {
     return te->fs;
 }
diff --git a/lib/rpmte_internal.h b/lib/rpmte_internal.h
index 060405d..90e1bcb 100644
--- a/lib/rpmte_internal.h
+++ b/lib/rpmte_internal.h
@@ -89,6 +89,9 @@ int rpmteHaveTransScript(rpmte te, rpmTag tag);
 RPM_GNUC_INTERNAL
 rpmps rpmteProblems(rpmte te);
 
+RPM_GNUC_INTERNAL
+int rpmteHavePolicies(rpmte te);
+
 //RPM_GNUC_INTERNAL
 rpmfs rpmteGetFileStates(rpmte te);
 
diff --git a/lib/transaction.c b/lib/transaction.c
index ba4e7cc..172f4de 100644
--- a/lib/transaction.c
+++ b/lib/transaction.c
@@ -885,6 +885,81 @@ static int runTransScripts(rpmts ts, rpmTag stag)
     return 0;
 }
 
+/*
+ * Extract and load selinux policy for transaction set
+ * param ts	Transaction set
+ * return	0 on success, -1 on error (invalid script tag)
+ */
+static int rpmtsLoadPolicy(rpmts ts)
+{
+	rpmtsi pi = NULL;
+	rpmte p = NULL;
+	rpmpsm psm = NULL;
+	int rc = RPMRC_OK;
+	pid_t pid;
+	ARGV_t semodArgs = NULL;
+	ARGV_t polFile = NULL;
+	int status = 0;
+	int numBaseArgs = 0;
+
+	argvAdd(&semodArgs, "semodule");
+	argvAdd(&semodArgs, "-i");
+	numBaseArgs = argvCount(semodArgs);
+
+	pi = rpmtsiInit(ts);
+	while (rc == RPMRC_OK && (p = rpmtsiNext(pi, TR_ADDED)) != NULL) {
+		if (!rpmteHavePolicies(p)) {
+			continue;
+		}
+
+		if (!rpmteOpen(p, ts, 0)) {
+			continue;
+		}
+
+		psm = rpmpsmNew(ts, p);
+		rc = rpmpsmStage(psm, PSM_POLINSTALL);
+		if (rc == RPMRC_OK) {
+			argvAppend(&semodArgs, rpmpsmGetPolicyFiles(psm));
+		}
+		psm = rpmpsmFree(psm);
+		rpmteClose(p, ts, 0);
+	}
+	pi = rpmtsiFree(pi);
+
+	if (rc == RPMRC_OK && argvCount(semodArgs) > numBaseArgs) {
+		pid = fork();
+		switch (pid) {
+		case -1:
+			rpmlog(RPMLOG_ERR, "failed to fork process: %s\n", strerror(errno));
+			rc = RPMRC_FAIL;
+			break;
+		case 0:
+			execv("/usr/sbin/semodule", semodArgs);
+			rpmlog(RPMLOG_ERR, "failed to execute semodule: %s\n", strerror(errno));
+			exit(1);
+		default:
+			waitpid(pid, &status, 0);
+			if (!WIFEXITED(status)) {
+				rpmlog(RPMLOG_ERR, "semodule terminated abnormally\n");
+				rc = RPMRC_FAIL;
+			} else if(WEXITSTATUS(status)) {
+				rpmlog(RPMLOG_ERR, "semodule failed with exit code %i\n", WEXITSTATUS(status));
+				rc = RPMRC_FAIL;
+			}
+		}
+	}
+
+	// remove tmp policy files
+	for (polFile = semodArgs + numBaseArgs; polFile && *polFile; polFile++) {
+		if (unlink(*polFile) < 0) {
+			rpmlog(RPMLOG_WARNING, "failed to unlink temporary policy file %s: %s\n", *polFile, strerror(errno));
+		}
+	}
+	semodArgs = argvFree(semodArgs);
+
+	return rc;
+}
+
 /* Add fingerprint for each file not skipped. */
 static void addFingerprints(rpmts ts, uint64_t fileCount, rpmFpHash ht, fingerPrintCache fpc)
 {
@@ -1176,6 +1251,14 @@ int rpmtsRun(rpmts ts, rpmps okProbs, rpmprobFilterFlags ignoreSet)
     /* Check package set for problems */
     ts->probs = checkProblems(ts);
 
+    if (!((rpmtsFlags(ts) & (RPMTRANS_FLAG_BUILD_PROBS|RPMTRANS_FLAG_TEST))
+     	  || (rpmpsNumProblems(ts->probs) &&
+		(okProbs == NULL || rpmpsTrim(ts->probs, okProbs))))) {
+		if (rpmtsLoadPolicy(ts) != RPMRC_OK) {
+			goto exit;
+		}
+	}
+
     /* Run pre-transaction scripts, but only if there are no known
      * problems up to this point and not disabled otherwise. */
     if (!((rpmtsFlags(ts) & (RPMTRANS_FLAG_BUILD_PROBS|RPMTRANS_FLAG_TEST|RPMTRANS_FLAG_NOPRE))
diff --git a/tests/data/SOURCES/poltest-1.0.tar.bz2 b/tests/data/SOURCES/poltest-1.0.tar.bz2
new file mode 100644
index 0000000000000000000000000000000000000000..9f179988e392e854659e456a84235c9d0e6c83f4
GIT binary patch
literal 384
zcmV-`0e}8NT4*^jL0KkKSp&m-X8-|Ce~iSi2mpL{|D6VI90Y&w->Luv01yBOFacUy
zFfy7^>5^n<0y1K3qf7x9X){skey9yaKSE5J27t)ZAkYA43?mT2DiKYJY3V&p1JY at r
z&;g(TXc~H%o3lq>V8uMmQ5A*dRK%0OZ6R&^bs>3mSO9>9#J;^9Uz;^ZSu)%Qlo1#V
zG#)KWox2F!78;lvD!bX8RhE?=?@#L?-lD7d4D8WvBC;}+HZhCgULk=1I+y_Xc-lUY
z9KEoFC}G&l*3;7mn3K-rgFK0e>HtwLb&g44Bo;x)b1I;Lm<U`2AwecHdBy9AXWPM`
z?}3Fu2Hs<kT;qxzGstu5Lt;s;16+BTs2Bl*a(znp+G{B9N_*)AfE6_ at FRG-4+vyb0
z(P~vgBOC+4w-*YbgGxZnMTX4;zavc}6{hrbBS77glXhavH^Rc<l!~K|E1`*CJIr6|
ef<zU<3}=o^1<ws*ZG~Wm_`8xR!i0bx8{;^Tr=<q~

literal 0
HcmV?d00001

diff --git a/tests/data/SOURCES/poltest-policy-1.0.tar.bz2 b/tests/data/SOURCES/poltest-policy-1.0.tar.bz2
new file mode 100644
index 0000000000000000000000000000000000000000..805abc058c9d0f748d86a187cca9810ff90eed46
GIT binary patch
literal 446
zcmV;v0YUykT4*^jL0KkKSp`94wg3U6f1Sp#2mnmy|D6m50095D-rxWP06+i`0RR99
zKmoiMAiw|s0B8UJ002rRsrnGrGbyL3ko5r227n$=3;+NC27mwn00gF#%3_(R=xCXW
zX{OOKGyr;l(|~VXwPtGh5d>lo45k<cjcWyrkQIs&blZ+lX at IB`Fb*FT?4iR!_kDzf
zNZg<KNVwt(1LPgHDcbL7788U~g$@Fy7vsI^&AB)fZJZ<sYGK*(PVL=IO=YnFr-04?
zacuy2puixN&ULN&@YEaboF*j<NijRc2QV%U4;OeGSS1HxY|KIDTwDs$sVGS(&gdB5
zzHHpqMIbI51VRtY-p70uQAKF|99wG!llt`36-17P1IK62mB7tF8EFPYSu at lYC}KUQ
z*n8zk<!BHDC<~vmI_QAd<)&!{9_v7}4O(}RCZYxRLs24mw at Ag-07026&yH2m0Wg`N
zS4SB4+uRR_C+6SZ=Squ~K(yyUo`>WHl1UD(i}hM=4nZ`Nj0iO3_sLHAfzu{rAhj at U
oRzl%}En1W!EpnGa2S3nC239B<QEeyA%m?_pk}1N3fGP_mux+rp^8f$<

literal 0
HcmV?d00001

diff --git a/tests/data/SPECS/poltest.spec b/tests/data/SPECS/poltest.spec
new file mode 100644
index 0000000..8d10b2f
--- /dev/null
+++ b/tests/data/SPECS/poltest.spec
@@ -0,0 +1,36 @@
+Summary: Policy in rpm example
+Name:    poltest
+Version: 1.0
+Release: 1
+Group:   Utilities
+License: GPL
+Source0: poltest-%{version}.tar.bz2
+Source1: poltest-policy-%{version}.tar.bz2
+Buildroot: %{_tmppath}/%{name}-%{version}-root
+
+%description
+Example for installing policy included in a package header
+
+%prep
+%setup -q
+%setup -q -T -D -a 1
+
+%build
+make CFLAGS="$RPM_OPT_FLAGS"
+make -f /usr/share/selinux/devel/Makefile -C poltest-policy-%{version}
+
+%install
+rm -rf $RPM_BUILD_ROOT
+make DESTDIR=%{buildroot} prefix=%{_prefix} install
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%files
+%defattr(-,root,root)
+%{_bindir}/poltest
+%policy poltest-policy-%{version}/poltest.pp
+
+%changelog
+* Wed Jul 1 2009 Steve Lawrence <slawrence at tresys.com>
+- create 
-- 
1.6.0.6



More information about the Rpm-maint mailing list