Skip to content

Commit

Permalink
Merge pull request #3610 from cgwalters/rhel9-ima
Browse files Browse the repository at this point in the history
build: Add support for propagating RPM IMA signatures
  • Loading branch information
jmarrero authored May 6, 2022
2 parents f9d4c46 + dd659e6 commit a186e98
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 19 deletions.
3 changes: 3 additions & 0 deletions docs/treefile.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ It supports the following parameters:
* `selinux`: boolean, optional: Defaults to `true`. If `false`, then
no SELinux labeling will be performed on the server side.

* `ima`: boolean, optional: Defaults to `false`. Propagate any
IMA signatures in input RPMs into the final OSTree commit.

* `boot-location` (or `boot_location`): string, optional:
There are 2 possible values:
* "new": A misnomer, this value is no longer "new". Kernel data
Expand Down
1 change: 1 addition & 0 deletions rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,7 @@ pub mod ffi {
fn get_documentation(&self) -> bool;
fn get_recommends(&self) -> bool;
fn get_selinux(&self) -> bool;
fn get_ima(&self) -> bool;
fn get_releasever(&self) -> String;
fn get_repo_metadata_target(&self) -> RepoMetadataTarget;
fn rpmdb_backend_is_target(&self) -> bool;
Expand Down
7 changes: 7 additions & 0 deletions rust/src/treefile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,7 @@ fn treefile_merge(dest: &mut TreeComposeConfig, src: &mut TreeComposeConfig) {
basearch,
rojig,
selinux,
ima,
gpg_key,
include,
container,
Expand Down Expand Up @@ -1257,6 +1258,10 @@ impl Treefile {
self.parsed.base.selinux.unwrap_or(true)
}

pub(crate) fn get_ima(&self) -> bool {
self.parsed.base.ima.unwrap_or(false)
}

pub(crate) fn get_releasever(&self) -> String {
self.parsed
.base
Expand Down Expand Up @@ -2277,6 +2282,8 @@ pub(crate) struct BaseComposeConfigFields {
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) selinux: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) ima: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) gpg_key: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) include: Option<Include>,
Expand Down
3 changes: 3 additions & 0 deletions src/libpriv/rpmostree-core.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -2425,6 +2425,9 @@ start_async_import_one_package (RpmOstreeContext *self, DnfPackage *pkg, GCancel
if (self->treefile_rs->get_readonly_executables ())
flags |= RPMOSTREE_IMPORTER_FLAGS_RO_EXECUTABLES;

if (self->treefile_rs->get_ima ())
flags |= RPMOSTREE_IMPORTER_FLAGS_IMA;

/* TODO - tweak the unpacker flags for containers */
OstreeRepo *ostreerepo = get_pkgcache_repo (self);
g_autoptr (RpmOstreeImporter) unpacker = rpmostree_importer_new_take_fd (
Expand Down
4 changes: 4 additions & 0 deletions src/libpriv/rpmostree-core.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ G_BEGIN_DECLS
#define RPMOSTREE_DIR_CACHE_SOLV "solv"
#define RPMOSTREE_DIR_LOCK "lock"

// Extended attribute used at build time.
#define RPMOSTREE_USER_IMA "user.ima"
#define RPMOSTREE_SYSTEM_IMA "security.ima"

/* See http://lists.rpm.org/pipermail/rpm-maint/2017-October/006681.html */
/* This is also defined on the Rust side. */
#define RPMOSTREE_RPMDB_LOCATION "usr/share/rpm"
Expand Down
68 changes: 61 additions & 7 deletions src/libpriv/rpmostree-importer.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#include <grp.h>
#include <pwd.h>
#include <rpm/rpmfi.h>
#include <rpm/rpmfiles.h>
#include <rpm/rpmlib.h>
#include <rpm/rpmlog.h>
#include <rpm/rpmts.h>
Expand Down Expand Up @@ -146,7 +147,7 @@ rpmostree_importer_read_metainfo (int fd, Header *out_header, gsize *out_cpio_of

if (out_fi)
{
ret_fi = rpmfiNew (ts, ret_header, RPMTAG_BASENAMES, (RPMFI_NOHEADER | RPMFI_FLAGS_INSTALL));
ret_fi = rpmfiNew (ts, ret_header, RPMTAG_BASENAMES, RPMFI_NOHEADER | RPMFI_FLAGS_QUERY);
ret_fi = rpmfiInit (ret_fi, 0);
}

Expand All @@ -159,6 +160,30 @@ rpmostree_importer_read_metainfo (int fd, Header *out_header, gsize *out_cpio_of
return TRUE;
}

/*
* ima_heck_zero_hdr: Check the signature for a zero header
*
* Check whether the given signature has a header with all zeros
*
* Returns -1 in case the signature is too short to compare
* (invalid signature), 0 in case the header is not only zeroes,
* and 1 if it is only zeroes.
*/
static int
ima_check_zero_hdr (const unsigned char *fsig, size_t siglen)
{
/*
* Every signature has a header signature_v2_hdr as defined in
* Linux's (4.5) security/integrity/integtrity.h. The following
* 9 bytes represent this header in front of the signature.
*/
static const uint8_t zero_hdr[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };

if (siglen < sizeof (zero_hdr))
return -1;
return (memcmp (fsig, &zero_hdr, sizeof (zero_hdr)) == 0);
}

static void
build_rpmfi_overrides (RpmOstreeImporter *self)
{
Expand All @@ -179,10 +204,14 @@ build_rpmfi_overrides (RpmOstreeImporter *self)
const char *fn = rpmfiFN (self->fi);
rpmfileAttrs fattrs = rpmfiFFlags (self->fi);

size_t siglen = 0;
const unsigned char *fsig = rpmfiFSignature (self->fi, &siglen);
const bool have_ima = (siglen > 0 && fsig && (ima_check_zero_hdr (fsig, siglen) == 0));

const gboolean user_is_root = (user == NULL || g_str_equal (user, "root"));
const gboolean group_is_root = (group == NULL || g_str_equal (group, "root"));
const gboolean fcaps_is_unset = (fcaps == NULL || fcaps[0] == '\0');
if (!(user_is_root && group_is_root && fcaps_is_unset))
if (!(user_is_root && group_is_root && fcaps_is_unset) || have_ima)
{
g_hash_table_insert (self->rpmfi_overrides, g_strdup (fn), GINT_TO_POINTER (i));
}
Expand Down Expand Up @@ -249,7 +278,7 @@ rpmostree_importer_new_take_fd (int *fd, OstreeRepo *repo, DnfPackage *pkg,

static void
get_rpmfi_override (RpmOstreeImporter *self, const char *path, const char **out_user,
const char **out_group, const char **out_fcaps)
const char **out_group, const char **out_fcaps, GVariant **out_ima)
{
gpointer v;

Expand All @@ -266,6 +295,18 @@ get_rpmfi_override (RpmOstreeImporter *self, const char *path, const char **out_
*out_group = rpmfiFGroup (self->fi);
if (out_fcaps)
*out_fcaps = rpmfiFCaps (self->fi);

if (out_ima)
{
size_t siglen;
const guint8 *fsig = rpmfiFSignature (self->fi, &siglen);
if (siglen > 0 && fsig && (ima_check_zero_hdr (fsig, siglen) == 0))
{
GVariant *ima_signature
= g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, fsig, siglen, 1);
*out_ima = g_variant_ref_sink (ima_signature);
}
}
}

static gboolean
Expand Down Expand Up @@ -461,7 +502,7 @@ compose_filter_cb (OstreeRepo *repo, const char *path, GFileInfo *file_info, gpo
/* Lookup any rpmfi overrides (was parsed from the header) */
const char *user = NULL;
const char *group = NULL;
get_rpmfi_override (self, path, &user, &group, NULL);
get_rpmfi_override (self, path, &user, &group, NULL, NULL);

auto entry = ROSCXX_VAL (
tmpfiles_translate (path, *file_info, user ?: "root", group ?: "root"), error);
Expand Down Expand Up @@ -501,12 +542,25 @@ xattr_cb (OstreeRepo *repo, const char *path, GFileInfo *file_info, gpointer use
auto self = static_cast<RpmOstreeImporter *> (user_data);
const char *fcaps = NULL;

get_rpmfi_override (self, path, NULL, NULL, &fcaps);
GVariant *imasig = NULL;
const bool use_ima = self->flags & RPMOSTREE_IMPORTER_FLAGS_IMA;
get_rpmfi_override (self, path, NULL, NULL, &fcaps, use_ima ? &imasig : NULL);

g_auto (GVariantBuilder) builder;
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ayay)"));
if (fcaps != NULL && fcaps[0] != '\0')
return rpmostree_fcap_to_xattr_variant (fcaps);
{
g_autoptr (GVariant) fcaps_v = rpmostree_fcap_to_ostree_xattr (fcaps);
g_variant_builder_add_value (&builder, fcaps_v);
}

if (imasig)
{
g_variant_builder_add (&builder, "(@ay@ay)", g_variant_new_bytestring (RPMOSTREE_SYSTEM_IMA),
imasig);
}

return NULL;
return g_variant_ref_sink (g_variant_builder_end (&builder));
}

/* Given a path in an RPM archive, possibly translate it for ostree convention. */
Expand Down
2 changes: 2 additions & 0 deletions src/libpriv/rpmostree-importer.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,14 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (RpmOstreeImporter, g_object_unref)
* ostree-compliant paths rather than erroring out
* @RPMOSTREE_IMPORTER_FLAGS_NODOCS: Skip documentation files
* @RPMOSTREE_IMPORTER_FLAGS_RO_EXECUTABLES: Make executable files readonly
* @RPMOSTREE_IMPORTER_FLAGS_IMA: Enable IMA
*/
typedef enum
{
RPMOSTREE_IMPORTER_FLAGS_SKIP_EXTRANEOUS = (1 << 0),
RPMOSTREE_IMPORTER_FLAGS_NODOCS = (1 << 1),
RPMOSTREE_IMPORTER_FLAGS_RO_EXECUTABLES = (1 << 2),
RPMOSTREE_IMPORTER_FLAGS_IMA = (1 << 3),
} RpmOstreeImporterFlags;

RpmOstreeImporter *rpmostree_importer_new_take_fd (int *fd, OstreeRepo *repo, DnfPackage *pkg,
Expand Down
76 changes: 64 additions & 12 deletions src/libpriv/rpmostree-postprocess.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,36 @@ struct CommitThreadData
GError **error;
};

// In unified core mode, we'll see user-mode checkout files.
// What we want for now is to access the embedded xattr values
// which (along with other canonical file metadata) have been serialized
// into the `user.ostreemeta` extended attribute. What we should
// actually do is add nice support for this in core ostree, and also
// automatically pick up file owner for example. This would be a key
// thing to unblock fully unprivileged builds.
//
// But for now, just slurp up the xattrs so we get IMA in particular.
static void
extend_ostree_xattrs (GVariantBuilder *builder, GVariant *vbytes)
{
g_autoptr (GBytes) bytes = g_variant_get_data_as_bytes (vbytes);
g_autoptr (GVariant) filemeta = g_variant_ref_sink (
g_variant_new_from_bytes (OSTREE_FILEMETA_GVARIANT_FORMAT, bytes, false));
g_autoptr (GVariant) xattrs = g_variant_get_child_value (filemeta, 3);
g_autoptr (GVariantIter) viter = g_variant_iter_new (xattrs);
GVariant *key, *value;
while (g_variant_iter_loop (viter, "(@ay@ay)", &key, &value))
{
const char *attrkey = g_variant_get_bytestring (key);
// Don't try to add the SELinux label - that's handled by the full
// relabel we do, although in the future it may be interesting to
// try canonically relying on the labeled pkgcache.
if (g_str_equal (attrkey, "security.selinux"))
continue;
g_variant_builder_add (builder, "(@ay@ay)", key, value);
}
}

/* Filters out all xattrs that aren't accepted. */
static GVariant *
filter_xattrs_cb (OstreeRepo *repo, const char *relpath, GFileInfo *file_info, gpointer user_data)
Expand All @@ -651,20 +681,16 @@ filter_xattrs_cb (OstreeRepo *repo, const char *relpath, GFileInfo *file_info, g
static const char *accepted_xattrs[] = {
"security.capability", /* https://lwn.net/Articles/211883/ */
"user.pax.flags", /* https://github.com/projectatomic/rpm-ostree/issues/412 */
"user.ima" /* will be replaced with security.ima */
RPMOSTREE_USER_IMA, /* will be replaced with security.ima */
};
g_autoptr (GVariant) existing_xattrs = NULL;
g_autoptr (GVariantIter) viter = NULL;
GError *local_error = NULL;
GError **error = &local_error;
GVariant *key, *value;
GVariantBuilder builder;

// From here, ensure the path is relative
if (relpath[0] == '/')
relpath++;

g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ayay)"));

if (!*relpath)
{
if (!glnx_fd_get_all_xattrs (rootfs_fd, &existing_xattrs, NULL, error))
Expand All @@ -682,19 +708,45 @@ filter_xattrs_cb (OstreeRepo *repo, const char *relpath, GFileInfo *file_info, g
g_atomic_int_set (&tdata->percent, (gint)((100.0 * tdata->n_processed) / tdata->n_bytes));
}

viter = g_variant_iter_new (existing_xattrs);
GVariantBuilder builder;
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ayay)"));

while (g_variant_iter_loop (viter, "(@ay@ay)", &key, &value))
GVariantIter viter;
g_variant_iter_init (&viter, existing_xattrs);
// Look for the special user.ostreemeta xattr; if present then it wins
GVariant *key, *value;
while (g_variant_iter_loop (&viter, "(@ay@ay)", &key, &value))
{
const char *attrkey = g_variant_get_bytestring (key);

// If it's the special bare-user xattr, then slurp out the embedded
// xattrs.
if (g_str_equal (attrkey, "user.ostreemeta"))
{
extend_ostree_xattrs (&builder, value);
return g_variant_ref_sink (g_variant_builder_end (&builder));
}
}

// Otherwise, find the physical xattrs; this happens in the unified core case.
g_variant_iter_init (&viter, existing_xattrs);
while (g_variant_iter_loop (&viter, "(@ay@ay)", &key, &value))
{
const char *attrkey = g_variant_get_bytestring (key);

for (guint i = 0; i < G_N_ELEMENTS (accepted_xattrs); i++)
{
const char *validkey = accepted_xattrs[i];
const char *attrkey = g_variant_get_bytestring (key);
if (g_str_equal (validkey, attrkey))
{
if (g_str_equal (validkey, "user.ima"))
g_variant_builder_add (&builder, "(@ay@ay)",
g_variant_new_bytestring ("security.ima"), value);
// Translate user.ima to its final security.ima value. This allows handling
// IMA outside of rpm-ostree, without needing IMA to be enabled on the
// "host" system.
if (g_str_equal (validkey, RPMOSTREE_USER_IMA))
{
g_variant_builder_add (&builder, "(@ay@ay)",
g_variant_new_bytestring (RPMOSTREE_SYSTEM_IMA), value);
}
else
g_variant_builder_add (&builder, "(@ay@ay)", key, value);
}
Expand Down

0 comments on commit a186e98

Please sign in to comment.