Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
repo: cross
matches: ${{ matrix.platform }}
token: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: cross-${{ matrix.platform }}
path: ${{ steps.cross.outputs.install_path }}
Expand All @@ -36,22 +36,26 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Build
run: cargo build --verbose
- name: Install request-key for Request/Instantiate flow
run: |
cargo build --example request-key --features std
sudo mv ./target/debug/examples/request-key /sbin/request-key
- name: Run tests
run: cargo test --verbose
run: cargo test --verbose -- --include-ignored

# Ensure clippy and formatting pass
clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1
with:
profile: minimal
Expand All @@ -71,14 +75,14 @@ jobs:
runs-on: ubuntu-latest
needs: install-cross
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
submodules: 'recursive'
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
- name: Download Cross
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: cross-linux-musl
path: /tmp
Expand Down
17 changes: 11 additions & 6 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Install stable toolchain
uses: actions-rs/toolchain@v1
Expand All @@ -23,14 +23,19 @@ jobs:
toolchain: stable
override: true

- name: Install cargo-tarpaulin
run: cargo install cargo-tarpaulin

- name: Install request-key for Request/Instantiate flow
run: |
cargo build --example request-key --features std
sudo mv ./target/debug/examples/request-key /sbin/request-key

- name: Run cargo-tarpaulin
uses: actions-rs/tarpaulin@v0.1
with:
version: '0.21.0'
args: "--lib"
run: cargo-tarpaulin --lib -- --include-ignored

- name: Upload to codecov.io
uses: codecov/codecov-action@v2
uses: codecov/codecov-action@v5
with:
file: ./cobertura.xml

12 changes: 8 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@ std = ["bitflags/std"]
name = "keyctl"
required-features = ["std"]

[[example]]
name = "request-key"
required-features = ["std"]

[dependencies]
libc = {version = "0.2.132", default-features = false}
bitflags = {version = "2.4", default-features = false}
libc = {version = "0.2.158", default-features = false}
bitflags = {version = "2.6", default-features = false}

[dev-dependencies]
zeroize = "1.5.7"
clap = {version = "4.4.11", default-features = false, features = ["std", "derive"]}
zeroize = "1.8.1"
clap = {version = "4.5.16", default-features = false, features = ["std", "derive", "help"]}
25 changes: 24 additions & 1 deletion examples/keyctl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@
//!
//! Demo code for the linux_keyutils crate.
use clap::Parser;
use linux_keyutils::{Key, KeyRing, KeyRingIdentifier, KeySerialId};
use linux_keyutils::{KeyPermissionsBuilder, Permission};
use linux_keyutils::{KeyRing, KeyRingIdentifier};
use std::error::Error;
use zeroize::Zeroizing;

#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
#[command(arg_required_else_help(true))]
#[command(subcommand_required(true))]
struct Args {
#[clap(subcommand)]
subcommand: Command,
}

