From ad1572c40ba04ba01d1f75a76c97e4bc5f6fcab6 Mon Sep 17 00:00:00 2001 From: "Nicolas B. Pierron" Date: Thu, 24 Mar 2016 21:20:07 +0000 Subject: [PATCH 01/12] Extract all imports out of pkgsWithOvverrides. This change add a `packages` argument, such that we can reuse the same function with a different set of packages. Moving `defaultPackages` as argument would be needed for writting tests to ensure that security fixes are applied correctly. --- pkgs/top-level/default.nix | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/pkgs/top-level/default.nix b/pkgs/top-level/default.nix index 4de75c2ed57e4..7df3282ce07bf 100644 --- a/pkgs/top-level/default.nix +++ b/pkgs/top-level/default.nix @@ -30,6 +30,16 @@ , crossSystem ? null , platform ? null + + # List of paths that are used to build the base channel, which is used as + # a reference for security updates. +, defaultPackages ? { + adapters = import ../stdenv/adapters.nix; + builders = import ../build-support/trivial-builders.nix; + stdenv = import ./stdenv.nix; + all = import ./all-packages.nix; + aliases = import ./aliases.nix; + } }: @@ -95,31 +105,34 @@ let # (un-overridden) set of packages, allowing packageOverrides # attributes to refer to the original attributes (e.g. "foo = # ... pkgs.foo ..."). - pkgs = pkgsWithOverrides (self: config.packageOverrides or (super: {})); + pkgs = pkgsWithOverridesWithPackages (self: config.packageOverrides or (super: {})) defaultPackages; # Return the complete set of packages, after applying the overrides # returned by the `overrider' function (see above). Warning: this # function is very expensive! - pkgsWithOverrides = overrider: + pkgsWithOverridesWithPackages = overrider: packages: let stdenvAdapters = self: super: - let res = import ../stdenv/adapters.nix self; in res // { + let res = packages.adapters self; in res // { stdenvAdapters = res; }; trivialBuilders = self: super: - (import ../build-support/trivial-builders.nix { + (packages.builders { inherit lib; inherit (self) stdenv; inherit (self.xorg) lndir; }); - stdenvDefault = self: super: (import ./stdenv.nix topLevelArguments) {} pkgs; + stdenvDefault = self: super: (packages.stdenv topLevelArguments) {} pkgs; - allPackagesArgs = topLevelArguments // { inherit pkgsWithOverrides; }; + allPackagesArgs = topLevelArguments // { + pkgsWithOverrides = overrider: + pkgsWithOverridesWithPackages overrider defaultPackages; + }; allPackages = self: super: - let res = import ./all-packages.nix allPackagesArgs res self; + let res = packages.all allPackagesArgs res self; in res; - aliases = self: super: import ./aliases.nix super; + aliases = self: super: packages.aliases super; # stdenvOverrides is used to avoid circular dependencies for building # the standard build environment. This mechanism uses the override From c2134e1cf448acb71d99cbef5de0772502dd0350 Mon Sep 17 00:00:00 2001 From: "Nicolas B. Pierron" Date: Thu, 24 Mar 2016 21:40:23 +0000 Subject: [PATCH 02/12] Add pkgsWithPackages function, and split pkgs expression. --- pkgs/top-level/default.nix | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkgs/top-level/default.nix b/pkgs/top-level/default.nix index 7df3282ce07bf..46075fbda42a5 100644 --- a/pkgs/top-level/default.nix +++ b/pkgs/top-level/default.nix @@ -105,7 +105,10 @@ let # (un-overridden) set of packages, allowing packageOverrides # attributes to refer to the original attributes (e.g. "foo = # ... pkgs.foo ..."). - pkgs = pkgsWithOverridesWithPackages (self: config.packageOverrides or (super: {})) defaultPackages; + pkgs = pkgsWithPackages defaultPackages; + + pkgsWithPackages = + pkgsWithOverridesWithPackages (self: config.packageOverrides or (super: {})); # Return the complete set of packages, after applying the overrides # returned by the `overrider' function (see above). Warning: this From e0efe6c178a88799e3869ba7f1cd04177b633776 Mon Sep 17 00:00:00 2001 From: "Nicolas B. Pierron" Date: Thu, 24 Mar 2016 21:43:25 +0000 Subject: [PATCH 03/12] Move the fix-point from pkgsWithOverridesWithPackages to pkgs, and add the unfix prefix to functions. --- pkgs/top-level/default.nix | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/pkgs/top-level/default.nix b/pkgs/top-level/default.nix index 46075fbda42a5..4896065bbe1ed 100644 --- a/pkgs/top-level/default.nix +++ b/pkgs/top-level/default.nix @@ -105,15 +105,15 @@ let # (un-overridden) set of packages, allowing packageOverrides # attributes to refer to the original attributes (e.g. "foo = # ... pkgs.foo ..."). - pkgs = pkgsWithPackages defaultPackages; + pkgs = lib.fix' (unfixPkgsWithPackages defaultPackages); - pkgsWithPackages = - pkgsWithOverridesWithPackages (self: config.packageOverrides or (super: {})); + unfixPkgsWithPackages = + unfixPkgsWithOverridesWithPackages (self: config.packageOverrides or (super: {})); # Return the complete set of packages, after applying the overrides # returned by the `overrider' function (see above). Warning: this # function is very expensive! - pkgsWithOverridesWithPackages = overrider: packages: + unfixPkgsWithOverridesWithPackages = overrider: packages: let stdenvAdapters = self: super: let res = packages.adapters self; in res // { @@ -129,7 +129,7 @@ let allPackagesArgs = topLevelArguments // { pkgsWithOverrides = overrider: - pkgsWithOverridesWithPackages overrider defaultPackages; + lib.fix' (unfixPkgsWithOverridesWithPackages overrider defaultPackages); }; allPackages = self: super: let res = packages.all allPackagesArgs res self; @@ -151,14 +151,13 @@ let customOverrides = self: super: lib.optionalAttrs (bootStdenv == null) (overrider self super); in - lib.fix' ( - lib.extends customOverrides ( - lib.extends stdenvOverrides ( - lib.extends aliases ( - lib.extends allPackages ( - lib.extends stdenvDefault ( - lib.extends trivialBuilders ( - lib.extends stdenvAdapters ( - self: {})))))))); + lib.extends customOverrides ( + lib.extends stdenvOverrides ( + lib.extends aliases ( + lib.extends allPackages ( + lib.extends stdenvDefault ( + lib.extends trivialBuilders ( + lib.extends stdenvAdapters ( + self: {}))))))); in pkgs From 187776fc1fde9de34c73fea475140d6928c5e9d3 Mon Sep 17 00:00:00 2001 From: "Nicolas B. Pierron" Date: Thu, 24 Mar 2016 22:02:11 +0000 Subject: [PATCH 04/12] Add maybeApplyAbiCompatiblePatches, as well as the quickfixPackages argument. --- pkgs/top-level/default.nix | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/pkgs/top-level/default.nix b/pkgs/top-level/default.nix index 4896065bbe1ed..ecb33ca60b489 100644 --- a/pkgs/top-level/default.nix +++ b/pkgs/top-level/default.nix @@ -40,6 +40,11 @@ all = import ./all-packages.nix; aliases = import ./aliases.nix; } + + # Additional list of packages, similar to defaultPackages, which is used + # to apply security fixes to a few packages which would be compiled, and + # to patch any of their dependencies, to have a fast turn-around. +, quickfixPackages ? null }: @@ -159,5 +164,21 @@ let lib.extends trivialBuilders ( lib.extends stdenvAdapters ( self: {}))))))); + + # Apply ABI compatible fixes: + # 1. This will cause the recompilation of packages which have a different + # derivation in quickfix, than what they have in the default packages. + # 2. This will patch any of the dependencies to substitute the hash of + # the default packages by the corresponding hash of the quickfix + # packages which got recompiled. + # + # If there is no quickfix to apply, or if we are bootstrapping the + # compilation environment, then there is no need for making any patches. + maybeApplyAbiCompatiblePatches = pkgs: + if bootStdenv == null && quickfixPackages != null then + throw "NYI" + else + pkgs; + in - pkgs + maybeApplyAbiCompatiblePatches pkgs From c40639d65072ffaafb4ec952ba380f46ad3531ec Mon Sep 17 00:00:00 2001 From: "Nicolas B. Pierron" Date: Fri, 25 Mar 2016 22:36:19 +0000 Subject: [PATCH 05/12] Add applyAbiCompatiblePatches function. This function is in charge of compiling security updates. It does so by taking advantage for the purity of the Nix language. It does not change any of the hashes, even when an identical set of packages is given as argument for the quickfixPackages. The purity of the Nix language offer us a clean way to reason about Nixpkgs as if this was a simple function. Thus, the following property hold true, even when applied to a collection of packages: let f = nixpkgsFun; in fix f == f (f (fix f)); This property is used for building security updates, by using a variant of the first function: let f = nixpkgsFun; g = fixpkgsFun; in g (fix f) Using a fixpkgsFun function which is a function similar to nixpkgsFun, we can generate a similar set of packages while only recompiling the fixed packages. The problem then becomes patching any package which are dependening on the fixed packages. This is what the applyAbiCompatiblePatches functions do. +-------+ | | +---v----+ | +--------+ +--------+ | | | | | | | | pkgs +-----> onefix +-+ +-+ recfix | | | | | | | | | +--------+ +--------+ | | +-----^--+ | | | +--v--v--+ | | | | | abifix +----+ | | +--------+ The loop back on pkgs is the original fix-point on the default set of packages. Then we recompile the patched programs, in the one-fix function call. The rec-fix is a way to probe for recompilations, by comparing the dependencies of one-fix packages, taken from pkgs, with the dependencies of rec-fix packages, taken from the abi-fix packages. The abi-fix packages is a zipping function which takes one-fix packages and rec-fix packages for patching one-fix packages. In practice, it also takes the original set of packages, to ensure that packages have the same package name length, and that other which depend on them can be patched safely. --- pkgs/top-level/default.nix | 168 ++++++++++++++++++++++++++++++++++++- 1 file changed, 167 insertions(+), 1 deletion(-) diff --git a/pkgs/top-level/default.nix b/pkgs/top-level/default.nix index ecb33ca60b489..26438438a03b5 100644 --- a/pkgs/top-level/default.nix +++ b/pkgs/top-level/default.nix @@ -176,9 +176,175 @@ let # compilation environment, then there is no need for making any patches. maybeApplyAbiCompatiblePatches = pkgs: if bootStdenv == null && quickfixPackages != null then - throw "NYI" + applyAbiCompatiblePatches pkgs else pkgs; + applyAbiCompatiblePatches = pkgs: with lib; + let + # - pkgs: set of packages compiled by default with no quickfix + # applied. + # + # - onefix: set of packages compiled against `pkgs`, without doing a + # fix point. This is used to recompiled packages which have + # security fixes, without recompiling any of the packages + # which are depending on them. + # + # - recfix: set of packages compiled against the set of fixed + # packages (`abifix`). This is used as a probe to see if + # any of the dependencies got fixed or patched. + # + # - abifix: set of fixed packaged, which are both fixed and patched. + # + onefix = unfixPkgsWithPackages quickfixPackages pkgs; + recfix = unfixPkgsWithPackages quickfixPackages abifix; + abifix = zipWithUpdatedPackages ["pkgs"] pkgs onefix recfix; + + # Traverse all packages. For each package, take the quickfix version + # of the package, and patch it if any of its dependency is different + # than the one used for building it in `pkgs`. + zipWithUpdatedPackages = path: pkgs: onefix: recfix: + zipAttrsWith (name: values: + let pkgsName = concatStringsSep "." (path ++ [name]); in + # Somebody added / removed a package in quickfix? + assert builtins.length values == 3; + let p = elemAt values 0; o = elemAt values 1; r = elemAt values 2; in + if name == "pkgs" && path == ["pkgs"] then abifix + else if isAttrs p then assert isAttrs o && isAttrs r; + if isDerivation p then assert isDerivation o && isDerivation r; + addErrorContext "While evaluating package ${pkgsName}" + (patchUpdatedDependencies pkgsName p o r) + else + zipWithUpdatedPackages (path ++ [name]) p o r + else + o + ) [pkgs onefix recfix]; + + # For each package: + # + # 1. Take the onefix version of the package. + # + # 2. Rename it, such that we can safely patch any of the packages + # which depend on this one. + # + # 3. Check if the arguments of the nix expression imported by + # `callPackage` are different. if none, return the renamed + # package. + # + # 4. Otherwise, replace hashes of the `onefix` package, by the hashes + # of the `recfix` package. + # + patchUpdatedDependencies = name: pkg: onefix: recfix: + let + # Get build inputs added by the mkDerivation function. + getUnfilteredDepenencies = drv: + drv.nativeBuildInputs or []; + + # Warn if the package does not provide any list of build inputs. + warnIfUnableToFindDeps = drv: + if drv ? nativeBuildInputs then true + else assert __trace "Security issue: Unable to locate buildInputs of `${name}`." true; true; + + # Note, we need to check the drv.outPath to add some strictness + # such that we eliminate derivation which might assert when they + # are evaluated. + validDeps = drv: + let res = builtins.tryEval (isDerivation drv && isString drv.outPath); in + res.success && res.value; + + # Get the list of dependencies added listed in build inputs, + # but filter out any input which cannot be properly evaluated + # to a derivation. The reason being that some arguments are + # ordinary values, and some arguments are packages specific to one + # architecture. + getDeps = drv: + filter validDeps (getUnfilteredDepenencies drv); + + assertSameName = {old, new}@result: + assert (builtins.parseDrvName old.name).name == (builtins.parseDrvName new.name).name; + result; + + differentDeps = {old, new}: + old != new; + + # Zip build inputs from the old package with the build inputs of + # the new package definition. + zipBuildInputs = + zipListsWith (old: new: assertSameName { inherit old new; }); + + # Extract the list of dependency given as build inputs, and filter + # out identical packages. + buildInputsDiff = {old, new}: + let oldDeps = getDeps old; newDeps = getDeps new; in + # assert __trace "${name}.${toString old}: oldDeps: ${toString (map (drv: (builtins.parseDrvName drv).name) oldDeps)}" true; + # assert __trace "${name}.${toString new}: newDeps: ${toString (map (drv: (builtins.parseDrvName drv).name) newDeps)}" true; + assert (length oldDeps) == (length newDeps); + filter differentDeps (zipBuildInputs oldDeps newDeps); + + # Derivation might be different because of the dependency of the + # fixed derivation is different. We have to recursively append all + # the differencies. + recursiveBuildInputsDiff = {old, new}@args: + let depDiffs = buildInputsDiff args; in + depDiffs ++ concatMap recursiveBuildInputsDiff depDiffs; + + dependencyDifferencies = + flip map (recursiveBuildInputsDiff { old = onefix; new = recfix; }) ({old, new}: { + old = builtins.unsafeDiscardStringContext (toString old); + new = toString new; + }); + + # If the name of the onefix does not have the same + # length, use the old name instead. This might cause a + # problem if people do not use --leq while updating. + onefixRenamed = + if stringLength pkg.name == stringLength onefix.name + then onefix + else + overrideDerivation onefix (drv: { + name = pkg.name; + }); + + # Copy the function and meta information of the recfix stage to + # the final package, such that one can extend and mutate the + # package as if this abi compatible patches mechanism did not + # exists. + forwardDrvAttributes = drv: {} + // optionalAttrs (drv ? override) { inherit (drv) override; } + // optionalAttrs (drv ? overrideDerivation) { inherit (drv) overrideDerivation; } + // { + inherit (drv) builder args stdenv system userHook + __ignoreNulls buildInputs propagatedBuildInputs + nativeBuildInputs propagatedNativeBuildInputs; + }; + in + if length dependencyDifferencies != 0 then + assert warnIfUnableToFindDeps onefix; + + patchDependencies onefixRenamed dependencyDifferencies + // (forwardDrvAttributes recfix) + else + onefixRenamed; + + # Create a derivation which is replace all the hashes of `pkgs`, by + # the fixed and patched versions of the `abifix` packages. + patchDependencies = drv: replaceList: + # The list is not bounded, thus to avoid having huge command lines, + # we create a file with all the renamed hashes. + let sedExpr = {old, new}: "s|${baseNameOf old}|${baseNameOf new}|g;\n"; in + let sedScript = pkgs.writeTextFile { + name = drv.name + "-patch"; + text = concatStrings (map sedExpr replaceList); + }; + in + pkgs.runCommand "${drv.name}" { nixStore = "${pkgs.nix}/bin/nix-store"; } '' + $nixStore --dump ${drv} | \ + sed -e 's|${baseNameOf drv}|'$(basename $out)'|g' -f ${sedScript} | \ + $nixStore --restore $out + ''; + + in + abifix; + in maybeApplyAbiCompatiblePatches pkgs From 9f6ccacee00ac52aeb74995c4dbb62c2516431eb Mon Sep 17 00:00:00 2001 From: "Nicolas B. Pierron" Date: Sat, 26 Mar 2016 17:30:41 +0000 Subject: [PATCH 06/12] Add a flag for buildfarms, to turn off the abiCompatiblePatches while running the quick-fixes. --- pkgs/top-level/default.nix | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pkgs/top-level/default.nix b/pkgs/top-level/default.nix index 26438438a03b5..e3a3b872a38d0 100644 --- a/pkgs/top-level/default.nix +++ b/pkgs/top-level/default.nix @@ -45,6 +45,16 @@ # to apply security fixes to a few packages which would be compiled, and # to patch any of their dependencies, to have a fast turn-around. , quickfixPackages ? null + + # When a set of quickfix packages is defined, this flag is used to enable + # patching packages which are depending on the packages which are updated + # in the quickfix packages. + # + # This flag highly recommend to be `true` on a user/server system, while + # it is suggested to set it to `false` on buildfarms, as we do not want to + # distribute, and use space for all the variants of the patched packages, + # on the buildfarm servers. +, doPatchWithDependencies ? true }: @@ -344,7 +354,8 @@ let ''; in - abifix; + if doPatchWithDependencies then abifix + else onefix; in maybeApplyAbiCompatiblePatches pkgs From 0b4d9b13126cdbe127206514b7d9fcd6c6060d3c Mon Sep 17 00:00:00 2001 From: "Nicolas B. Pierron" Date: Sat, 26 Mar 2016 17:31:01 +0000 Subject: [PATCH 07/12] Add a static analysis to report security issues. This static analysis unroll the fix-point of Nixpkgs and number each iteration of the fix-point. This numbering is then used to verify the assumption made by the applyAbiCompatiblePatches function of top=level/default.nix. This static analysis reports 2 kinds of errors, and 1 warning. Errors: - Alias Original: A package defined in Nixpkgs is directly defined as a value taken from the fix-point. This can happen for overriden packages. This is a security issue because fixes can be ignored. - Unpatched Inputs: An input which provided in buildInputs or nativeBuildInputs is taken from more than one generation of Nixpkgs. This can be caused by the Alias Original issue, or can be caused by the function which fills the input used in buildInputs. This is a security issue, because fixes of the dependency will not cause this package to be recompiled. Warning: - Static Linking: A package dependent directly on a version of a package which has the same generation as it-self. This is not a security issue, but if this is not expected then this would cause extra recompilation on the buildfarm and add delays until the fixed versions are available. --- pkgs/test/security/static-analaysis.nix | 200 ++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 pkgs/test/security/static-analaysis.nix diff --git a/pkgs/test/security/static-analaysis.nix b/pkgs/test/security/static-analaysis.nix new file mode 100644 index 0000000000000..4462c6e91cd4d --- /dev/null +++ b/pkgs/test/security/static-analaysis.nix @@ -0,0 +1,200 @@ +# ABI compatible patches are made based on one assumption, which is that +# Nixpkgs is a function which provide a set of derivation, where all the +# dependencies are taken from its argument. This hypothesis has to hold, +# in order to make it possible to override packages. +# +# Unfortunately, they are many ways in which a dependency can be defined, +# and it could be defined after multiple iterations through the Nixpkgs +# function, or at the same iteration. +# +# This file is made to analyze and report issues in the resolutions of +# dependencies such that we can generate safe security updates. + +let + # Load Nixpkgs without any additional wrapping. + pkgs = import ../../../. { + quickfixPackages = null; + doPatchWithDependencies = false; + }; + pkgsFun = pkgs.__unfix__; + + lib = pkgs.lib; + + # These attributes are unrolling the fix-point of Nixpkgs over a few + # numbers of iteration, and all derivations of each iteration are annotated + # with their generation number. + # + # Such annotations are useful to analyze the data-flow of the derivations, + # and to find issues which are not safe for applying ABI compatible + # patches. + rangeMax = 10; + genPkgs = with lib; + let generations = range 0 rangeMax; in + fold (gen: pkgs: annotatePkgs gen (pkgsFun pkgs)) pkgs generations; + + # Annotate all derivations with an extra attribute, named `_generation`. + # This extra attribute identify all packages coming from this layer of + # Nixpkgs. This is used to track attributes which are not handled by the + # applyAbiCompatiblePatches function. + annotatePkgs = generation: pkgs: with lib; + let + recursiveAnnotateValue = attrs: + mapAttrsRecursiveCond + (as: !isDerivation as) (path: maybeAnnotateValue path) + attrs; + + # We don't want to patch callPackages, but the override function + # returned by it. + validFunctionName = path: + let funName = elemAt path (length path - 1); in + ! elem funName [ "newScope" "callPackage" "callPackages" "callPackage_i686" "mkDerivation" ]; + + annotateValue = path: value: + if isDerivation value then + # assert __trace (strict ["annotatePkgs::" generation] ++ path) true; + (mapAttrs (name: maybeAnnotateValue (path ++ [".drv" name])) value) + // { + _generation = + if value ? _generation then value._generation + else + # assert __trace (strict ["annotatePkgs>>" generation] ++ path) true; + generation; + } + else if isFunction value && validFunctionName path then + x: maybeAnnotateValue (path ++ [">"]) (value x) + else + value; + + maybeAnnotateValue = path: value: + let res = builtins.tryEval (annotateValue path value); in + if res.success then res.value + else value; + in + recursiveAnnotateValue pkgs; + + # This is a clone of the recursive update function, which is made lazier, + # by not evaluating the left-hand-side. Thus, avoid being strict on + # derivations. + recursiveUpdate = lhs: rhs: with lib; + recursiveUpdateUntil (path: lhs: rhs: !isAttrs rhs) lhs rhs; + + # Do not waste time looking for derivations which might cause us trouble, + # either by causing infinite loops or simply by taking too much time to + # visit. + filteredPkgs = recursiveUpdate genPkgs { + + # Prevent infinite recursions within the following attributes: + allStdenvs = null; + pkgs = null; + pkgsi686Linux = null; + gnome3.gnome3 = null; + lispPackages.pkgs = null; + + # Prevent errors not caught by builtins.tryEval. + darwin.CF_new = null; + darwin.Libc_new = null; + darwin.Libnotify_new = null; + darwin.Libsystem_new = null; + darwin.cctools_cross = null; + darwin.libdispatch_new = null; + darwin.libiconv_new = null; + darwin.objc4_new = null; + darwin.xnu_new = null; + + linuxPackages_3_10_tuxonice = null; + nodePackages.by-spec.pure-css = null; + + # Call me lazy... + recurseForDerivations = true; + }; + + # This is the same as the `collectWithPath` function of Nixpkgs's library, + # except that it uses `tryEval` to ignore invalid evaluations, such as + # broken and unfree packages. + # + # Example: + # maybeCollectWithPath (x: x ? outPath) + # { a = { outPath = "a/"; }; b = { outPath = "b/"; }; } + # => [ { path = ["a"]; value = { outPath = "a/"; }; } + # { path = ["b"]; value = { outPath = "b/"; }; } + # ] + # + maybeCollectWithPath = pred: attrs: with lib; + let + collectInternal = path: attrs: + # assert __trace (["maybeCollectWithPath::"] ++ path) true; + addErrorContext "while collecting derivations under ${concatStringsSep "." path}:" ( + if pred attrs then + [ { path = concatStringsSep "." path; value = attrs; } ] + else if isAttrs attrs && attrs.recurseForDerivations or false then + concatMap (name: maybeCollectInternal (path ++ [name]) attrs.${name}) + (attrNames attrs) + else + []); + + maybeCollectInternal = path: attrs: + # Some evaluation of isAttrs might raise an assertion while + # evaluating Nixpkgs, tryEval is used to work-around this issue. + let res = builtins.tryEval (collectInternal path attrs); in + if res.success then res.value + else []; + + in + maybeCollectInternal [] attrs; + + # Collect all derivations. + collectDerivations = with lib; pkgs: + maybeCollectWithPath (drv: isDerivation drv && drv.outPath != "") pkgs; + + # Look at the collected derivations, and annotate each derivation with + # some errors/warnings that should be considered. Filter out any + # derivation which does not represent a risk for applying security + # updates. + analyzePackages = with lib; + let + getGeneration = dft: drv: drv._generation or dft; + inputsByGeneration = dft: gen: drv: + filter (d: isDerivation d && getGeneration dft d == gen) (drv.nativeBuildInputs or []); + inputsOlderThanGeneration = dft: gen: drv: + filter (d: isDerivation d && getGeneration dft d > gen) (drv.nativeBuildInputs or []); + + analyze = {path, value}@elem: + rec { + generation = getGeneration rangeMax value; + inputs-by-generations = { + gen0 = inputsByGeneration generation 0 value; + gen1 = inputsByGeneration generation 1 value; + old = inputsOlderThanGeneration generation 1 value; + }; + messages = [] + ++ optional (generation != 0) "alias-original: generation ${toString generation}" + ++ optional (inputs-by-generations.old != []) ''unpatched-inputs: generation ${toString generation}, inputs: ${ + concatStringsSep ", " (map (d: "(${toString (getGeneration generation d)}, ${d.outPath})") inputs-by-generations.old) + }'' + ++ optional (inputs-by-generations.gen0 != []) ''static-linking: generation ${toString generation}, inputs: ${ + concatStringsSep ", " (map (d: "(${toString (getGeneration generation d)}, ${d.outPath})") inputs-by-generations.gen0) + }''; + }; + + addMessages = e: e // analyze e; + in + filter (e: e.messages != []) ( + map addMessages (collectDerivations filteredPkgs)); + + # Debug function which prints the list of known issues which can be + # checked statically. + displayAnalysis = issues: with lib; + let + displayMessages = e: + map (m: "${e.path}: ${m}") e.messages; + allMessages = + concatMap displayMessages issues; + in + assert __trace (''List of ${toString (length allMessages)} potential issues: + + '' + concatStringsSep "\n" allMessages + ) true; + null; + +in + displayAnalysis analyzePackages From 2cc9534f0ec877b2742f460549d94c1476d5f523 Mon Sep 17 00:00:00 2001 From: "Nicolas B. Pierron" Date: Sat, 26 Mar 2016 19:17:14 +0000 Subject: [PATCH 08/12] Add a test case to verify that security fixes are correctly applied. The test case create a serie of 5 test packages which are forming a chain. It verifies that: - Hashes are identical if the packages have no need to be recompiled nor patched. - Packages with fixes are recompiled. - Packages with updated dependencies are patched, including the recompiled one. - Hashes are correct and consistent in the final version. - Version numbers are not updated. (abi compatible hypothesis) - Patches carry indirect dependencies modifications. - 2 fixed dependencies can be updates at once. --- pkgs/test/security/check-quickfix.nix | 201 ++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 pkgs/test/security/check-quickfix.nix diff --git a/pkgs/test/security/check-quickfix.nix b/pkgs/test/security/check-quickfix.nix new file mode 100644 index 0000000000000..6d4a3699a6f70 --- /dev/null +++ b/pkgs/test/security/check-quickfix.nix @@ -0,0 +1,201 @@ +let + testDrv = stdenv: name: rec { + inherit name; + buildInputs = [ ]; + buildCommand = '' + mkdir -p $out + touch $out/installed + echo ${name} >> $out/installed + touch $out/dependency + echo $out >> $out/dependency + ''; + + meta = with stdenv.lib; { + homepage = https://nixos.org/; + description = "Test case"; + longDescription = "Test case"; + license = licenses.mit; + maintainers = [ maintainers.pierron ]; + platforms = platforms.all; + }; + }; + + testDrvWithDep = stdenv: name: dep: + let drv = testDrv stdenv name; in drv // { + buildInputs = [ dep ]; + buildCommand = drv.buildCommand + '' + cat ${dep}/installed >> $out/installed + cat ${dep}/dependency >> $out/dependency + ''; + }; + + testPkg = stdenv: name: + stdenv.mkDerivation (testDrv stdenv name); + + testPkgWithDep = stdenv: name: dep: + stdenv.mkDerivation (testDrvWithDep stdenv name dep); + + pkgTestDead = { stdenv, name ? "dead-1.0.0" }: + testPkg stdenv name; + pkgTestBeef = { stdenv, name ? "beef-1.0.0", test-dead }: + testPkgWithDep stdenv name test-dead; + pkgTestAte = { stdenv, name ? "ate-1.0.0", test-beef }: + testPkgWithDep stdenv name test-beef; + pkgTestBad = { stdenv, name ? "bad-1.0.0", test-ate }: + testPkgWithDep stdenv name test-ate; + pkgTestFood = { stdenv, name ? "food-1.0.0", test-bad }: + testPkgWithDep stdenv name test-bad; + + originalPackages = { + adapters = import ../../../pkgs/stdenv/adapters.nix; + builders = import ../../../pkgs/build-support/trivial-builders.nix; + stdenv = import ../../../pkgs/top-level/stdenv.nix; + all = import ../../../pkgs/top-level/all-packages.nix; + aliases = import ../../../pkgs/top-level/aliases.nix; + }; + + defaultPackages = originalPackages // { + all = top: self: pkgs: originalPackages.all top self pkgs // ( + let callPackage = top.lib.callPackageWith pkgs; in { + test-dead = callPackage pkgTestDead { }; + test-beef = callPackage pkgTestBeef { }; + test-ate = callPackage pkgTestAte { }; + test-bad = callPackage pkgTestBad { }; + test-food = callPackage pkgTestFood { }; + }); + }; + + # Only change the foo package which is used explicitly by bar, and + # indirectly by baz. + quickfixPackages = originalPackages // { + all = top: self: pkgs: originalPackages.all top self pkgs // ( + let callPackage = top.lib.callPackageWith pkgs; in { + test-dead = callPackage pkgTestDead { }; + test-beef = callPackage pkgTestBeef { name = "beef-1.0.2"; }; + test-ate = callPackage pkgTestAte { }; + test-bad = callPackage pkgTestBad { name = "bad-1.0.51"; }; + test-food = callPackage pkgTestFood { }; + }); + }; + + withoutFix = import ../../../. { + inherit defaultPackages; + quickfixPackages = null; + }; + + withFix = import ../../../. { + inherit defaultPackages quickfixPackages; + }; +in + + withoutFix.stdenv.mkDerivation { + name = "check-quickfix"; + buildInputs = []; + buildCommand = '' + length() { + local arg="$1"; + echo ''${#arg}; + } + set -x; + + : Check that fixes are correctly applies. + test ${withoutFix.test-dead} = ${withFix.test-dead} + test \! ${withoutFix.test-beef} = ${withFix.test-beef} # recompiled + test \! ${withoutFix.test-ate } = ${withFix.test-ate } # patched + test \! ${withoutFix.test-bad } = ${withFix.test-bad } # recompiled & patched + test \! ${withoutFix.test-food} = ${withFix.test-food} # patched + + : Check output paths have identical length. + test $(length ${withoutFix.test-dead}) -eq $(length ${withFix.test-dead}) + test $(length ${withoutFix.test-beef}) -eq $(length ${withFix.test-beef}) + test $(length ${withoutFix.test-ate }) -eq $(length ${withFix.test-ate }) + test $(length ${withoutFix.test-bad }) -eq $(length ${withFix.test-bad }) # renamed + test $(length ${withoutFix.test-food}) -eq $(length ${withFix.test-food}) + + : Check compiled packages names. + grep -q "dead-1.0.0" ${withoutFix.test-dead}/installed + grep -q "beef-1.0.0" ${withoutFix.test-beef}/installed + grep -q "ate-1.0.0" ${withoutFix.test-ate }/installed + grep -q "bad-1.0.0" ${withoutFix.test-bad }/installed + grep -q "food-1.0.0" ${withoutFix.test-food}/installed + + grep -q "dead-1.0.0" ${withoutFix.test-beef}/installed + grep -q "beef-1.0.0" ${withoutFix.test-ate }/installed + grep -q "ate-1.0.0" ${withoutFix.test-bad }/installed + grep -q "bad-1.0.0" ${withoutFix.test-food}/installed + + grep -q "dead-1.0.0" ${withoutFix.test-ate }/installed + grep -q "beef-1.0.0" ${withoutFix.test-bad }/installed + grep -q "ate-1.0.0" ${withoutFix.test-food}/installed + + grep -q "dead-1.0.0" ${withoutFix.test-bad }/installed + grep -q "beef-1.0.0" ${withoutFix.test-food}/installed + + grep -q "dead-1.0.0" ${withoutFix.test-food}/installed + + grep -q "dead-1.0.0" ${withFix.test-dead}/installed + grep -q "beef-1.0.2" ${withFix.test-beef}/installed + grep -q "ate-1.0.0" ${withFix.test-ate }/installed + grep -q "bad-1.0.51" ${withFix.test-bad }/installed # not renamed + grep -q "food-1.0.0" ${withFix.test-food}/installed + + grep -q "dead-1.0.0" ${withFix.test-beef}/installed + grep -q "beef-1.0.0" ${withFix.test-ate }/installed # not updated + grep -q "ate-1.0.0" ${withFix.test-bad }/installed + grep -q "bad-1.0.0" ${withFix.test-food}/installed # not updated + + grep -q "dead-1.0.0" ${withFix.test-ate }/installed + grep -q "beef-1.0.0" ${withFix.test-bad }/installed # not updated + grep -q "ate-1.0.0" ${withFix.test-food}/installed + + grep -q "dead-1.0.0" ${withFix.test-bad }/installed + grep -q "beef-1.0.0" ${withFix.test-food}/installed # not updated + + grep -q "dead-1.0.0" ${withFix.test-food}/installed + + : Check dependencies hashes. + grep -q ${withoutFix.test-dead} ${withoutFix.test-dead}/dependency + grep -q ${withoutFix.test-beef} ${withoutFix.test-beef}/dependency + grep -q ${withoutFix.test-ate } ${withoutFix.test-ate }/dependency + grep -q ${withoutFix.test-bad } ${withoutFix.test-bad }/dependency + grep -q ${withoutFix.test-food} ${withoutFix.test-food}/dependency + + grep -q ${withoutFix.test-dead} ${withoutFix.test-beef}/dependency + grep -q ${withoutFix.test-beef} ${withoutFix.test-ate }/dependency + grep -q ${withoutFix.test-ate } ${withoutFix.test-bad }/dependency + grep -q ${withoutFix.test-bad } ${withoutFix.test-food}/dependency + + grep -q ${withoutFix.test-dead} ${withoutFix.test-ate }/dependency + grep -q ${withoutFix.test-beef} ${withoutFix.test-bad }/dependency + grep -q ${withoutFix.test-ate } ${withoutFix.test-food}/dependency + + grep -q ${withoutFix.test-dead} ${withoutFix.test-bad }/dependency + grep -q ${withoutFix.test-beef} ${withoutFix.test-food}/dependency + + grep -q ${withoutFix.test-dead} ${withoutFix.test-food}/dependency + + grep -q ${withFix.test-dead} ${withFix.test-dead}/dependency + grep -q ${withFix.test-beef} ${withFix.test-beef}/dependency # recompiled + grep -q ${withFix.test-ate } ${withFix.test-ate }/dependency + grep -q ${withFix.test-bad } ${withFix.test-bad }/dependency # recompiled + grep -q ${withFix.test-food} ${withFix.test-food}/dependency + + grep -q ${withFix.test-dead} ${withFix.test-beef}/dependency + grep -q ${withFix.test-beef} ${withFix.test-ate }/dependency # patched + grep -q ${withFix.test-ate } ${withFix.test-bad }/dependency + grep -q ${withFix.test-bad } ${withFix.test-food}/dependency # patched + + grep -q ${withFix.test-dead} ${withFix.test-ate }/dependency + grep -q ${withFix.test-beef} ${withFix.test-bad }/dependency # patched + grep -q ${withFix.test-ate } ${withFix.test-food}/dependency + + grep -q ${withFix.test-dead} ${withFix.test-bad }/dependency + grep -q ${withFix.test-beef} ${withFix.test-food}/dependency # patched + + grep -q ${withFix.test-dead} ${withFix.test-food}/dependency + + mkdir -p $out + echo success > $out/result + set +x + ''; + } From ca70916a9fa2ec8a3a47c3ef93904cb36d1179ed Mon Sep 17 00:00:00 2001 From: "Nicolas B. Pierron" Date: Mon, 28 Mar 2016 12:57:39 +0000 Subject: [PATCH 09/12] Add test cases to ensure that the onefix, and abifix are keeping the same hashes if there is no fixes. --- pkgs/test/security/abifix-noop.nix | 9 +++ pkgs/test/security/lib.nix | 82 +++++++++++++++++++++++++ pkgs/test/security/onefix-noop.nix | 6 ++ pkgs/test/security/static-analaysis.nix | 46 +------------- 4 files changed, 99 insertions(+), 44 deletions(-) create mode 100644 pkgs/test/security/abifix-noop.nix create mode 100644 pkgs/test/security/lib.nix create mode 100644 pkgs/test/security/onefix-noop.nix diff --git a/pkgs/test/security/abifix-noop.nix b/pkgs/test/security/abifix-noop.nix new file mode 100644 index 0000000000000..c8fef3331f7c0 --- /dev/null +++ b/pkgs/test/security/abifix-noop.nix @@ -0,0 +1,9 @@ +# Check that evaluating all the packages, with the abi patching mechanism +# with no fixes is effectively a no-op. + +with import ./lib.nix; + +assert builtins.trace "Found ${builtins.toString (builtins.length pkgsDrvs)} packages." true; +assert builtins.length pkgsDrvs == builtins.length abifixDrvs; +assert lib.all lib.id (zipPkgs pkgsDrvs abifixDrvs); +true diff --git a/pkgs/test/security/lib.nix b/pkgs/test/security/lib.nix new file mode 100644 index 0000000000000..fe105ed6c2251 --- /dev/null +++ b/pkgs/test/security/lib.nix @@ -0,0 +1,82 @@ +let + lib = import ../../../lib; +in + +with lib; + +rec { + inherit lib; + + originalPackages = { + adapters = import ../../../pkgs/stdenv/adapters.nix; + builders = import ../../../pkgs/build-support/trivial-builders.nix; + stdenv = import ../../../pkgs/top-level/stdenv.nix; + all = import ../../../pkgs/top-level/all-packages.nix; + aliases = import ../../../pkgs/top-level/aliases.nix; + }; + + pkgs = import ../../../. { + defaultPackages = originalPackages; + quickfixPackages = null; + doPatchWithDependencies = false; + } // { recurseForDerivations = true; }; + + onefix = import ../../../. { + defaultPackages = originalPackages; + quickfixPackages = originalPackages; + doPatchWithDependencies = false; + } // { recurseForDerivations = true; }; + + abifix = import ../../../. { + defaultPackages = originalPackages; + quickfixPackages = originalPackages; + doPatchWithDependencies = true; + } // { recurseForDerivations = true; }; + + # This is the same as the `collectWithPath` function of Nixpkgs's library, + # except that it uses `tryEval` to ignore invalid evaluations, such as + # broken and unfree packages. + # + # Example: + # maybeCollectWithPath (x: x ? outPath) + # { a = { outPath = "a/"; }; b = { outPath = "b/"; }; } + # => [ { path = ["a"]; value = { outPath = "a/"; }; } + # { path = ["b"]; value = { outPath = "b/"; }; } + # ] + # + maybeCollectWithPath = pred: attrs: with lib; + let + collectInternal = path: attrs: + # assert __trace (["maybeCollectWithPath::"] ++ path) true; + addErrorContext "while collecting derivations under ${concatStringsSep "." path}:" ( + if pred attrs then + [ { path = concatStringsSep "." path; value = attrs; } ] + else if isAttrs attrs && attrs.recurseForDerivations or false then + concatMap (name: maybeCollectInternal (path ++ [name]) attrs.${name}) + (attrNames attrs) + else + []); + + maybeCollectInternal = path: attrs: + # Some evaluation of isAttrs might raise an assertion while + # evaluating Nixpkgs, tryEval is used to work-around this issue. + let res = builtins.tryEval (collectInternal path attrs); in + if res.success then res.value + else []; + + in + maybeCollectInternal [] attrs; + + # Collect all derivations. + collectDerivations = with lib; pkgs: + maybeCollectWithPath (drv: isDerivation drv && drv.outPath != "") pkgs; + + pkgsDrvs = collectDerivations pkgs; + onefixDrvs = collectDerivations onefix; + abifixDrvs = collectDerivations abifix; + + # Zip all packages collected so far, and verify that they are equal. + zipPkgs = + lib.zipListsWith + (p: o: p.path == o.path && p.value.outPath == o.value.outPath); +} diff --git a/pkgs/test/security/onefix-noop.nix b/pkgs/test/security/onefix-noop.nix new file mode 100644 index 0000000000000..a8c40d540f824 --- /dev/null +++ b/pkgs/test/security/onefix-noop.nix @@ -0,0 +1,6 @@ +with import ./lib.nix; + +assert builtins.trace "Found ${builtins.toString (builtins.length pkgsDrvs)} packages." true; +assert builtins.length pkgsDrvs == builtins.length onefixDrvs; +assert lib.all lib.id (zipPkgs pkgsDrvs onefixDrvs); +true diff --git a/pkgs/test/security/static-analaysis.nix b/pkgs/test/security/static-analaysis.nix index 4462c6e91cd4d..14a7d3958466a 100644 --- a/pkgs/test/security/static-analaysis.nix +++ b/pkgs/test/security/static-analaysis.nix @@ -10,16 +10,12 @@ # This file is made to analyze and report issues in the resolutions of # dependencies such that we can generate safe security updates. +with import ./lib.nix; + let # Load Nixpkgs without any additional wrapping. - pkgs = import ../../../. { - quickfixPackages = null; - doPatchWithDependencies = false; - }; pkgsFun = pkgs.__unfix__; - lib = pkgs.lib; - # These attributes are unrolling the fix-point of Nixpkgs over a few # numbers of iteration, and all derivations of each iteration are annotated # with their generation number. @@ -108,44 +104,6 @@ let recurseForDerivations = true; }; - # This is the same as the `collectWithPath` function of Nixpkgs's library, - # except that it uses `tryEval` to ignore invalid evaluations, such as - # broken and unfree packages. - # - # Example: - # maybeCollectWithPath (x: x ? outPath) - # { a = { outPath = "a/"; }; b = { outPath = "b/"; }; } - # => [ { path = ["a"]; value = { outPath = "a/"; }; } - # { path = ["b"]; value = { outPath = "b/"; }; } - # ] - # - maybeCollectWithPath = pred: attrs: with lib; - let - collectInternal = path: attrs: - # assert __trace (["maybeCollectWithPath::"] ++ path) true; - addErrorContext "while collecting derivations under ${concatStringsSep "." path}:" ( - if pred attrs then - [ { path = concatStringsSep "." path; value = attrs; } ] - else if isAttrs attrs && attrs.recurseForDerivations or false then - concatMap (name: maybeCollectInternal (path ++ [name]) attrs.${name}) - (attrNames attrs) - else - []); - - maybeCollectInternal = path: attrs: - # Some evaluation of isAttrs might raise an assertion while - # evaluating Nixpkgs, tryEval is used to work-around this issue. - let res = builtins.tryEval (collectInternal path attrs); in - if res.success then res.value - else []; - - in - maybeCollectInternal [] attrs; - - # Collect all derivations. - collectDerivations = with lib; pkgs: - maybeCollectWithPath (drv: isDerivation drv && drv.outPath != "") pkgs; - # Look at the collected derivations, and annotate each derivation with # some errors/warnings that should be considered. Filter out any # derivation which does not represent a risk for applying security From 8729a7007617f9d0ede152857797197f6741d795 Mon Sep 17 00:00:00 2001 From: "Nicolas B. Pierron" Date: Mon, 28 Mar 2016 13:00:21 +0000 Subject: [PATCH 10/12] Add security test cases as release blockers. --- pkgs/test/security/default.nix | 36 ++++++++++++++++++++++++++++++++++ pkgs/top-level/release.nix | 4 ++++ 2 files changed, 40 insertions(+) create mode 100644 pkgs/test/security/default.nix diff --git a/pkgs/test/security/default.nix b/pkgs/test/security/default.nix new file mode 100644 index 0000000000000..78b621dcad144 --- /dev/null +++ b/pkgs/test/security/default.nix @@ -0,0 +1,36 @@ +{ nixpkgs, pkgs }: + +with pkgs; + +let + nixCommand = name: command: + runCommand name { buildInputs = [ nix ]; } '' + datadir="${nix}/share" + export TEST_ROOT=$(pwd)/test-tmp + export NIX_BUILD_HOOK= + export NIX_CONF_DIR=$TEST_ROOT/etc + export NIX_DB_DIR=$TEST_ROOT/db + export NIX_LOCALSTATE_DIR=$TEST_ROOT/var + export NIX_LOG_DIR=$TEST_ROOT/var/log/nix + export NIX_MANIFESTS_DIR=$TEST_ROOT/var/nix/manifests + export NIX_STATE_DIR=$TEST_ROOT/var/nix + export NIX_STORE_DIR=$TEST_ROOT/store + export PAGER=cat + cacheDir=$TEST_ROOT/binary-cache + nix-store --init + + cd ${nixpkgs}/pkgs/test/security + ${command} + touch $out + ''; +in + +{ + check-quickfix = import ./check-quickfix.nix; + onefix-noop = nixCommand "onefix-noop" '' + nix-instantiate --timeout 60 ./onefix-noop.nix --eval-only + ''; + abifix-noop = nixCommand "abifix-noop" '' + nix-instantiate --timeout 60 ./abifix-noop.nix --eval-only + ''; +} diff --git a/pkgs/top-level/release.nix b/pkgs/top-level/release.nix index b46b589562620..33f5f47628900 100644 --- a/pkgs/top-level/release.nix +++ b/pkgs/top-level/release.nix @@ -29,6 +29,7 @@ let manual = import ../../doc; lib-tests = import ../../lib/tests/release.nix { inherit nixpkgs; }; + tests.security = import ../../pkgs/test/security { inherit nixpkgs pkgs; }; unstable = pkgs.releaseTools.aggregate { name = "nixpkgs-${jobs.tarball.version}"; @@ -38,6 +39,9 @@ let jobs.metrics jobs.manual jobs.lib-tests + jobs.tests.security.check-quickfix + jobs.tests.security.onefix-noop + jobs.tests.security.abifix-noop jobs.stdenv.x86_64-linux jobs.stdenv.i686-linux jobs.stdenv.x86_64-darwin From 9b88a3b0a1f9f935ff844b7098e33cd6ee796a8c Mon Sep 17 00:00:00 2001 From: "Nicolas B. Pierron" Date: Tue, 29 Mar 2016 21:04:31 +0000 Subject: [PATCH 11/12] Make patchUpdatedDependencies lazier, to accept loops with override function calls. --- pkgs/top-level/default.nix | 44 ++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/pkgs/top-level/default.nix b/pkgs/top-level/default.nix index e3a3b872a38d0..f9c81dfc11e25 100644 --- a/pkgs/top-level/default.nix +++ b/pkgs/top-level/default.nix @@ -295,8 +295,11 @@ let # fixed derivation is different. We have to recursively append all # the differencies. recursiveBuildInputsDiff = {old, new}@args: - let depDiffs = buildInputsDiff args; in - depDiffs ++ concatMap recursiveBuildInputsDiff depDiffs; + if new ? buildInputsDifferences + then new.buildInputsDifferences + else + let depDiffs = buildInputsDiff args; in + depDiffs ++ concatMap recursiveBuildInputsDiff depDiffs; dependencyDifferencies = flip map (recursiveBuildInputsDiff { old = onefix; new = recfix; }) ({old, new}: { @@ -315,26 +318,25 @@ let name = pkg.name; }); - # Copy the function and meta information of the recfix stage to - # the final package, such that one can extend and mutate the - # package as if this abi compatible patches mechanism did not - # exists. - forwardDrvAttributes = drv: {} - // optionalAttrs (drv ? override) { inherit (drv) override; } - // optionalAttrs (drv ? overrideDerivation) { inherit (drv) overrideDerivation; } - // { - inherit (drv) builder args stdenv system userHook - __ignoreNulls buildInputs propagatedBuildInputs - nativeBuildInputs propagatedNativeBuildInputs; - }; + # If any of the dependencies is different, then patch the package, + # and return the patched version of the package, otherwise return + # the renamed package. + patchedDrv = + if length dependencyDifferencies != 0 then + assert warnIfUnableToFindDeps onefix; + patchDependencies onefixRenamed dependencyDifferencies + else + onefixRenamed; in - if length dependencyDifferencies != 0 then - assert warnIfUnableToFindDeps onefix; - - patchDependencies onefixRenamed dependencyDifferencies - // (forwardDrvAttributes recfix) - else - onefixRenamed; + # As the merge operator is not lazy enough, we have to create a + # new attribute set which inherit all the names from the original + # package, while the resolution of the name is made through the + # recfix version of the package. This way, only the outPath and + # outDrv are resolved through the patched version of the package. + mapAttrs (n: v: recfix."${n}") pkg // { + inherit (patchedDrv) outPath drvPath; + buildInputsDifferences = dependencyDifferencies; + }; # Create a derivation which is replace all the hashes of `pkgs`, by # the fixed and patched versions of the `abifix` packages. From 847405c6abaee65718877647de36dd93b2de510c Mon Sep 17 00:00:00 2001 From: "Nicolas B. Pierron" Date: Sat, 2 Apr 2016 13:46:17 +0000 Subject: [PATCH 12/12] Security Static Analysis: forceNativeDrv and lowPrio are not changing the evaluation depth of the derivations. --- pkgs/test/security/static-analaysis.nix | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkgs/test/security/static-analaysis.nix b/pkgs/test/security/static-analaysis.nix index 14a7d3958466a..03d2e90b6511a 100644 --- a/pkgs/test/security/static-analaysis.nix +++ b/pkgs/test/security/static-analaysis.nix @@ -43,7 +43,9 @@ let # returned by it. validFunctionName = path: let funName = elemAt path (length path - 1); in - ! elem funName [ "newScope" "callPackage" "callPackages" "callPackage_i686" "mkDerivation" ]; + ! elem funName [ "newScope" "callPackage" "callPackages" "callPackage_i686" "mkDerivation" + "forceNativeDrv" "lowPrio" + ]; annotateValue = path: value: if isDerivation value then