[Rpm-maint] [PATCH 17/19] Add hook for verifying policy modules

Steve Lawrence slawrence at tresys.com
Tue Feb 2 20:25:20 UTC 2010


In the current state, there is no verification that a file specified with
the %module tag is actually a policy module. This patch adds an rpmbuild
hook that can be used to perform module verification. A %__check_policies
macro has been added which can point to a script/executable that performs
module validation at rpmbuild time. The patch also adds a simple script,
check-policies, that checks the magic number of modules to ensure that they
are valid modules. Similar to the check-files script, the check-policies
script takes a list of files on stdin and echos any files that are not
valid modules to stdout.
---
 build/policies.c         |   61 ++++++++++++++-
 configure.ac             |   22 +++++-
 macros.in                |    2 +
 scripts/Makefile.am      |    8 ++
 scripts/check-policies.c |  200 ++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 290 insertions(+), 3 deletions(-)
 create mode 100644 scripts/check-policies.c

diff --git a/build/policies.c b/build/policies.c
index a1ee679..7da9ab7 100644
--- a/build/policies.c
+++ b/build/policies.c
@@ -9,6 +9,7 @@
 #include <rpm/argv.h>
 #include <rpm/rpmlog.h>
 #include <rpm/rpmpol.h>
+#include <rpm/rpmfc.h>
 
 #include "rpmio/rpmio_internal.h"
 #include "rpmio/base64.h"
@@ -18,6 +19,8 @@
 #define SKIPSPACE(s)    { while ((s) && *(s) &&  risspace(*(s))) (s)++; }
 #define SKIPNONSPACE(s) { while ((s) && *(s) && !risspace(*(s))) (s)++; }
 
+static StringBuf check_policyList = NULL;
+
 typedef struct ModuleRec_s {
 	char *path;
 	char *name;
@@ -234,6 +237,12 @@ static rpmRC writeModuleToHeader(ModuleRec mod, Package pkg)
 		headerPutUint32(pkg->header, RPMTAG_POLICYOBSOLETESBY, &count, 1);
 	}
 
+	/* add module to list of modules to check */
+	if (check_policyList) {
+		appendStringBuf(check_policyList, mod->path);
+		appendStringBuf(check_policyList, "\n");
+	}
+
 	rc = RPMRC_OK;
 
 exit:
@@ -457,6 +466,47 @@ exit:
 
 }
 
+/**
+ * Check packaged policies.
+ * @param fileList	packaged policy list
+ * @return		RPMRC_OK if all policies are ok, RPMRC_FAIL otherwise
+ */
+static int checkPolicies(StringBuf policyList)
+{
+	static char *const av_ckpolicy[] = { "%{?__check_policies}", NULL };
+	StringBuf sb_stdout = NULL;
+	char *s;
+	int ret;
+	int rc = RPMRC_FAIL;
+
+	s = rpmExpand(av_ckpolicy[0], NULL);
+	if (!(s && *s)) {
+		goto exit;
+	}
+
+	rpmlog(RPMLOG_NOTICE, _("Checking policy module(s): %s\n"), s);
+
+	ret = rpmfcExec(av_ckpolicy, policyList, &sb_stdout, 0);
+	if (ret < 0) {
+		goto exit;
+	}
+
+	if (sb_stdout) {
+		const char *t = getStringBuf(sb_stdout);
+		if ((*t != '\0') && (*t != '\n')) {
+			rpmlog(RPMLOG_ERR, _("Invalid policy module(s) found:\n%s"), t);
+			goto exit;
+		}
+	}
+
+	rc = RPMRC_OK;
+
+exit:
+	sb_stdout = freeStringBuf(sb_stdout);
+	s = _free(s);
+	return rc;
+}
+
 static rpmRC processPolicies(rpmSpec spec, Package pkg, int test)
 {
 	ARGV_t policies = NULL;
@@ -514,6 +564,8 @@ int processBinaryPolicies(rpmSpec spec, int test)
 	char *nvr;
 
 #if WITH_SELINUX
+	check_policyList = newStringBuf();
+
 	for (pkg = spec->packages; pkg != NULL; pkg = pkg->next) {
 		if (pkg->policyList == NULL) {
 			continue;
@@ -525,11 +577,16 @@ int processBinaryPolicies(rpmSpec spec, int test)
 
 		if (processPolicies(spec, pkg, test) != RPMRC_OK) {
 			rc = RPMRC_FAIL;
-			break;
+			goto exit;
 		}
 	}
-#endif
 
+	rc = checkPolicies(check_policyList);
+
+exit:
+	check_policyList = freeStringBuf(check_policyList);
+
+#endif
 	return rc;
 }
 
diff --git a/configure.ac b/configure.ac
index 34feb63..d58cfcb 100644
--- a/configure.ac
+++ b/configure.ac
@@ -634,11 +634,31 @@ AS_IF([test "$with_selinux" = yes],[
   ],[
     AC_MSG_ERROR([--with-selinux given, but semanage/semanage.h not found])
   ])
+
+  AC_CHECK_HEADER([sepol/sepol.h],[
+    save_LIBS="$LIBS"
+    AC_CHECK_LIB([sepol],[sepol_module_package_create],[],[
+      AC_MSG_ERROR([--with-selinux given, but sepol_module_package_create missing in libsemanage])])
+    AC_CHECK_LIB([sepol],[sepol_module_package_free],[],[
+      AC_MSG_ERROR([--with-selinux given, but sepol_module_package_free missing in libsemanage])])
+    AC_CHECK_LIB([sepol],[sepol_module_package_read],[],[
+      AC_MSG_ERROR([--with-selinux given, but sepol_module_package_read missing in libsemanage])])
+    AC_CHECK_LIB([sepol],sepol_policy_file_create[],[],[
+      AC_MSG_ERROR([--with-selinux given, but sepol_policy_file_create missing in libsemanage])])
+    AC_CHECK_LIB([sepol],[sepol_policy_file_free],[],[
+      AC_MSG_ERROR([--with-selinux given, but sepol_policy_file_free missing in libsemanage])])
+    AC_CHECK_LIB([sepol],[sepol_policy_file_set_fp],[],[
+      AC_MSG_ERROR([--with-selinux given, but sepol_policy_file_set_fp missing in libsemanage])])
+    LIBS="$save_LIBS"
+  ],[
+    AC_MSG_ERROR([--with-selinux given, but sepol/sepol.h not found])
+  ])
+
 ])
 
 AS_IF([test "$with_selinux" = yes],[
   AC_DEFINE(WITH_SELINUX, 1, [Build with selinux support?])
-  WITH_SELINUX_LIB="-lselinux -lsemanage"
+  WITH_SELINUX_LIB="-lselinux -lsemanage -lsepol"
 ])
 AC_SUBST(WITH_SELINUX_LIB)
 AM_CONDITIONAL(SELINUX,[test "$with_selinux" = yes])
