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&p...@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_*)af...@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...@ib`fb*ft?4ir!_kdzf zNZg<KNVwt(1lpghdcbl7788u...@fy7vsi^&AB)fZJZ<sYGK*(PVL=IO=YnFr-04? zacuy2puixN&ULN&@YEaboF*j<NijRc2QV%U4;OeGSS1HxY|KIDTwDs$sVGS(&gdB5 zzHHpqMIbI51VRtY-p70uQAKF|99wg!llt`36-17p1ik62mb7tf8efp...@lyc}kuq z*n8zk<!BHDC<~vmI_QAd<)&!{9_v7}4O(}rczyxrls2...@ag-07026&yH2m0Wg`N zS4SB4+uRR_C+6SZ=Squ~K(yyUo`>WHl1UD(i}hm=4nz`nj0io3_slhafzu{r...@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 <slawre...@tresys.com> +- create _______________________________________________ Rpm-maint mailing list Rpm-maint@lists.rpm.org http://lists.rpm.org/mailman/listinfo/rpm-maint