Skip to content

Commit

Permalink
[antlir2][rpms] unit test for rpm with file capabilities
Browse files Browse the repository at this point in the history
Summary:
As I work on rootless image builds I want to make sure that rpms that have
binaries with file capabilities are correctly installed.

Test Plan:
```
❯ buck2 test fbcode//antlir/antlir2/test_images/rpms:test-binaries-with-file-capabilities
Buck UI: https://www.internalfb.com/buck2/c8e3191c-eba3-493b-9d2b-e02845f7a5f4
Test UI: https://www.internalfb.com/intern/testinfra/testrun/5910974717837853
Note:    Using experimental modern dice
Network: Up: 4.8KiB  Down: 0B  (reSessionID-da12f5a1-f8b2-4a7b-85d2-f37b3817e7de)
Jobs completed: 9. Time elapsed: 4.0s.
Cache hits: 0%. Commands: 1 (cached: 0, remote: 0, local: 1)
Tests finished: Pass 1. Fail 0. Fatal 0. Skip 0. Build failure 0
```

Reviewed By: naveedgol

Differential Revision: D51439248

fbshipit-source-id: f979f4a6c399c71bfca5ab693a3f3dbcddebb570
  • Loading branch information
vmagro authored and facebook-github-bot committed Nov 28, 2023
1 parent 7e74887 commit 6bc2dfe
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 1 deletion.
1 change: 1 addition & 0 deletions antlir/antlir2/antlir2_packager/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ rust_binary(
"//antlir/antlir2/antlir2_isolate:antlir2_isolate",
"//antlir/antlir2/antlir2_rootless:antlir2_rootless",
"//antlir/antlir2/antlir2_working_volume:antlir2_working_volume",
"//antlir/antlir2/libcap:libcap",
"//antlir/util/cli/json_arg:json_arg",
],
)
11 changes: 10 additions & 1 deletion antlir/antlir2/antlir2_packager/src/rpm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

use std::fmt::Write as _;
use std::fs::File;
use std::io::Write as _;
use std::path::Path;
use std::path::PathBuf;
Expand All @@ -17,6 +18,7 @@ use anyhow::ensure;
use anyhow::Context;
use anyhow::Result;
use itertools::Itertools;
use libcap::FileExt as _;
use serde::Deserialize;
use tempfile::NamedTempFile;

Expand Down Expand Up @@ -66,7 +68,7 @@ impl PackageFormat for Rpm {
.join("\n");

let requires_post = self
.requires
.requires_post
.iter()
.map(|r| format!("Requires(post): {r}"))
.join("\n");
Expand Down Expand Up @@ -168,6 +170,13 @@ License: {license}
if relpath == Path::new("/") {
continue;
}
if let Some(caps) = File::open(entry.path()).and_then(|f| f.get_capabilities())? {
let caps = caps.to_text()?;
spec.push_str("%caps(");
spec.push_str(&caps);
spec.push_str(") ");
}

spec.push_str(relpath.to_str().expect("our paths are always valid utf8"));
spec.push('\n');
}
Expand Down
22 changes: 22 additions & 0 deletions antlir/antlir2/libcap/BUCK
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
load("//antlir/bzl:build_defs.bzl", "rust_bindgen_library", "rust_library")

oncall("antlir")

rust_bindgen_library(
name = "libcap-sys",
cpp_deps = [
"third-party//libcap:cap",
],
generate = ("types", "functions", "vars"),
header = "bridge.h",
visibility = [],
)

rust_library(
name = "libcap",
srcs = glob(["src/*.rs"]),
deps = [
"libc",
":libcap-sys",
],
)
8 changes: 8 additions & 0 deletions antlir/antlir2/libcap/bridge.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#include "sys/capability.h"
74 changes: 74 additions & 0 deletions antlir/antlir2/libcap/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

use std::ffi::CStr;
use std::os::fd::AsRawFd;
use std::os::raw::c_char;
use std::os::raw::c_void;

use libc::ENODATA;

pub type Result<T> = std::io::Result<T>;

pub struct Capabilities(libcap_sys::cap_t);

pub trait FileExt {
fn get_capabilities(&self) -> Result<Option<Capabilities>>;
}

impl FileExt for std::fs::File {
fn get_capabilities(&self) -> Result<Option<Capabilities>> {
let ret = unsafe { libcap_sys::cap_get_fd(self.as_raw_fd()) };
if ret.is_null() {
let err = std::io::Error::last_os_error();
if err.raw_os_error().expect("must be set") == ENODATA {
Ok(None)
} else {
Err(err)
}
} else {
Ok(Some(Capabilities(ret)))
}
}
}

impl Drop for Capabilities {
fn drop(&mut self) {
unsafe {
libcap_sys::cap_free(self.0 as *mut c_void);
}
}
}

struct CapText(*mut c_char);

impl Drop for CapText {
fn drop(&mut self) {
unsafe {
libcap_sys::cap_free(self.0 as *mut c_void);
}
}
}

impl Capabilities {
fn cap_text(&self) -> Result<CapText> {
let s = unsafe { libcap_sys::cap_to_text(self.0, std::ptr::null_mut()) };
if s.is_null() {
Err(std::io::Error::last_os_error())
} else {
Ok(CapText(s))
}
}

pub fn to_text(&self) -> Result<String> {
let cap_text = self.cap_text()?;
let cstr = unsafe { CStr::from_ptr(cap_text.0) };
cstr.to_str()
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
.map(|s| s.to_owned())
}
}
18 changes: 18 additions & 0 deletions antlir/antlir2/test_images/rpms/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -391,3 +391,21 @@ image_python_test(
srcs = ["test_db_backend.py"],
layer = simple,
)

test_binaries_with_file_capabilities_layer = test_rpms(
name = "test-binaries-with-file-capabilities-layer",
expected = expected_t(installed = [
"antlir2-with-capability",
]),
features = [feature.rpms_install(rpms = [
"libcap", # getcap cli
"antlir2-with-capability",
])],
parent_layer = simple,
)

image_python_test(
name = "test-binaries-with-file-capabilities",
srcs = ["test_binaries_with_file_capabilities.py"],
layer = test_binaries_with_file_capabilities_layer,
)
25 changes: 25 additions & 0 deletions antlir/antlir2/test_images/rpms/repo/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,31 @@ all_rpms.append(
),
)

image.layer(
name = "antlir2-with-capability-layer",
features = [
feature.ensure_dirs_exist(dirs = "/usr/bin"),
feature.install(
src = "//antlir:empty",
dst = "/usr/bin/antlir2-with-capability",
xattrs = {
"security.capability": "0sAQAAAoAAAAAAAAAAAAAAAAAAAAA=", #cap_setuid=ep
},
),
],
flavor = "//antlir/antlir2/test_images:test-image-flavor",
)

all_rpms.append(
test_rpm(
name = "antlir2-with-capability",
epoch = 0,
parent_layer = ":antlir2-with-capability-layer",
release = "1",
version = "1",
),
)

repo(
name = "test-repo",
compress = "none",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.


import subprocess
import unittest


class TestBinariesWithFileCapabilities(unittest.TestCase):
def setUp(self) -> None:
super().setUp()

def test_newuidmap_caps(self) -> None:
self.assertEqual(
subprocess.run(
["getcap", "/usr/bin/antlir2-with-capability"],
capture_output=True,
text=True,
check=True,
).stdout.strip(),
"/usr/bin/antlir2-with-capability cap_setuid=ep",
)

0 comments on commit 6bc2dfe

Please sign in to comment.