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

Stephen Lawrence slawrence at tresys.com
Mon Jul 6 19:42:28 UTC 2009


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 sucessfully installed, you can run

   # semodule -l | grep poltest

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

diff --git a/lib/psm.c b/lib/psm.c
index e552c6c..c8ba1c6 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)
 {
@@ -1613,6 +1693,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 c7ee82a..bbf8ed9 100644
--- a/lib/rpmte.c
+++ b/lib/rpmte.c
@@ -66,6 +66,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;
@@ -895,6 +900,11 @@ int rpmteHaveTransScript(rpmte te, rpmTag tag)
     return rc;
 }
 
+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 e88dad5..e775dc0 100644
--- a/lib/rpmte_internal.h
+++ b/lib/rpmte_internal.h
@@ -86,6 +86,9 @@ int rpmteMarkFailed(rpmte te, rpmts ts);
 RPM_GNUC_INTERNAL
 int rpmteHaveTransScript(rpmte te, rpmTag tag);
 
+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 f1a5290..537f133 100644
--- a/lib/transaction.c
+++ b/lib/transaction.c
@@ -877,6 +877,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)
 {
@@ -1170,6 +1245,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/*|RPMTRANS_FLAG_NOPOLICY*/))
+     	  || (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 



More information about the Rpm-maint mailing list