From ea4ddd9586da50587a258b89350d5490c4f449a5 Mon Sep 17 00:00:00 2001 From: Yuriy Taraday Date: Mon, 1 Sep 2025 07:26:54 +0200 Subject: [PATCH 1/3] Reapply "stdenv: Add CPE fields to meta" This reverts commit de74f9caf0854a9ee8c39774cf4521870fa1f44a. (cherry picked from commit dd12290517ff6e57ab2ca384b101e0cdc617570c) --- doc/redirects.json | 15 ++ doc/stdenv/meta.chapter.md | 71 +++++++ lib/meta.nix | 189 +++++++++++++++++- .../networking/sync/rsync/default.nix | 5 + pkgs/by-name/he/hello/package.nix | 1 + .../development/compilers/gcc/common/meta.nix | 1 + pkgs/development/compilers/gcc/default.nix | 1 + .../compilers/llvm/common/common-let.nix | 2 + .../linux/kernel/manual-config.nix | 7 + pkgs/shells/bash/5.nix | 10 + pkgs/stdenv/generic/check-meta.nix | 66 +++++- 11 files changed, 365 insertions(+), 3 deletions(-) diff --git a/doc/redirects.json b/doc/redirects.json index 187f446bfca1a..5c6e9dcffc9a1 100644 --- a/doc/redirects.json +++ b/doc/redirects.json @@ -192,6 +192,12 @@ "sec-language-cosmic": [ "index.html#sec-language-cosmic" ], + "sec-meta-identifiers": [ + "index.html#sec-meta-identifiers" + ], + "sec-meta-identifiers-cpe": [ + "index.html#sec-meta-identifiers-cpe" + ], "sec-modify-via-packageOverrides": [ "index.html#sec-modify-via-packageOverrides" ], @@ -547,6 +553,15 @@ "typst-package-scope-and-usage": [ "index.html#typst-package-scope-and-usage" ], + "var-meta-identifiers-cpe": [ + "index.html#var-meta-identifiers-cpe" + ], + "var-meta-identifiers-cpeParts": [ + "index.html#var-meta-identifiers-cpeParts" + ], + "var-meta-identifiers-possibleCPEs": [ + "index.html#var-meta-identifiers-possibleCPEs" + ], "var-meta-teams": [ "index.html#var-meta-teams" ], diff --git a/doc/stdenv/meta.chapter.md b/doc/stdenv/meta.chapter.md index 6f092d406126a..947009869ff15 100644 --- a/doc/stdenv/meta.chapter.md +++ b/doc/stdenv/meta.chapter.md @@ -248,3 +248,74 @@ Code to be executed on a peripheral device or embedded controller, built by a th ### `lib.sourceTypes.binaryBytecode` {#lib.sourceTypes.binaryBytecode} Code to run on a VM interpreter or JIT compiled into bytecode by a third party. This includes packages which download Java `.jar` files from another source. + +## Software identifiers {#sec-meta-identifiers} + +Package's `meta.identifiers` attribute specifies information about software identifiers associated with this package. Software identifiers are used, for example: +* to generate Software Bill of Materials (SBOM) that lists all components used to build the software, which can later be used to perform vulnerability or license analysis of the resulting software; +* to lookup software in different vulnerability databases or report new vulnerabilities to them. + +Overriding the default `meta.identifiers` attribute is optional, but it is recommended to fill in pieces to help tools mentioned above get precise data. +For example, we could get automatic notifications about potential vulnerabilities for users in the future. +All identifiers specified in `meta.identifiers` are expected to be unambiguous and valid. + +`meta.identifiers` contains `v1` attribute which is an attribute set that guarantees backward compatibility of its constituents. Right now it contains copies of all other attributes in `meta.identifiers`. + +### CPE {#sec-meta-identifiers-cpe} + +Common Platform Enumeration (CPE) is a specification maintained by NIST as part of the Security Content Automation Protocol (SCAP). It is used to identify software in National Vulnerabilities Database (NVD, https://nvd.nist.gov) and other vulnerability databases. + +Current version of CPE 2.3 consists of 13 parts: + +``` +cpe:2.3:a:::::::::: +``` + +Some of them are as follows: + +* *CPE version* - current version of CPE is `2.3` +* *part* - usually in Nixpkgs `a` for "application", can also be `o` for "operating system" or `h` for "hardware" +* *vendor* - can point to the source of the package, or to Nixpkgs itself +* *product* - name of the package +* *version* - version of the package +* *update* - name of the latest update, can be a patch version for semantically versioned packages +* *edition* - any additional specification about the version + +You can find information about all of these attributes in the [official specification](https://csrc.nist.gov/projects/security-content-automation-protocol/specifications/cpe/naming) (heading 5.3.3, pages 11-13). + +Any fields that don't have a value are set to either `-` if the value is not available or `*` when the field can match any value. + +For example, for glibc 2.40.1 CPE would be `cpe:2.3:a:gnu:glibc:2.40:1:*:*:*:*:*:*`. + +#### `meta.identifiers.cpeParts` {#var-meta-identifiers-cpeParts} + +This attribute contains an attribute set of all parts of the CPE for this package. Most of the parts default to `*` (match any value), with some exceptions: + +* `part` defaults to `a` (application), can also be set to `o` for operating systems, for example, Linux kernel, or to `h` for hardware +* `vendor` cannot be deduced from other sources, so it must be specified by the package author +* `product` defaults to provided derivation's `pname` attribute and must be provided explicitly if `pname` is missing +* `version` and `update` have no defaults and should be specified explicitly or using helper functions, when missing, `cpe` attribute will be empty, and all possible guesses using helper functions will be in `possibleCPEs` attribute. + +It is up to the package author to make sure all parts are correct and match expected values in [NVD dictionary](https://nvd.nist.gov/products/cpe). Unknown values can be skipped, which would leave them with the default value of `*`. + +Following functions help with filling out `version` and `update` fields: + +* [`lib.meta.cpeFullVersionWithVendor`](#function-library-lib.meta.cpeFullVersionWithVendor) +* [`lib.meta.cpePatchVersionInUpdateWithVendor`](#function-library-lib.meta.cpePatchVersionInUpdateWithVendor) + +For many packages to make CPE available it should be enough to specify only: + +```nix +{ + # ... + meta.identifiers.cpeParts = lib.meta.cpePatchVersionInUpdateWithVendor vendor version; +} +``` + +#### `meta.identifiers.cpe` {#var-meta-identifiers-cpe} + +A readonly attribute that concatenates all CPE parts in one string. + +#### `meta.identifiers.possibleCPEs` {#var-meta-identifiers-possibleCPEs} + +A readonly attribute containing the list of guesses for what CPE for this package can look like. It includes all variants of version handling mentioned above. Each item is an attrset with attributes `cpeParts` and `cpe` for each guess. diff --git a/lib/meta.nix b/lib/meta.nix index ee234d94489bb..cf48a700b23a1 100644 --- a/lib/meta.nix +++ b/lib/meta.nix @@ -15,7 +15,12 @@ let assertMsg ; inherit (lib.attrsets) mapAttrs' filterAttrs; - inherit (builtins) isString match typeOf; + inherit (builtins) + isString + match + typeOf + elemAt + ; in rec { @@ -484,4 +489,186 @@ rec { assert assertMsg (match ".*/.*" y == null) "lib.meta.getExe': The second argument \"${y}\" is a nested path with a \"/\" character, but it should just be the name of the executable instead."; "${getBin x}/bin/${y}"; + + /** + Generate [CPE parts](#var-meta-identifiers-cpeParts) from inputs. Copies `vendor` and `version` to the output, and sets `update` to `*`. + + # Inputs + + `vendor` + + : package's vendor + + `version` + + : package's version + + # Type + + ``` + cpeFullVersionWithVendor :: string -> string -> AttrSet + ``` + + # Examples + :::{.example} + ## `lib.meta.cpeFullVersionWithVendor` usage example + + ```nix + lib.meta.cpeFullVersionWithVendor "gnu" "1.2.3" + => { + vendor = "gnu"; + version = "1.2.3"; + update = "*"; + } + ``` + + ::: + :::{.example} + ## `lib.meta.cpeFullVersionWithVendor` usage in derivations + + ```nix + mkDerivation rec { + version = "1.2.3"; + # ... + meta = { + # ... + identifiers.cpeParts = lib.meta.cpeFullVersionWithVendor "gnu" version; + }; + } + ``` + ::: + */ + cpeFullVersionWithVendor = vendor: version: { + inherit vendor version; + update = "*"; + }; + + /** + Alternate version of [`lib.meta.cpePatchVersionInUpdateWithVendor`](#function-library-lib.meta.cpePatchVersionInUpdateWithVendor). + If `cpePatchVersionInUpdateWithVendor` succeeds, returns an attribute set with `success` set to `true` and `value` set to the result. + Otherwise, `success` is set to `false` and `error` is set to the string representation of the error. + + # Inputs + + `vendor` + + : package's vendor + + `version` + + : package's version + + # Type + + ``` + tryCPEPatchVersionInUpdateWithVendor :: string -> string -> AttrSet + ``` + + # Examples + :::{.example} + ## `lib.meta.tryCPEPatchVersionInUpdateWithVendor` usage example + + ```nix + lib.meta.tryCPEPatchVersionInUpdateWithVendor "gnu" "1.2.3" + => { + success = true; + value = { + vendor = "gnu"; + version = "1.2"; + update = "3"; + }; + } + ``` + + ::: + :::{.example} + ## `lib.meta.cpePatchVersionInUpdateWithVendor` error example + + ```nix + lib.meta.tryCPEPatchVersionInUpdateWithVendor "gnu" "5.3p0" + => { + success = false; + error = "version 5.3p0 doesn't match regex `([0-9]+\\.[0-9]+)\\.([0-9]+)`"; + } + ``` + + ::: + */ + tryCPEPatchVersionInUpdateWithVendor = + vendor: version: + let + regex = "([0-9]+\\.[0-9]+)\\.([0-9]+)"; + # we have to call toString here in case version is an attrset with __toString attribute + versionMatch = builtins.match regex (toString version); + in + if versionMatch == null then + { + success = false; + error = "version ${version} doesn't match regex `${regex}`"; + } + else + { + success = true; + value = { + inherit vendor; + version = elemAt versionMatch 0; + update = elemAt versionMatch 1; + }; + }; + + /** + Generate [CPE parts](#var-meta-identifiers-cpeParts) from inputs. Copies `vendor` to the result. When `version` matches `X.Y.Z` where all parts are numerical, sets `version` and `update` fields to `X.Y` and `Z`. Throws an error if the version doesn't match the expected template. + + # Inputs + + `vendor` + + : package's vendor + + `version` + + : package's version + + # Type + + ``` + cpePatchVersionInUpdateWithVendor :: string -> string -> AttrSet + ``` + + # Examples + :::{.example} + ## `lib.meta.cpePatchVersionInUpdateWithVendor` usage example + + ```nix + lib.meta.cpePatchVersionInUpdateWithVendor "gnu" "1.2.3" + => { + vendor = "gnu"; + version = "1.2"; + update = "3"; + } + ``` + + ::: + :::{.example} + ## `lib.meta.cpePatchVersionInUpdateWithVendor` usage in derivations + + ```nix + mkDerivation rec { + version = "1.2.3"; + # ... + meta = { + # ... + identifiers.cpeParts = lib.meta.cpePatchVersionInUpdateWithVendor "gnu" version; + }; + } + ``` + + ::: + */ + cpePatchVersionInUpdateWithVendor = + vendor: version: + let + result = tryCPEPatchVersionInUpdateWithVendor vendor version; + in + if result.success then result.value else throw result.error; } diff --git a/pkgs/applications/networking/sync/rsync/default.nix b/pkgs/applications/networking/sync/rsync/default.nix index 5c4e899d376d2..a9ba4c1a7fbd6 100644 --- a/pkgs/applications/networking/sync/rsync/default.nix +++ b/pkgs/applications/networking/sync/rsync/default.nix @@ -79,5 +79,10 @@ stdenv.mkDerivation rec { ivan ]; platforms = platforms.unix; + identifiers.cpeParts = { + vendor = "samba"; + inherit version; + update = "-"; + }; }; } diff --git a/pkgs/by-name/he/hello/package.nix b/pkgs/by-name/he/hello/package.nix index 0590131913f46..d555fba73da24 100644 --- a/pkgs/by-name/he/hello/package.nix +++ b/pkgs/by-name/he/hello/package.nix @@ -55,5 +55,6 @@ stdenv.mkDerivation (finalAttrs: { maintainers = with lib.maintainers; [ stv0g ]; mainProgram = "hello"; platforms = lib.platforms.all; + identifiers.cpeParts.vendor = "gnu"; }; }) diff --git a/pkgs/development/compilers/gcc/common/meta.nix b/pkgs/development/compilers/gcc/common/meta.nix index 716b6e875f24a..0344ce5c0df5a 100644 --- a/pkgs/development/compilers/gcc/common/meta.nix +++ b/pkgs/development/compilers/gcc/common/meta.nix @@ -25,4 +25,5 @@ in platforms = platforms.unix; teams = [ teams.gcc ]; + identifiers.cpeParts.vendor = "gnu"; } diff --git a/pkgs/development/compilers/gcc/default.nix b/pkgs/development/compilers/gcc/default.nix index 82d676bc8656d..6bf5b4da670b2 100644 --- a/pkgs/development/compilers/gcc/default.nix +++ b/pkgs/development/compilers/gcc/default.nix @@ -450,6 +450,7 @@ pipe longDescription platforms teams + identifiers ; } // optionalAttrs (!atLeast11) { diff --git a/pkgs/development/compilers/llvm/common/common-let.nix b/pkgs/development/compilers/llvm/common/common-let.nix index 1a17e39a06908..800f2000e68cd 100644 --- a/pkgs/development/compilers/llvm/common/common-let.nix +++ b/pkgs/development/compilers/llvm/common/common-let.nix @@ -34,6 +34,8 @@ rec { ++ lib.optionals (lib.versionAtLeast release_version "7") lib.platforms.riscv ++ lib.optionals (lib.versionAtLeast release_version "14") lib.platforms.m68k ++ lib.optionals (lib.versionAtLeast release_version "16") lib.platforms.loongarch64; + + identifiers.cpeParts.vendor = "llvm"; }; releaseInfo = diff --git a/pkgs/os-specific/linux/kernel/manual-config.nix b/pkgs/os-specific/linux/kernel/manual-config.nix index 2f1abf1f99c92..64d923165f545 100644 --- a/pkgs/os-specific/linux/kernel/manual-config.nix +++ b/pkgs/os-specific/linux/kernel/manual-config.nix @@ -529,6 +529,13 @@ lib.makeOverridable ( ] ++ lib.optional (lib.versionOlder version "5.19") "loongarch64-linux"; timeout = 14400; # 4 hours + identifiers.cpeParts = { + part = "o"; + vendor = "linux"; + product = "linux_kernel"; + inherit version; + update = "*"; + }; } // extraMeta; }; diff --git a/pkgs/shells/bash/5.nix b/pkgs/shells/bash/5.nix index 84c6ce7573cf6..3cefac6c62ae7 100644 --- a/pkgs/shells/bash/5.nix +++ b/pkgs/shells/bash/5.nix @@ -184,5 +184,15 @@ lib.warnIf (withDocs != null) badPlatforms = [ lib.systems.inspect.patterns.isMinGW ]; maintainers = [ ]; mainProgram = "bash"; + identifiers.cpeParts = + let + versionSplit = lib.split "p" version; + in + { + vendor = "gnu"; + product = "bash"; + version = lib.elemAt versionSplit 0; + update = lib.elemAt versionSplit 2; + }; }; } diff --git a/pkgs/stdenv/generic/check-meta.nix b/pkgs/stdenv/generic/check-meta.nix index 40d945304d67a..a5875be6ec2b4 100644 --- a/pkgs/stdenv/generic/check-meta.nix +++ b/pkgs/stdenv/generic/check-meta.nix @@ -11,6 +11,7 @@ let inherit (lib) all attrNames + attrValues concatMapStrings concatMapStringsSep concatStrings @@ -38,6 +39,8 @@ let inherit (lib.meta) availableOn + cpeFullVersionWithVendor + tryCPEPatchVersionInUpdateWithVendor ; inherit (lib.generators) @@ -426,6 +429,8 @@ let # Used for the original location of the maintainer and team attributes to assist with pings. maintainersPosition = any; teamsPosition = any; + + identifiers = attrs; }; checkMetaAttr = @@ -559,6 +564,19 @@ let else validYes; + # Helper functions and declarations to handle identifiers, extracted to reduce allocations + hasAllCPEParts = cpeParts: !any isNull (attrValues cpeParts); + makeCPE = + cpeParts: + "cpe:2.3:${cpeParts.part}:${cpeParts.vendor}:${cpeParts.product}:${cpeParts.version}:${cpeParts.update}:${cpeParts.edition}:${cpeParts.sw_edition}:${cpeParts.target_sw}:${cpeParts.target_hw}:${cpeParts.language}:${cpeParts.other}"; + possibleCPEPartsFuns = [ + (vendor: version: { + success = true; + value = cpeFullVersionWithVendor vendor version; + }) + tryCPEPatchVersionInUpdateWithVendor + ]; + # The meta attribute is passed in the resulting attribute set, # but it's not part of the actual derivation, i.e., it's not # passed to the builder and is not a dependency. But since we @@ -622,8 +640,52 @@ let # if you add a new maintainer or team attribute please ensure that this expectation is still met. maintainers = attrs.meta.maintainers or [ ] ++ concatMap (team: team.members or [ ]) attrs.meta.teams or [ ]; - } - // { + + identifiers = + let + defaultCPEParts = { + part = "a"; + vendor = null; + product = attrs.pname or null; + version = null; + update = null; + edition = "*"; + sw_edition = "*"; + target_sw = "*"; + target_hw = "*"; + language = "*"; + other = "*"; + }; + + cpeParts = defaultCPEParts // attrs.meta.identifiers.cpeParts or { }; + cpe = if hasAllCPEParts cpeParts then makeCPE cpeParts else null; + + possibleCPEs = + if cpe != null then + [ { inherit cpeParts cpe; } ] + else if attrs.meta.identifiers.cpeParts.vendor or null == null || attrs.version or null == null then + [ ] + else + concatMap ( + f: + let + result = f attrs.meta.identifiers.cpeParts.vendor attrs.version; + # Note that attrs.meta.identifiers.cpeParts at this point can include defaults with user overrides. + # Since we can't split them apart, user overrides don't apply to possibleCPEs. + guessedParts = cpeParts // result.value; + in + optional (result.success && (hasAllCPEParts guessedParts)) { + cpeParts = guessedParts; + cpe = (makeCPE guessedParts); + } + ) possibleCPEPartsFuns; + v1 = { inherit cpeParts cpe possibleCPEs; }; + in + v1 + // { + inherit v1; + }; + # Expose the result of the checks for everyone to see. unfree = hasUnfreeLicense attrs; broken = isMarkedBroken attrs; From ff409846434699aa27cc385d1b72798e554a3d1e Mon Sep 17 00:00:00 2001 From: Yuriy Taraday Date: Mon, 1 Sep 2025 07:55:53 +0200 Subject: [PATCH 2/3] stdenv: Remove all null values from meta.identifiers nix-env writes a warning for each derivation that has null in its meta values, so fields without known values are removed from the result. Fixes issue raised by @K900 in https://github.com/NixOS/nixpkgs/pull/409797#issuecomment-3238995897 (cherry picked from commit a178fd8c436915d233a1de37a2b211489aad5340) --- pkgs/stdenv/generic/check-meta.nix | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/pkgs/stdenv/generic/check-meta.nix b/pkgs/stdenv/generic/check-meta.nix index a5875be6ec2b4..617686b9af493 100644 --- a/pkgs/stdenv/generic/check-meta.nix +++ b/pkgs/stdenv/generic/check-meta.nix @@ -565,7 +565,12 @@ let validYes; # Helper functions and declarations to handle identifiers, extracted to reduce allocations - hasAllCPEParts = cpeParts: !any isNull (attrValues cpeParts); + hasAllCPEParts = + cpeParts: + let + values = attrValues cpeParts; + in + (length values == 11) && !any isNull values; makeCPE = cpeParts: "cpe:2.3:${cpeParts.part}:${cpeParts.vendor}:${cpeParts.product}:${cpeParts.version}:${cpeParts.update}:${cpeParts.edition}:${cpeParts.sw_edition}:${cpeParts.target_sw}:${cpeParts.target_hw}:${cpeParts.language}:${cpeParts.other}"; @@ -643,12 +648,14 @@ let identifiers = let + # nix-env writes a warning for each derivation that has null in its meta values, so + # fields without known values are removed from the result defaultCPEParts = { part = "a"; - vendor = null; - product = attrs.pname or null; - version = null; - update = null; + #vendor = null; + ${if attrs ? pname then "product" else null} = attrs.pname; + #version = null; + #update = null; edition = "*"; sw_edition = "*"; target_sw = "*"; @@ -679,7 +686,10 @@ let cpe = (makeCPE guessedParts); } ) possibleCPEPartsFuns; - v1 = { inherit cpeParts cpe possibleCPEs; }; + v1 = { + inherit cpeParts possibleCPEs; + ${if cpe != null then "cpe" else null} = cpe; + }; in v1 // { From 4895d6226da2ac357de9888bebad8ec36009a982 Mon Sep 17 00:00:00 2001 From: Yuriy Taraday Date: Fri, 12 Sep 2025 17:43:00 +0200 Subject: [PATCH 3/3] stdenv: Address comments by @ConnorBaker (cherry picked from commit 5e1eee582748866a51ca8f780829c3b2cf523fc3) --- pkgs/stdenv/generic/check-meta.nix | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/pkgs/stdenv/generic/check-meta.nix b/pkgs/stdenv/generic/check-meta.nix index 617686b9af493..588ac5a889722 100644 --- a/pkgs/stdenv/generic/check-meta.nix +++ b/pkgs/stdenv/generic/check-meta.nix @@ -572,8 +572,20 @@ let in (length values == 11) && !any isNull values; makeCPE = - cpeParts: - "cpe:2.3:${cpeParts.part}:${cpeParts.vendor}:${cpeParts.product}:${cpeParts.version}:${cpeParts.update}:${cpeParts.edition}:${cpeParts.sw_edition}:${cpeParts.target_sw}:${cpeParts.target_hw}:${cpeParts.language}:${cpeParts.other}"; + { + part, + vendor, + product, + version, + update, + edition, + sw_edition, + target_sw, + target_hw, + language, + other, + }: + "cpe:2.3:${part}:${vendor}:${product}:${version}:${update}:${edition}:${sw_edition}:${target_sw}:${target_hw}:${language}:${other}"; possibleCPEPartsFuns = [ (vendor: version: { success = true; @@ -681,9 +693,9 @@ let # Since we can't split them apart, user overrides don't apply to possibleCPEs. guessedParts = cpeParts // result.value; in - optional (result.success && (hasAllCPEParts guessedParts)) { + optional (result.success && hasAllCPEParts guessedParts) { cpeParts = guessedParts; - cpe = (makeCPE guessedParts); + cpe = makeCPE guessedParts; } ) possibleCPEPartsFuns; v1 = {