#[derive(clap::Subcommand, Debug, PartialEq)]
#[command(arg_required_else_help(true))]
enum Command {
/// Create a new key
Create {
Expand Down Expand Up @@ -51,6 +54,17 @@ enum Command {
#[clap(short, long)]
description: String,
},
/// Instantiate a partially constructed key
Instantiate {
#[clap(short, long)]
keyid: Option<i32>,

#[clap(short, long)]
payload: String,

#[clap(short, long)]
ring: Option<i32>,
},
}

fn main() -> Result<(), Box<dyn Error>> {
Expand Down Expand Up @@ -104,6 +118,15 @@ fn main() -> Result<(), Box<dyn Error>> {
key.invalidate()?;
println!("Removed key with ID {:?}", key.get_id());
}
// Instantiate a partially constructed key
Command::Instantiate {
keyid,
payload,
ring,
} => {
let key = Key::from_id(KeySerialId::new(keyid.unwrap_or(i32::MAX)));
key.instantiate(&payload, KeySerialId::new(ring.unwrap_or(i32::MAX)))?;
}
};

Ok(())
Expand Down
65 changes: 65 additions & 0 deletions examples/request-key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//! Request Key Implementation (replacement for /sbin/request-key)
//!
//! https://www.kernel.org/doc/html/v4.15/security/keys/request-key.html
use clap::Parser;
use linux_keyutils::{Key, KeyRingIdentifier, KeySerialId};
use std::error::Error;
use zeroize::Zeroizing;

#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
#[command(arg_required_else_help(true))]
#[command(subcommand_required(true))]
struct Args {
#[clap(subcommand)]
subcommand: Command,
}

#[derive(clap::Subcommand, Debug, PartialEq)]
#[command(arg_required_else_help(true))]
enum Command {
/// Kernel invokes this program with the following parameters
///
/// https://github.com/torvalds/linux/blob/7d06015d936c861160803e020f68f413b5c3cd9d/security/keys/request_key.c#L116
///
/// Path is hard coded to /sbin/request-key
Create {
key_id: i32,
uid: u32,
gid: u32,
thread_ring: i32,
process_ring: i32,
session_ring: i32,
},
}

fn main() -> Result<(), Box<dyn Error>> {
let args = Args::parse();
_ = match args.subcommand {
// Add a new key to the keyring
Command::Create {
key_id,
uid,
gid,
thread_ring: _,
process_ring: _,
session_ring,
} => {
// Assume authority over the temporary key
let key = Key::from_id(KeySerialId(key_id));
key.assume_authority()?;

// Ensure the ownership is correct
key.chown(Some(uid), Some(gid))?;

// Read payload from special key KeyRingIdentifier::ReqKeyAuthKey
let reqkey = Key::from_id(KeySerialId(KeyRingIdentifier::ReqKeyAuthKey as i32));
let mut buf = Zeroizing::new([0u8; 2048]);
let len = reqkey.read(&mut buf)?;

// Instantiate key
key.instantiate(&buf[..len], KeySerialId(session_ring))?;
}
};
Ok(())
}
11 changes: 11 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ pub enum KeyError {
/// Write to destination failed
WriteError,

// Insufficient permissions
PermissionDenied,

// Missing file or directory (ENOENT)
//
// For request_key this could be due to a missing /sbin/request-key
// binary. I.e. keyutils utilities are not installed.
MissingFileOrDirectory,

/// Unknown - catch all, return this instead of panicing
Unknown(i32),
}
Expand All @@ -73,6 +82,8 @@ impl KeyError {
pub fn from_errno() -> KeyError {
match unsafe { *libc::__errno_location() } {
// Create Errors
libc::ENOENT => KeyError::MissingFileOrDirectory,
libc::EPERM => KeyError::PermissionDenied,
libc::EACCES => KeyError::AccessDenied,
libc::EDQUOT => KeyError::QuotaExceeded,
libc::EFAULT => KeyError::BadAddress,
Expand Down
59 changes: 59 additions & 0 deletions src/ffi/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,65 @@ pub(crate) fn add_key(
))
}

/// request_key() attempts to find a key of the given type with a description that
/// matches the specified description. If such a key could not be found, then
/// the key is optionally created.
///
/// If the key is found or created, request_key() attaches it to the keyring
/// and returns the key's serial number.
///
/// request_key() first recursively searches for a matching key in all of the keyrings
/// attached to the calling process. The keyrings are searched in the order:
/// thread-specific keyring, process-specific keyring, and then session keyring.
///
/// If request_key() is called from a program invoked by request_key() on behalf
/// of some other process to generate a key, then the keyrings of that other process
/// will be searched next, using that other process's user ID, group ID, supplementary
/// group IDs, and security context to determine access.
///
/// The search of the keyring tree is breadth-first: the keys in each keyring searched
/// are checked for a match before any child keyrings are recursed into. Only keys for
/// which the caller has search permission be found, and only keyrings for which the
/// caller has search permission may be searched.
///
/// If the key is not found and callout info is empty then the call fails with the
/// error ENOKEY.
///
/// If the key is not found and callout info is not empty, then the kernel attempts
/// to invoke a user-space program to instantiate the key.
pub(crate) fn request_key(
ktype: KeyType,
keyring: libc::c_ulong,
description: &str,
info: Option<&str>,
) -> Result<KeySerialId, KeyError> {
// Perform conversion into a c string
let description = CString::new(description).or(Err(KeyError::InvalidDescription))?;
let callout = CString::new(info.unwrap_or("")).or(Err(KeyError::InvalidDescription))?;

// Perform the actual system call. By setting callout to NULL the kernel will
// not invoke /sbin/request-key
let res = unsafe {
libc::syscall(
libc::SYS_request_key,
Into::<&'static CStr>::into(ktype).as_ptr(),
description.as_ptr(),
info.map_or_else(core::ptr::null, |_| callout.as_ptr()),
keyring as u32,
)
};

// Return the underlying error
if res < 0 {
return Err(KeyError::from_errno());
}

// Otherwise return the ID
Ok(KeySerialId::new(
res.try_into().or(Err(KeyError::InvalidIdentifier))?,
))
}

/// keyctl() allows user-space programs to perform key manipulation.
///
/// The operation performed by keyctl() is determined by the value of the operation argument.
Expand Down
2 changes: 1 addition & 1 deletion src/ffi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ macro_rules! keyctl {
pub use types::*;

#[allow(unused_imports)]
pub(crate) use functions::{add_key, keyctl_impl};
pub(crate) use functions::{add_key, keyctl_impl, request_key};

// Export the macro for use
pub(crate) use keyctl;
12 changes: 5 additions & 7 deletions src/ffi/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,11 @@ impl KeySerialId {
/// Using Rust's type system to ensure only valid strings are provided to the syscall.
impl From<KeyType> for &'static CStr {
fn from(t: KeyType) -> &'static CStr {
unsafe {
match t {
KeyType::KeyRing => CStr::from_bytes_with_nul_unchecked(b"keyring\0"),
KeyType::User => CStr::from_bytes_with_nul_unchecked(b"user\0"),
KeyType::Logon => CStr::from_bytes_with_nul_unchecked(b"logon\0"),
KeyType::BigKey => CStr::from_bytes_with_nul_unchecked(b"big_key\0"),
}
match t {
KeyType::KeyRing => c"keyring",
KeyType::User => c"user",
KeyType::Logon => c"logon",
KeyType::BigKey => c"big_key",
}
}
}
Expand Down
Loading
Loading