From 54ca558607ba5c7ca1515d3c27779b73bcd75a4d Mon Sep 17 00:00:00 2001 From: Joel Capitao Date: Thu, 30 Jul 2026 11:32:52 +0200 Subject: [PATCH] lsm: treat `unlabeled_t` SELinux context as unlabeled for relabeling Files created in permissive mode can retain the policy initial `unlabeled_t` type. Treating any existing xattr as labeled caused relabeling passes to skip these files and leave policy mismatches in installed systems. Expose a quiet internal predicate so this classification can be verified directly in integration environments without duplicating the SELinux parsing logic. Assisted-by: AI Signed-off-by: Colin Walters --- Cargo.lock | 156 ++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + contrib/packaging/bootc.spec | 3 + crates/cli/src/main.rs | 5 +- crates/lib/Cargo.toml | 1 + crates/lib/src/cli.rs | 116 ++++++++++++++++++++++++-- crates/lib/src/lsm.rs | 88 ++++++++++++++++++-- deny.toml | 2 +- 8 files changed, 352 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6882cad1b7..2e1199709d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -213,6 +213,26 @@ dependencies = [ "vsock", ] +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.11.1", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex 1.3.0", + "syn 2.0.117", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -369,7 +389,7 @@ dependencies = [ "libsystemd", "linkme", "linux-kernel-cmdline", - "nom", + "nom 8.0.0", "ocidir", "openssl", "ostree-ext", @@ -377,6 +397,7 @@ dependencies = [ "regex", "rustix", "schemars", + "selinux", "serde", "serde_ignored", "serde_json", @@ -576,6 +597,15 @@ dependencies = [ "shlex 1.3.0", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom 7.1.3", +] + [[package]] name = "cfg-expr" version = "0.20.7" @@ -623,6 +653,17 @@ dependencies = [ "windows-link", ] +[[package]] +name = "clang-sys" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.6.5" @@ -979,7 +1020,7 @@ dependencies = [ "cap-std-ext", "futures-util", "hex", - "itertools", + "itertools 0.15.0", "oci-spec", "rustix", "semver", @@ -1243,6 +1284,12 @@ dependencies = [ "litrs", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dyn-clone" version = "1.0.20" @@ -1671,6 +1718,12 @@ dependencies = [ "system-deps", ] +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + [[package]] name = "gobject-sys" version = "0.20.10" @@ -1945,6 +1998,15 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.15.0" @@ -2013,6 +2075,16 @@ version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + [[package]] name = "liboverdrop" version = "0.1.0" @@ -2044,7 +2116,7 @@ dependencies = [ "libc", "log", "nix 0.29.0", - "nom", + "nom 8.0.0", "once_cell", "serde", "sha2 0.10.9", @@ -2191,6 +2263,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2239,6 +2317,16 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nom" version = "8.0.0" @@ -2861,6 +2949,12 @@ version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + [[package]] name = "rustc_version" version = "0.4.1" @@ -2905,6 +2999,15 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schemars" version = "1.2.2" @@ -2937,6 +3040,32 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "selinux" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ed8a2f05a488befa851d8de2e3b55bc3889d4fac6758d120bd94098608f63fb" +dependencies = [ + "bitflags 2.11.1", + "libc", + "once_cell", + "parking_lot", + "selinux-sys", + "thiserror 2.0.19", +] + +[[package]] +name = "selinux-sys" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "debaba5832b4831ffe0ba9118b526c752c960f41c46c4ef197d9a15f5179d6fd" +dependencies = [ + "bindgen", + "cc", + "dunce", + "walkdir", +] + [[package]] name = "semver" version = "1.0.28" @@ -3721,6 +3850,16 @@ dependencies = [ "nix 0.31.2", ] +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -3868,6 +4007,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -4170,7 +4318,7 @@ dependencies = [ "chrono", "clap", "fn-error-context", - "itertools", + "itertools 0.15.0", "mandown", "owo-colors", "rand 0.10.2", diff --git a/Cargo.toml b/Cargo.toml index b5a4607940..f90b93cb2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,6 +73,7 @@ regex = "1.10.4" rustix = { "version" = "1", features = ["use-libc", "thread", "net", "fs", "system", "process", "mount"] } serde = "1.0.199" serde_json = "1.0.116" +selinux = "=0.5.0" shlex = "2.0" similar-asserts = "2.0.0" static_assertions = "1.1.0" diff --git a/contrib/packaging/bootc.spec b/contrib/packaging/bootc.spec index 7ce0f7fd2b..9a55055c6e 100644 --- a/contrib/packaging/bootc.spec +++ b/contrib/packaging/bootc.spec @@ -44,6 +44,9 @@ Source1: %{url}/releases/download/v%{version}/bootc-%{version}-vendor.tar ExcludeArch: %{ix86} BuildRequires: libzstd-devel +# selinux-sys generates libselinux bindings with bindgen at build time. +# bindgen requires libclang. +BuildRequires: clang-devel BuildRequires: make BuildRequires: ostree-devel BuildRequires: openssl-devel diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index a8a2edd4d4..5b82151b3c 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -12,7 +12,10 @@ async fn async_main() -> Result<()> { // As you can see, the role of this file is mostly to just be a shim // to call into the code that lives in the internal shared library. - bootc_lib::cli::run_from_iter(std::env::args()).await + match bootc_lib::cli::run_from_iter(std::env::args()).await? { + bootc_lib::cli::CliExitStatus::Success => Ok(()), + bootc_lib::cli::CliExitStatus::PredicateFalse => std::process::exit(1), + } } /// Perform process global initialization, then create an async runtime diff --git a/crates/lib/Cargo.toml b/crates/lib/Cargo.toml index 9f5be3400b..7cf66459cf 100644 --- a/crates/lib/Cargo.toml +++ b/crates/lib/Cargo.toml @@ -51,6 +51,7 @@ regex = { workspace = true } rustix = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } +selinux = { workspace = true } tempfile = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["io-std", "time", "process", "rt", "net"] } diff --git a/crates/lib/src/cli.rs b/crates/lib/src/cli.rs index 86b4eb9998..8be8de9e26 100644 --- a/crates/lib/src/cli.rs +++ b/crates/lib/src/cli.rs @@ -672,6 +672,26 @@ pub(crate) enum UkiSubcommands { }, } +/// Subcommands for `bootc internals selinux`. +#[derive(Debug, clap::Subcommand, PartialEq, Eq)] +pub(crate) enum SelinuxOpts { + /// Exit successfully when PATH is unlabeled; otherwise exit with status 1. + IsUnlabeled { + /// Absolute path to inspect without following a final symbolic link. + #[arg(value_parser = parse_absolute_path)] + path: Utf8PathBuf, + }, +} + +fn parse_absolute_path(value: &str) -> std::result::Result { + let path = Utf8PathBuf::from(value); + if path.is_absolute() { + Ok(path) + } else { + Err("path must be absolute".to_string()) + } +} + /// Hidden, internal only options #[derive(Debug, clap::Subcommand, PartialEq, Eq)] pub(crate) enum InternalsOpts { @@ -693,6 +713,8 @@ pub(crate) enum InternalsOpts { }, #[clap(subcommand)] Fsverity(FsverityOpts), + #[clap(subcommand)] + Selinux(SelinuxOpts), /// Perform consistency checking. Fsck, /// Perform cleanup actions @@ -1819,9 +1841,29 @@ pub fn global_init() -> Result<()> { Ok(()) } +/// The outcome of executing the CLI. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CliExitStatus { + Success, + PredicateFalse, +} + +fn is_unlabeled_exit_status( + state: crate::lsm::SELinuxLabelState, + path: &Utf8Path, +) -> Result { + match state { + crate::lsm::SELinuxLabelState::Unlabeled => Ok(CliExitStatus::Success), + crate::lsm::SELinuxLabelState::Labeled => Ok(CliExitStatus::PredicateFalse), + crate::lsm::SELinuxLabelState::Unsupported => { + anyhow::bail!("SELinux labeling is unsupported for {path}") + } + } +} + /// Parse the provided arguments and execute. /// Calls [`clap::Error::exit`] on failure, printing the error message and aborting the program. -pub async fn run_from_iter(args: I) -> Result<()> +pub async fn run_from_iter(args: I) -> Result where I: IntoIterator, I::Item: Into + Clone, @@ -1876,9 +1918,9 @@ impl Opt { } /// Internal (non-generic/monomorphized) primary CLI entrypoint -async fn run_from_opt(opt: Opt) -> Result<()> { +async fn run_from_opt(opt: Opt) -> Result { let root = &Dir::open_ambient_dir("/", cap_std::ambient_authority())?; - match opt { + let result = match opt { Opt::Upgrade(opts) => { let storage = &get_storage().await?; match storage.kind()? { @@ -1927,7 +1969,8 @@ async fn run_from_opt(opt: Opt) -> Result<()> { no_truncate, } => { if list { - return lints::lint_list(std::io::stdout().lock()); + return lints::lint_list(std::io::stdout().lock()) + .map(|()| CliExitStatus::Success); } let warnings = if fatal_warnings { lints::WarningDisposition::FatalWarnings @@ -2274,6 +2317,21 @@ async fn run_from_opt(opt: Opt) -> Result<()> { Ok(()) } }, + InternalsOpts::Selinux(SelinuxOpts::IsUnlabeled { path }) => { + ensure!(crate::lsm::selinux_enabled()?, "SELinux is not enabled"); + let path = path + .strip_prefix("/") + .expect("absolute paths have a root prefix"); + match is_unlabeled_exit_status( + crate::lsm::has_security_selinux(&root, path)?, + path, + )? { + CliExitStatus::Success => Ok(()), + CliExitStatus::PredicateFalse => { + return Ok(CliExitStatus::PredicateFalse); + } + } + } InternalsOpts::Cfs { args } => composefs_ctl::run_from_iter(args.iter()).await, InternalsOpts::Reboot => crate::reboot::reboot(), InternalsOpts::Fsck => { @@ -2386,7 +2444,7 @@ async fn run_from_opt(opt: Opt) -> Result<()> { BootedStorageKind::Composefs(booted_cfs) => { if reset { - return reset_soft_reboot(); + return reset_soft_reboot().map(|()| CliExitStatus::Success); } prepare_soft_reboot_composefs( @@ -2551,7 +2609,8 @@ async fn run_from_opt(opt: Opt) -> Result<()> { } } } - } + }; + result.map(|()| CliExitStatus::Success) } #[cfg(test)] @@ -2650,6 +2709,51 @@ mod tests { )); } + #[test] + fn test_parse_selinux_is_unlabeled() { + let opt = Opt::try_parse_from([ + "bootc", + "internals", + "selinux", + "is-unlabeled", + "/var/lib/probe", + ]) + .unwrap(); + assert!(matches!( + opt, + Opt::Internals(InternalsOpts::Selinux(SelinuxOpts::IsUnlabeled { path })) + if path == "/var/lib/probe" + )); + assert!( + Opt::try_parse_from(["bootc", "internals", "selinux", "is-unlabeled", "relative",]) + .is_err() + ); + } + + #[test] + fn test_is_unlabeled_exit_status() { + let path = Utf8Path::new("probe"); + let cases = [ + ( + crate::lsm::SELinuxLabelState::Unlabeled, + CliExitStatus::Success, + ), + ( + crate::lsm::SELinuxLabelState::Labeled, + CliExitStatus::PredicateFalse, + ), + ]; + for (state, expected) in cases { + assert_eq!(is_unlabeled_exit_status(state, path).unwrap(), expected); + } + assert!( + is_unlabeled_exit_status(crate::lsm::SELinuxLabelState::Unsupported, path) + .unwrap_err() + .to_string() + .contains("unsupported") + ); + } + #[test] fn test_parse_generator() { assert!(matches!( diff --git a/crates/lib/src/lsm.rs b/crates/lib/src/lsm.rs index 2c059406df..4d629b2601 100644 --- a/crates/lib/src/lsm.rs +++ b/crates/lib/src/lsm.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::ffi::CString; use std::io::Write; use std::os::fd::AsRawFd; use std::os::unix::process::CommandExt; @@ -23,8 +24,17 @@ use rustix::fd::AsFd; const SELINUXFS: &str = "/sys/fs/selinux"; /// The SELinux xattr const SELINUX_XATTR: &[u8] = b"security.selinux\0"; +/// The kernel initial SID used for objects without a label. +const SELINUX_INITIAL_SID_UNLABELED: &str = "unlabeled"; const SELF_CURRENT: &str = "/proc/self/attr/current"; +fn unlabeled_type() -> Result { + let context = + selinux::SecurityContext::of_initial_kernel_context(SELINUX_INITIAL_SID_UNLABELED, true) + .context("Querying the SELinux unlabeled initial context")?; + context_type(context.as_bytes()) +} + #[context("Querying selinux availability")] pub(crate) fn selinux_enabled() -> Result { Path::new("/proc/1/root/sys/fs/selinux/enforce") @@ -250,19 +260,55 @@ pub(crate) fn set_security_selinux(fd: std::os::fd::BorrowedFd, label: &[u8]) -> /// The labeling state; "unsupported" is distinct as we need to handle /// cases like the ESP which don't support labeling. +#[derive(Debug, PartialEq, Eq)] pub(crate) enum SELinuxLabelState { Unlabeled, Unsupported, Labeled, } +fn context_type(context: &[u8]) -> Result { + // security.selinux xattrs may include a trailing NUL terminator. + let context = context.strip_suffix(b"\0").unwrap_or(context); + let context = CString::new(context).context("SELinux context contains an interior NUL")?; + context + .to_str() + .context("SELinux context is not valid UTF-8")?; + selinux::OpaqueSecurityContext::from_c_str(&context) + .context("Parsing SELinux context")? + .the_type() + .context("Reading SELinux context type") +} + /// Query the SELinux labeling for a particular path pub(crate) fn has_security_selinux(root: &Dir, path: &Utf8Path) -> Result { + let unlabeled_type = unlabeled_type()?; + has_security_selinux_inner(root, path, Some(&unlabeled_type)) +} + +fn has_security_selinux_inner( + root: &Dir, + path: &Utf8Path, + unlabeled_type: Option<&CString>, +) -> Result { // TODO: avoid hardcoding a max size here let mut buf = [0u8; 2048]; let fdpath = format!("/proc/self/fd/{}/{path}", root.as_raw_fd()); match rustix::fs::lgetxattr(fdpath, "security.selinux", &mut buf) { - Ok(_) => Ok(SELinuxLabelState::Labeled), + Ok(len) => { + // Installation can relabel a target while its host has SELinux disabled. + // In that case, retain the conservative xattr-presence behavior. + let Some(unlabeled_type) = unlabeled_type else { + return Ok(SELinuxLabelState::Labeled); + }; + let label_type = context_type(&buf[..len]) + .with_context(|| format!("Parsing SELinux context for {path:?}"))?; + if label_type.as_c_str() == unlabeled_type.as_c_str() { + Ok(SELinuxLabelState::Unlabeled) + } else { + Ok(SELinuxLabelState::Labeled) + } + } Err(rustix::io::Errno::OPNOTSUPP) => Ok(SELinuxLabelState::Unsupported), Err(rustix::io::Errno::NODATA) => Ok(SELinuxLabelState::Unlabeled), Err(e) => Err(e).with_context(|| format!("Failed to look up context for {path:?}")), @@ -285,16 +331,14 @@ pub(crate) fn set_security_selinux_path(root: &Dir, path: &Utf8Path, label: &[u8 Ok(()) } -/// Given a policy, ensure the target file path has a security.selinux label. -/// If the path already is labeled, this function is a no-op, even if -/// the policy would default to a different label. -pub(crate) fn ensure_labeled( +fn ensure_labeled_inner( root: &Dir, path: &Utf8Path, metadata: &Metadata, policy: &ostree::SePolicy, + unlabeled_type: Option<&CString>, ) -> Result { - let r = has_security_selinux(root, path)?; + let r = has_security_selinux_inner(root, path, unlabeled_type)?; if matches!(r, SELinuxLabelState::Unlabeled) { relabel(root, metadata, path, None, policy)?; } @@ -402,6 +446,7 @@ pub(crate) fn ensure_dir_labeled_recurse( use cap_std_ext::dirext::WalkConfiguration; use std::ops::ControlFlow; + let unlabeled_type = selinux_enabled()?.then(unlabeled_type).transpose()?; // Juggle the cap-std requirement for relative paths vs the libselinux // requirement for absolute paths by special casing the empty string "" as "." // just for the initial directory enumeration. @@ -415,7 +460,7 @@ pub(crate) fn ensure_dir_labeled_recurse( // Label the starting directory itself; the walk API only visits children. let metadata = root.symlink_metadata(path_for_read)?; - match ensure_labeled(root, path, &metadata, policy)? { + match ensure_labeled_inner(root, path, &metadata, policy, unlabeled_type.as_ref())? { SELinuxLabelState::Unlabeled => { n += 1; } @@ -450,7 +495,7 @@ pub(crate) fn ensure_dir_labeled_recurse( let path = Utf8Path::from_path(component.path) .ok_or_else(|| anyhow::anyhow!("Invalid non-UTF-8 path: {:?}", component.path))?; - match ensure_labeled(root, path, &metadata, policy)? { + match ensure_labeled_inner(root, path, &metadata, policy, unlabeled_type.as_ref())? { SELinuxLabelState::Unlabeled => { n += 1; } @@ -573,4 +618,31 @@ mod tests { let found: &[(&[u8], &[u8])] = &[(b"foo", b"bar"), (SELINUX_XATTR, b"foo_t")]; assert!(xattrs_have_selinux(&Variant::from(found))); } + + #[test] + fn test_context_type() { + let cases: &[(&[u8], Option<&str>)] = &[ + (b"system_u:object_r:unlabeled_t:s0", Some("unlabeled_t")), + (b"system_u:object_r:unlabeled_t:s0\0", Some("unlabeled_t")), + ( + b"system_u:object_r:unlabeled_t:s0:c0.c1023", + Some("unlabeled_t"), + ), + (b"system_u:object_r:var_t:s0", Some("var_t")), + (b"system_u:object_r", None), + (b"system_u:object_r:var_t:s0\0\0", None), + (b"system_u:object_r:var_t\0:s0", None), + (b"system_u:object_r:unlabeled_t:\xff", None), + ]; + + for (context, expected_type) in cases { + assert_eq!( + context_type(context) + .ok() + .and_then(|context_type| context_type.into_string().ok()) + .as_deref(), + *expected_type + ); + } + } } diff --git a/deny.toml b/deny.toml index 65b2a57dd4..e132f8e293 100644 --- a/deny.toml +++ b/deny.toml @@ -2,7 +2,7 @@ allow = ["Apache-2.0", "Apache-2.0 WITH LLVM-exception", "MIT", "BSD-3-Clause", "BSD-2-Clause", "Zlib", "Unlicense", "CC0-1.0", "BSL-1.0", - "Unicode-DFS-2016", "Unicode-3.0"] + "Unicode-DFS-2016", "Unicode-3.0", "ISC"] private = { ignore = true } [[bans.deny]]