diff --git a/macros.in b/macros.in
index 3be717d..a0e2853 100644
--- a/macros.in
+++ b/macros.in
@@ -371,6 +371,8 @@ package or when debugging this package.\
 # Note: Disable (by commenting out) for legacy compatibility.
 %__check_files         %{_rpmconfigdir}/check-files %{buildroot}
 
+%__check_policies      %{_rpmconfigdir}/check-policies %{_builddir}/%{?buildsubdir}/
+
 #
 # Should unpackaged files in a build root terminate a build?
 #
diff --git a/scripts/Makefile.am b/scripts/Makefile.am
index b2927d9..f0c17d5 100644
--- a/scripts/Makefile.am
+++ b/scripts/Makefile.am
@@ -4,6 +4,8 @@ include $(top_srcdir)/rpm.am
 
 CLEANFILES =
 
+rpmconfig_PROGRAMS =
+
 EXTRA_DIST = \
 	brp-compress brp-python-bytecompile brp-java-gcjcompile \
 	brp-strip brp-strip-comment-note brp-python-hardlink \
@@ -41,3 +43,9 @@ rpmconfig_DATA = \
 	rpmdiff.cgi rpm.daily rpm.log rpm.xinetd \
 	macros.perl macros.php macros.python
 
+if SELINUX
+rpmconfig_PROGRAMS += check-policies
+check_policies_SOURCES = check-policies.c
+check_policies_LDADD   = @WITH_SELINUX_LIB@
+endif
+
diff --git a/scripts/check-policies.c b/scripts/check-policies.c
new file mode 100644
index 0000000..b119c0f
--- /dev/null
+++ b/scripts/check-policies.c
@@ -0,0 +1,200 @@
+/* Author: Joshua Brindle <method at manicmethod.com>
+ *         Chad Sellers <csellers at tresys.com>
+ *
+ * Copyright (C) 2010 Tresys Technology, LLC
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <sepol/module.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <linux/limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <bzlib.h>
+#include <stdio_ext.h>
+
+
+void usage(char* cmd);
+ssize_t bunzip(FILE *f, char **data);
+int verify_package(char *filename);
+
+
+void usage(char *cmd) {
+	printf("Usage: %s ROOT_PATH\n", cmd);
+	exit(1);
+}
+
+#define BZ2_MAGICSTR "BZh"
+#define BZ2_MAGICLEN (sizeof(BZ2_MAGICSTR)-1)
+
+/* bunzip() a file to '*data', returning the total number of uncompressed bytes
+ *  * in the file.  Returns -1 if file could not be decompressed. */
+ssize_t bunzip(FILE *f, char **data)
+{
+	BZFILE* b;
+	size_t  nBuf;
+	char    *buf = NULL;
+	size_t  size = 1<<18;
+	int     bzerror;
+	size_t  total = 0;
+	ssize_t ret = -1;
+
+	buf = malloc(size);
+	if (!buf)
+		goto bad;
+	bzerror = fread(buf, 1, BZ2_MAGICLEN, f);
+	rewind(f);
+	if ((bzerror != BZ2_MAGICLEN) || memcmp(buf, BZ2_MAGICSTR, BZ2_MAGICLEN))
+		goto bad;
+
+	b = BZ2_bzReadOpen ( &bzerror, f, 0, 1, NULL, 0 );
+	if ( bzerror != BZ_OK ) {
+		BZ2_bzReadClose ( &bzerror, b );
+		goto bad;
+	}
+
+	char *uncompress = realloc(NULL, size);
+
+	while ( bzerror == BZ_OK) {
+		nBuf = BZ2_bzRead ( &bzerror, b, buf, sizeof(buf));
+		if (( bzerror == BZ_OK ) || ( bzerror == BZ_STREAM_END )) {
+			if (total + nBuf > size) {
+				size *= 2;
+				uncompress = realloc(uncompress, size);
+			}
+			memcpy(&uncompress[total], buf, nBuf);
+			total += nBuf;
+		}
+	}
+	if ( bzerror != BZ_STREAM_END ) {
+		BZ2_bzReadClose ( &bzerror, b );
+		free(uncompress);
+		goto bad;
+	}
+	else {
+		ret = total;
+	}
+	BZ2_bzReadClose ( &bzerror, b );
+
+	*data = uncompress;
+bad:
+	free(buf);
+	return ret;
+}
+
+/* Verify a policy package at filename
+ * return -1 for program error (e.g. out of memory)
+ * return 1 for invalid package
+ * return 0 for good pacakge
+ */
+int verify_package(char *filename)
+{
+	int ret = -1;
+	FILE *fp = NULL;
+	struct sepol_policy_file *pf = NULL;
+	sepol_module_package_t *p = NULL;
+	ssize_t size;
+	char *data = NULL;
+
+
+	if (sepol_module_package_create(&p)) {
+		fprintf(stderr, "Out of memory\n");
+		goto bad;
+	}
+	if (sepol_policy_file_create(&pf)) {
+		fprintf(stderr, "Out of memory\n");
+		goto bad;
+	}
+	fp = fopen(filename, "r");
+	if (!fp) {
+		fprintf(stderr, "Could not open file %s:  %s\n", filename, strerror(errno));
+		goto bad;
+	}
+
+	if ((size = bunzip(fp, &data)) > 0) {
+		fclose(fp);
+		fp = fmemopen(data, size, "rb");
+		if (!fp) {
+			fprintf(stderr, "Out of memory!\n");
+			goto bad;
+		}
+	}
+
+	rewind(fp);
+	__fsetlocking(fp, FSETLOCKING_BYCALLER);
+	sepol_policy_file_set_fp(pf, fp);
+
+	ret = sepol_module_package_read(p, pf, 0);
+	if (ret) {
+		fprintf(stderr, "Error while reading package from %s\n", filename);
+		ret = 1;
+		goto bad;
+	}
+
+	ret = 0;
+
+bad:
+	free(data);
+	sepol_module_package_free(p);
+	sepol_policy_file_free(pf);
+	if (fp)
+		fclose(fp);
+	return ret;
+}
+
+int main(int argc, char **argv)
+{
+	struct stat s;
+	char path[PATH_MAX];
+	char module[PATH_MAX];
+	int ret;
+
+	if (argc != 2)
+		usage(argv[0]);
+
+	char *root_dir = argv[1];
+
+	if (stat(root_dir, &s) < 0) {
+		fprintf(stderr, "ROOT_PATH %s does not exist\n", root_dir);
+		exit(1);
+	}
+
+	if (!S_ISDIR(s.st_mode)) {
+		fprintf(stderr, "ROOT_PATH %s is not a directory\n", root_dir);
+		exit(1);
+	}
+
+	while (fgets(module, PATH_MAX, stdin)) {
+		if (module[strlen(module) - 1] == '\n') {
+			module[strlen(module) - 1] = '\0';
+		}
+		snprintf(path, PATH_MAX, "%s/%s", root_dir, module);
+		ret = verify_package(path);
+		if (ret == -1) {
+			/* program error, we need to bail */
+			fprintf(stdout, "Error verifying policy %s\n", path);
+			exit(1);
+		} else if (ret == 1) {
+			/* invalid package, print filename to tell caller */
+			fprintf(stdout, "   %s\n", path);
+		}
+	}
+	return 0;
+}
-- 
1.6.2.5



More information about the Rpm-maint mailing list