summary refs log tree commit diff
diff options
context:
space:
mode:
authorSilvan Mosberger <infinisil@icloud.com>2019-01-28 10:38:00 +0100
committerGitHub <noreply@github.com>2019-01-28 10:38:00 +0100
commit51d2eed83b4787e167e59871380433c02c033c42 (patch)
tree2905fac4fc39c732be9369f075db977cff493e12
parent8c2aaf37fde021717d7ca7b2ec651a628f9ae38b (diff)
parent045e1332d97d0db7c09265d2c2041fbca906286c (diff)
downloadnixpkgs-51d2eed83b4787e167e59871380433c02c033c42.tar
nixpkgs-51d2eed83b4787e167e59871380433c02c033c42.tar.gz
nixpkgs-51d2eed83b4787e167e59871380433c02c033c42.tar.bz2
nixpkgs-51d2eed83b4787e167e59871380433c02c033c42.tar.lz
nixpkgs-51d2eed83b4787e167e59871380433c02c033c42.tar.xz
nixpkgs-51d2eed83b4787e167e59871380433c02c033c42.tar.zst
nixpkgs-51d2eed83b4787e167e59871380433c02c033c42.zip
Merge pull request #42838 from teto/kernel_autoconf
[RFC] add ability to merge structured configs
-rw-r--r--lib/kernel.nix62
-rw-r--r--nixos/modules/system/boot/kernel_config.nix137
-rw-r--r--pkgs/os-specific/linux/kernel/common-config.nix64
-rw-r--r--pkgs/os-specific/linux/kernel/generic.nix47
-rw-r--r--pkgs/os-specific/linux/kernel/hardened-config.nix212
-rw-r--r--pkgs/test/default.nix2
-rw-r--r--pkgs/test/kernel.nix53
-rw-r--r--pkgs/top-level/all-packages.nix2
8 files changed, 363 insertions, 216 deletions
diff --git a/lib/kernel.nix b/lib/kernel.nix
index 45b33aea7b8..5923011774b 100644
--- a/lib/kernel.nix
+++ b/lib/kernel.nix
@@ -1,57 +1,21 @@
-{ lib
-# we pass the kernel version here to keep a nice syntax `whenOlder "4.13"`
-# kernelVersion, e.g., config.boot.kernelPackages.version
-, version
-, mkValuePreprocess ? null
-}:
+{ lib, version }:
 
 with lib;
 rec {
-  # Common patterns
-  when        = cond: opt: if cond then opt else null;
-  whenAtLeast = ver: when (versionAtLeast version ver);
-  whenOlder   = ver: when (versionOlder version ver);
-  whenBetween = verLow: verHigh: when (versionAtLeast version verLow && versionOlder version verHigh);
+  # Common patterns/legacy
+  whenAtLeast = ver: mkIf (versionAtLeast version ver);
+  whenOlder   = ver: mkIf (versionOlder version ver);
+  # range is (inclusive, exclusive)
+  whenBetween = verLow: verHigh: mkIf (versionAtLeast version verLow && versionOlder version verHigh);
 
-  # Keeping these around in case we decide to change this horrible implementation :)
-  option = x: if x == null then null else "?${x}";
-  yes    = "y";
-  no     = "n";
-  module = "m";
 
-  mkValue = val:
-  let
-    isNumber = c: elem c ["0" "1" "2" "3" "4" "5" "6" "7" "8" "9"];
-  in
-    if val == "" then "\"\""
-    else if val == yes || val == module || val == no then val
-    else if all isNumber (stringToCharacters val) then val
-    else if substring 0 2 val == "0x" then val
-    else val; # FIXME: fix quoting one day
+  # Keeping these around in case we decide to change this horrible implementation :)
+  option = x:
+      x // { optional = true; };
 
+  yes      = { tristate    = "y"; };
+  no       = { tristate    = "n"; };
+  module   = { tristate    = "m"; };
+  freeform = x: { freeform = x; };
 
-  # generate nix intermediate kernel config file of the form
-  #
-  #       VIRTIO_MMIO m
-  #       VIRTIO_BLK y
-  #       VIRTIO_CONSOLE n
-  #       NET_9P_VIRTIO? y
-  #
-  # Use mkValuePreprocess to preprocess option values, aka mark 'modules' as
-  # 'yes' or vice-versa
-  # Borrowed from copumpkin https://github.com/NixOS/nixpkgs/pull/12158
-  # returns a string, expr should be an attribute set
-  generateNixKConf = exprs: mkValuePreprocess:
-  let
-    mkConfigLine = key: rawval:
-    let
-      val = if builtins.isFunction mkValuePreprocess then mkValuePreprocess rawval else rawval;
-    in
-      if val == null
-        then ""
-        else if hasPrefix "?" val
-          then "${key}? ${mkValue (removePrefix "?" val)}\n"
-          else "${key} ${mkValue val}\n";
-    mkConf = cfg: concatStrings (mapAttrsToList mkConfigLine cfg);
-  in mkConf exprs;
 }
diff --git a/nixos/modules/system/boot/kernel_config.nix b/nixos/modules/system/boot/kernel_config.nix
new file mode 100644
index 00000000000..fbbd0982b2c
--- /dev/null
+++ b/nixos/modules/system/boot/kernel_config.nix
@@ -0,0 +1,137 @@
+{ lib, config, ... }:
+
+with lib;
+let
+  findWinner = candidates: winner:
+    any (x: x == winner) candidates;
+
+  # winners is an ordered list where first item wins over 2nd etc
+  mergeAnswer = winners: locs: defs:
+    let
+      values = map (x: x.value) defs;
+      freeformAnswer = intersectLists values winners;
+      inter = intersectLists values winners;
+      winner = head winners;
+    in
+    if defs == [] then abort "This case should never happen."
+    else if winner == [] then abort "Give a valid list of winner"
+    else if inter == [] then mergeOneOption locs defs
+    else if findWinner values winner then
+      winner
+    else
+      mergeAnswer (tail winners) locs defs;
+
+  mergeFalseByDefault = locs: defs:
+    if defs == [] then abort "This case should never happen."
+    else if any (x: x == false) defs then false
+    else true;
+
+  kernelItem = types.submodule {
+    options = {
+      tristate = mkOption {
+        type = types.enum [ "y" "m" "n" null ] // {
+          merge = mergeAnswer [ "y" "m" "n" ];
+        };
+        default = null;
+        internal = true;
+        visible = true;
+        description = ''
+          Use this field for tristate kernel options expecting a "y" or "m" or "n".
+        '';
+      };
+
+      freeform = mkOption {
+        type = types.nullOr types.str // {
+          merge = mergeEqualOption;
+        };
+        default = null;
+        example = ''MMC_BLOCK_MINORS.freeform = "32";'';
+        description = ''
+          Freeform description of a kernel configuration item value.
+        '';
+      };
+
+      optional = mkOption {
+        type = types.bool // { merge = mergeFalseByDefault; };
+        default = false;
+        description = ''
+          Wether option should generate a failure when unused.
+        '';
+      };
+    };
+  };
+
+  mkValue = with lib; val:
+  let
+    isNumber = c: elem c ["0" "1" "2" "3" "4" "5" "6" "7" "8" "9"];
+
+  in
+    if (val == "") then "\"\""
+    else if val == "y" || val == "m" || val == "n" then val
+    else if all isNumber (stringToCharacters val) then val
+    else if substring 0 2 val == "0x" then val
+    else val; # FIXME: fix quoting one day
+
+
+  # generate nix intermediate kernel config file of the form
+  #
+  #       VIRTIO_MMIO m
+  #       VIRTIO_BLK y
+  #       VIRTIO_CONSOLE n
+  #       NET_9P_VIRTIO? y
+  #
+  # Borrowed from copumpkin https://github.com/NixOS/nixpkgs/pull/12158
+  # returns a string, expr should be an attribute set
+  # Use mkValuePreprocess to preprocess option values, aka mark 'modules' as 'yes' or vice-versa
+  # use the identity if you don't want to override the configured values
+  generateNixKConf = exprs:
+  let
+    mkConfigLine = key: item:
+      let
+        val = if item.freeform != null then item.freeform else item.tristate;
+      in
+        if val == null
+          then ""
+          else if (item.optional)
+            then "${key}? ${mkValue val}\n"
+            else "${key} ${mkValue val}\n";
+
+    mkConf = cfg: concatStrings (mapAttrsToList mkConfigLine cfg);
+  in mkConf exprs;
+
+in
+{
+
+  options = {
+
+    intermediateNixConfig = mkOption {
+      readOnly = true;
+      type = types.lines;
+      example = ''
+        USB? y
+        DEBUG n
+      '';
+      description = ''
+        The result of converting the structured kernel configuration in settings
+        to an intermediate string that can be parsed by generate-config.pl to
+        answer the kernel `make defconfig`.
+      '';
+    };
+
+    settings = mkOption {
+      type = types.attrsOf kernelItem;
+      example = literalExample '' with lib.kernel; {
+        "9P_NET" = yes;
+        USB = optional yes;
+        MMC_BLOCK_MINORS = freeform "32";
+      }'';
+      description = ''
+        Structured kernel configuration.
+      '';
+    };
+  };
+
+  config = {
+    intermediateNixConfig = generateNixKConf config.settings;
+  };
+}
diff --git a/pkgs/os-specific/linux/kernel/common-config.nix b/pkgs/os-specific/linux/kernel/common-config.nix
index ddd1e9828d5..1a56e68fa4b 100644
--- a/pkgs/os-specific/linux/kernel/common-config.nix
+++ b/pkgs/os-specific/linux/kernel/common-config.nix
@@ -12,23 +12,12 @@
 # Configuration
 { stdenv, version
 
-# to let user override values, aka converting modules to included and vice-versa
-, mkValueOverride ? null
-
-# new extraConfig as a flattened set
-, structuredExtraConfig ? {}
-
-# legacy extraConfig as string
-, extraConfig ? ""
-
 , features ? { grsecurity = false; xen_dom0 = false; }
 }:
 
-assert (mkValueOverride == null) || (builtins.isFunction mkValueOverride);
-
 with stdenv.lib;
 
-with import ../../../../lib/kernel.nix { inherit (stdenv) lib; inherit version; };
+  with import ../../../../lib/kernel.nix { inherit (stdenv) lib; inherit version; };
 
 let
 
@@ -46,7 +35,7 @@ let
       DEBUG_NX_TEST             = whenOlder "4.11" no;
       CPU_NOTIFIER_ERROR_INJECT = whenOlder "4.4" (option no);
       DEBUG_STACK_USAGE         = no;
-      DEBUG_STACKOVERFLOW       = when (!features.grsecurity) no;
+      DEBUG_STACKOVERFLOW       = mkIf (!features.grsecurity) no;
       RCU_TORTURE_TEST          = no;
       SCHEDSTATS                = no;
       DETECT_HUNG_TASK          = yes;
@@ -114,7 +103,7 @@ let
       IP_DCCP_CCID3      = no; # experimental
       CLS_U32_PERF       = yes;
       CLS_U32_MARK       = yes;
-      BPF_JIT            = when (stdenv.hostPlatform.system == "x86_64-linux") yes;
+      BPF_JIT            = mkIf (stdenv.hostPlatform.system == "x86_64-linux") yes;
       WAN                = yes;
       # Required by systemd per-cgroup firewalling
       CGROUP_BPF                  = option yes;
@@ -184,7 +173,7 @@ let
       FB_VESA             = yes;
       FRAMEBUFFER_CONSOLE = yes;
       FRAMEBUFFER_CONSOLE_ROTATION = yes;
-      FB_GEODE            = when (stdenv.hostPlatform.system == "i686-linux") yes;
+      FB_GEODE            = mkIf (stdenv.hostPlatform.system == "i686-linux") yes;
     };
 
     video = {
@@ -239,7 +228,7 @@ let
     };
 
     usb = {
-      USB_DEBUG            = option (whenOlder "4.18" no);
+      USB_DEBUG = { optional = true; tristate = whenOlder "4.18" "n";};
       USB_EHCI_ROOT_HUB_TT = yes; # Root Hub Transaction Translators
       USB_EHCI_TT_NEWSCHED = yes; # Improved transaction translator scheduling
     };
@@ -250,7 +239,7 @@ let
       FANOTIFY        = yes;
       TMPFS           = yes;
       TMPFS_POSIX_ACL = yes;
-      FS_ENCRYPTION   = option (whenAtLeast "4.9" module);
+      FS_ENCRYPTION   = { optional = true; tristate = whenAtLeast "4.9" "m"; };
 
       EXT2_FS_XATTR     = yes;
       EXT2_FS_POSIX_ACL = yes;
@@ -262,7 +251,7 @@ let
 
       EXT4_FS_POSIX_ACL = yes;
       EXT4_FS_SECURITY  = yes;
-      EXT4_ENCRYPTION   = option ((if (versionOlder version "4.8") then module else yes));
+      EXT4_ENCRYPTION   = { optional = true; tristate = if (versionOlder version "4.8") then "m" else "y"; };
 
       REISERFS_FS_XATTR     = option yes;
       REISERFS_FS_POSIX_ACL = option yes;
@@ -324,7 +313,7 @@ let
 
       # Native Language Support modules, needed by some filesystems
       NLS              = yes;
-      NLS_DEFAULT      = "utf8";
+      NLS_DEFAULT      = freeform "utf8";
       NLS_UTF8         = module;
       NLS_CODEPAGE_437 = module; # VFAT default for the codepage= mount option
       NLS_ISO8859_1    = module; # VFAT default for the iocharset= mount option
@@ -334,13 +323,13 @@ let
 
     security = {
       # Detect writes to read-only module pages
-      DEBUG_SET_MODULE_RONX            = option (whenOlder "4.11" yes);
+      DEBUG_SET_MODULE_RONX            = { optional = true; tristate = whenOlder "4.11" "y"; };
       RANDOMIZE_BASE                   = option yes;
       STRICT_DEVMEM                    = option yes; # Filter access to /dev/mem
-      SECURITY_SELINUX_BOOTPARAM_VALUE = "0"; # Disable SELinux by default
+      SECURITY_SELINUX_BOOTPARAM_VALUE = freeform "0"; # Disable SELinux by default
       # Prevent processes from ptracing non-children processes
       SECURITY_YAMA                    = option yes;
-      DEVKMEM                          = when (!features.grsecurity) no; # Disable /dev/kmem
+      DEVKMEM                          = mkIf (!features.grsecurity) no; # Disable /dev/kmem
 
       USER_NS                          = yes; # Support for user namespaces
 
@@ -350,7 +339,7 @@ let
     } // optionalAttrs (!stdenv.hostPlatform.isAarch32) {
 
       # Detect buffer overflows on the stack
-      CC_STACKPROTECTOR_REGULAR = option (whenOlder "4.18" yes);
+      CC_STACKPROTECTOR_REGULAR = {optional = true; tristate = whenOlder "4.18" "y";};
     };
 
     microcode = {
@@ -407,8 +396,8 @@ let
       FTRACE_SYSCALLS       = yes;
       SCHED_TRACER          = yes;
       STACK_TRACER          = yes;
-      UPROBE_EVENT          = option (whenOlder "4.11" yes);
-      UPROBE_EVENTS         = option (whenAtLeast "4.11" yes);
+      UPROBE_EVENT          = { optional = true; tristate = whenOlder "4.11" "y";};
+      UPROBE_EVENTS         = { optional = true; tristate = whenAtLeast "4.11" "y";};
       BPF_SYSCALL           = whenAtLeast "4.4" yes;
       BPF_EVENTS            = whenAtLeast "4.4" yes;
       FUNCTION_PROFILER     = yes;
@@ -418,23 +407,23 @@ let
     virtualisation = {
       PARAVIRT = option yes;
 
-      HYPERVISOR_GUEST = when (!features.grsecurity) yes;
+      HYPERVISOR_GUEST = mkIf (!features.grsecurity) yes;
       PARAVIRT_SPINLOCKS  = option yes;
 
       KVM_APIC_ARCHITECTURE             = whenOlder "4.8" yes;
       KVM_ASYNC_PF                      = yes;
-      KVM_COMPAT                        = option (whenBetween "4.0" "4.12"  yes);
-      KVM_DEVICE_ASSIGNMENT             = option (whenBetween "3.10" "4.12" yes);
+      KVM_COMPAT = { optional = true; tristate = whenBetween "4.0" "4.12" "y"; };
+      KVM_DEVICE_ASSIGNMENT  = { optional = true; tristate = whenBetween "3.10" "4.12" "y"; };
       KVM_GENERIC_DIRTYLOG_READ_PROTECT = whenAtLeast "4.0"  yes;
-      KVM_GUEST                         = when (!features.grsecurity) yes;
+      KVM_GUEST                         = mkIf (!features.grsecurity) yes;
       KVM_MMIO                          = yes;
       KVM_VFIO                          = yes;
       KSM = yes;
       VIRT_DRIVERS = yes;
       # We nneed 64 GB (PAE) support for Xen guest support
-      HIGHMEM64G = option (when (!stdenv.is64bit) yes);
+      HIGHMEM64G = { optional = true; tristate = mkIf (!stdenv.is64bit) "y";};
 
-      VFIO_PCI_VGA = when stdenv.is64bit yes;
+      VFIO_PCI_VGA = mkIf stdenv.is64bit yes;
 
     } // optionalAttrs (stdenv.isx86_64 || stdenv.isi686) ({
       XEN = option yes;
@@ -542,8 +531,8 @@ let
       CRYPTO_TEST              = option no;
       EFI_TEST                 = option no;
       GLOB_SELFTEST            = option no;
-      DRM_DEBUG_MM_SELFTEST    = option (whenOlder "4.18" no);
-      LNET_SELFTEST            = option (whenOlder "4.18" no);
+      DRM_DEBUG_MM_SELFTEST    = { optional = true; tristate = whenOlder "4.18" "n";};
+      LNET_SELFTEST            = { optional = true; tristate = whenOlder "4.18" "n";};
       LOCK_TORTURE_TEST        = option no;
       MTD_TESTS                = option no;
       NOTIFIER_ERROR_INJECTION = option no;
@@ -598,7 +587,7 @@ let
       AIC79XX_DEBUG_ENABLE = no;
       AIC7XXX_DEBUG_ENABLE = no;
       AIC94XX_DEBUG = no;
-      B43_PCMCIA = option (whenOlder "4.4" yes);
+      B43_PCMCIA = { optional=true; tristate = whenOlder "4.4" "y";};
 
       BLK_DEV_INTEGRITY       = yes;
 
@@ -651,7 +640,7 @@ let
       # GPIO on Intel Bay Trail, for some Chromebook internal eMMC disks
       PINCTRL_BAYTRAIL   = yes;
       # 8 is default. Modern gpt tables on eMMC may go far beyond 8.
-      MMC_BLOCK_MINORS   = "32";
+      MMC_BLOCK_MINORS   = freeform "32";
 
       REGULATOR  = yes; # Voltage and Current Regulator Support
       RC_DEVICES = option yes; # Enable IR devices
@@ -698,7 +687,8 @@ let
 
       # Bump the maximum number of CPUs to support systems like EC2 x1.*
       # instances and Xeon Phi.
-      NR_CPUS = "384";
+      NR_CPUS = freeform "384";
     };
   };
-in (generateNixKConf ((flattenKConf options) // structuredExtraConfig) mkValueOverride) + extraConfig
+in
+  flattenKConf options
diff --git a/pkgs/os-specific/linux/kernel/generic.nix b/pkgs/os-specific/linux/kernel/generic.nix
index 30878d1b96c..a41f1eb989b 100644
--- a/pkgs/os-specific/linux/kernel/generic.nix
+++ b/pkgs/os-specific/linux/kernel/generic.nix
@@ -47,7 +47,6 @@
 , preferBuiltin ? stdenv.hostPlatform.platform.kernelPreferBuiltin or false
 , kernelArch ? stdenv.hostPlatform.platform.kernelArch
 
-, mkValueOverride ? null
 , ...
 }:
 
@@ -68,20 +67,26 @@ let
     ia32Emulation = true;
   } // features) kernelPatches;
 
-  intermediateNixConfig = import ./common-config.nix {
-    inherit stdenv version structuredExtraConfig mkValueOverride;
-
-    # append extraConfig for backwards compatibility but also means the user can't override the kernelExtraConfig part
-    extraConfig = extraConfig + lib.optionalString (stdenv.hostPlatform.platform ? kernelExtraConfig) stdenv.hostPlatform.platform.kernelExtraConfig;
+  commonStructuredConfig = import ./common-config.nix {
+    inherit stdenv version ;
 
     features = kernelFeatures; # Ensure we know of all extra patches, etc.
   };
 
-  kernelConfigFun = baseConfig:
+  # extra config in legacy string format
+  extraConfig = extraConfig + lib.optionalString (stdenv.hostPlatform.platform ? kernelExtraConfig) stdenv.hostPlatform.platform.kernelExtraConfig;
+
+  intermediateNixConfig = configfile.moduleStructuredConfig.intermediateNixConfig;
+
+  structuredConfigFromPatches =
+        map ({extraStructuredConfig ? {}, ...}: {settings=extraStructuredConfig;}) kernelPatches;
+
+  # appends kernel patches extraConfig
+  kernelConfigFun = baseConfigStr:
     let
       configFromPatches =
         map ({extraConfig ? "", ...}: extraConfig) kernelPatches;
-    in lib.concatStringsSep "\n" ([baseConfig] ++ configFromPatches);
+    in lib.concatStringsSep "\n" ([baseConfigStr] ++ configFromPatches);
 
   configfile = stdenv.mkDerivation {
     inherit ignoreConfigErrors autoModules preferBuiltin kernelArch;
@@ -131,7 +136,30 @@ let
     installPhase = "mv $buildRoot/.config $out";
 
     enableParallelBuilding = true;
-  };
+
+    passthru = rec {
+
+      module = import ../../../../nixos/modules/system/boot/kernel_config.nix;
+      # used also in apache
+      # { modules = [ { options = res.options; config = svc.config or svc; } ];
+      #   check = false;
+      # The result is a set of two attributes
+      moduleStructuredConfig = (lib.evalModules {
+        modules = [
+          module
+          { settings = commonStructuredConfig; }
+          { settings = structuredExtraConfig; }
+        ]
+        ++  structuredConfigFromPatches
+        ;
+      }).config;
+
+      #
+      structuredConfig = moduleStructuredConfig.settings;
+    };
+
+
+  }; # end of configfile derivation
 
   kernel = (callPackage ./manual-config.nix {}) {
     inherit version modDirVersion src kernelPatches stdenv extraMeta configfile;
@@ -141,6 +169,7 @@ let
 
   passthru = {
     features = kernelFeatures;
+    inherit commonStructuredConfig;
     passthru = kernel.passthru // (removeAttrs passthru [ "passthru" ]);
   };
 
diff --git a/pkgs/os-specific/linux/kernel/hardened-config.nix b/pkgs/os-specific/linux/kernel/hardened-config.nix
index ed540a9e751..f1f18c64130 100644
--- a/pkgs/os-specific/linux/kernel/hardened-config.nix
+++ b/pkgs/os-specific/linux/kernel/hardened-config.nix
@@ -11,138 +11,110 @@
 { stdenv, version }:
 
 with stdenv.lib;
+with import ../../../../lib/kernel.nix { inherit (stdenv) lib; inherit version; };
 
 assert (versionAtLeast version "4.9");
 
-''
-# Report BUG() conditions and kill the offending process.
-BUG y
-
-${optionalString (versionAtLeast version "4.10") ''
-  BUG_ON_DATA_CORRUPTION y
-''}
-
-${optionalString (stdenv.hostPlatform.platform.kernelArch == "x86_64") ''
-  DEFAULT_MMAP_MIN_ADDR 65536 # Prevent allocation of first 64K of memory
+optionalAttrs (stdenv.hostPlatform.platform.kernelArch == "x86_64") {
+  DEFAULT_MMAP_MIN_ADDR = freeform "65536";  # Prevent allocation of first 64K of memory
 
   # Reduce attack surface by disabling various emulations
-  IA32_EMULATION n
-  X86_X32 n
+  IA32_EMULATION     = no;
+  X86_X32            = no;
   # Note: this config depends on EXPERT y and so will not take effect, hence
   # it is left "optional" for now.
-  MODIFY_LDT_SYSCALL? n
-
-  VMAP_STACK y # Catch kernel stack overflows
+  MODIFY_LDT_SYSCALL = option no;
+  VMAP_STACK         = yes; # Catch kernel stack overflows
 
   # Randomize position of kernel and memory.
-  RANDOMIZE_BASE y
-  RANDOMIZE_MEMORY y
+  RANDOMIZE_BASE   = yes;
+  RANDOMIZE_MEMORY = yes;
 
   # Disable legacy virtual syscalls by default (modern glibc use vDSO instead).
   #
   # Note that the vanilla default is to *emulate* the legacy vsyscall mechanism,
   # which is supposed to be safer than the native variant (wrt. ret2libc), so
   # disabling it mainly helps reduce surface.
-  LEGACY_VSYSCALL_NONE y
-''}
-
-# Safer page access permissions (wrt. code injection).  Default on >=4.11.
-${optionalString (versionOlder version "4.11") ''
-  DEBUG_RODATA y
-  DEBUG_SET_MODULE_RONX y
-''}
-
-# Mark LSM hooks read-only after init.  SECURITY_WRITABLE_HOOKS n
-# conflicts with SECURITY_SELINUX_DISABLE y; disabling the latter
-# implicitly marks LSM hooks read-only after init.
-#
-# SELinux can only be disabled at boot via selinux=0
-#
-# We set SECURITY_WRITABLE_HOOKS n primarily for documentation purposes; the
-# config builder fails to detect that it has indeed been unset.
-${optionalString (versionAtLeast version "4.12") ''
-  SECURITY_SELINUX_DISABLE n
-  SECURITY_WRITABLE_HOOKS? n
-''}
-
-DEBUG_WX y # boot-time warning on RWX mappings
-${optionalString (versionAtLeast version "4.11") ''
-  STRICT_KERNEL_RWX y
-''}
-
-# Stricter /dev/mem
-STRICT_DEVMEM? y
-IO_STRICT_DEVMEM? y
-
-# Perform additional validation of commonly targeted structures.
-DEBUG_CREDENTIALS y
-DEBUG_NOTIFIERS y
-DEBUG_LIST y
-DEBUG_PI_LIST y # doesn't BUG()
-DEBUG_SG y
-SCHED_STACK_END_CHECK y
-
-${optionalString (versionAtLeast version "4.13") ''
-  REFCOUNT_FULL y
-''}
-
-# Perform usercopy bounds checking.
-HARDENED_USERCOPY y
-${optionalString (versionAtLeast version "4.16") ''
-  HARDENED_USERCOPY_FALLBACK n  # for full whitelist enforcement
-''}
-
-# Randomize allocator freelists.
-SLAB_FREELIST_RANDOM y
-
-${optionalString (versionAtLeast version "4.14") ''
-  SLAB_FREELIST_HARDENED y
-''}
-
-# Allow enabling slub/slab free poisoning with slub_debug=P
-SLUB_DEBUG y
-
-# Wipe higher-level memory allocations on free() with page_poison=1
-PAGE_POISONING y
-PAGE_POISONING_NO_SANITY y
-PAGE_POISONING_ZERO y
-
-# Reboot devices immediately if kernel experiences an Oops.
-PANIC_ON_OOPS y
-PANIC_TIMEOUT -1
-
-GCC_PLUGINS y # Enable gcc plugin options
-# Gather additional entropy at boot time for systems that may not have appropriate entropy sources.
-GCC_PLUGIN_LATENT_ENTROPY y
-
-${optionalString (versionAtLeast version "4.11") ''
-  GCC_PLUGIN_STRUCTLEAK y # A port of the PaX structleak plugin
-''}
-${optionalString (versionAtLeast version "4.14") ''
-  GCC_PLUGIN_STRUCTLEAK_BYREF_ALL y # Also cover structs passed by address
-''}
-${optionalString (versionAtLeast version "4.20") ''
-  GCC_PLUGIN_STACKLEAK y # A port of the PaX stackleak plugin
-''}
-
-${optionalString (versionAtLeast version "4.13") ''
-  GCC_PLUGIN_RANDSTRUCT y # A port of the PaX randstruct plugin
-  GCC_PLUGIN_RANDSTRUCT_PERFORMANCE y
-''}
-
-# Disable various dangerous settings
-ACPI_CUSTOM_METHOD n # Allows writing directly to physical memory
-PROC_KCORE n # Exposes kernel text image layout
-INET_DIAG n # Has been used for heap based attacks in the past
-
-# Use -fstack-protector-strong (gcc 4.9+) for best stack canary coverage.
-${optionalString (versionOlder version "4.18") ''
-  CC_STACKPROTECTOR_REGULAR n
-  CC_STACKPROTECTOR_STRONG y
-''}
-
-# Enable compile/run-time buffer overflow detection ala glibc's _FORTIFY_SOURCE
-${optionalString (versionAtLeast version "4.13") ''
-  FORTIFY_SOURCE y
-''}
-''
+  LEGACY_VSYSCALL_NONE = yes;
+} // {
+  # Report BUG() conditions and kill the offending process.
+  BUG = yes;
+
+  BUG_ON_DATA_CORRUPTION = whenAtLeast "4.10" yes;
+
+  # Safer page access permissions (wrt. code injection).  Default on >=4.11.
+  DEBUG_RODATA          = whenOlder "4.11" yes;
+  DEBUG_SET_MODULE_RONX = whenOlder "4.11" yes;
+
+  # Mark LSM hooks read-only after init.  SECURITY_WRITABLE_HOOKS n
+  # conflicts with SECURITY_SELINUX_DISABLE y; disabling the latter
+  # implicitly marks LSM hooks read-only after init.
+  #
+  # SELinux can only be disabled at boot via selinux=0
+  #
+  # We set SECURITY_WRITABLE_HOOKS n primarily for documentation purposes; the
+  # config builder fails to detect that it has indeed been unset.
+  SECURITY_SELINUX_DISABLE = whenAtLeast "4.12" no;
+  SECURITY_WRITABLE_HOOKS  = whenAtLeast "4.12" (option no);
+
+  DEBUG_WX = yes; # boot-time warning on RWX mappings
+  STRICT_KERNEL_RWX = whenAtLeast "4.11" yes;
+
+  # Stricter /dev/mem
+  STRICT_DEVMEM    = option yes;
+  IO_STRICT_DEVMEM = option yes;
+
+  # Perform additional validation of commonly targeted structures.
+  DEBUG_CREDENTIALS     = yes;
+  DEBUG_NOTIFIERS       = yes;
+  DEBUG_LIST            = yes;
+  DEBUG_PI_LIST         = yes; # doesn't BUG()
+  DEBUG_SG              = yes;
+  SCHED_STACK_END_CHECK = yes;
+
+  REFCOUNT_FULL = whenAtLeast "4.13" yes;
+
+  # Perform usercopy bounds checking.
+  HARDENED_USERCOPY = yes;
+  HARDENED_USERCOPY_FALLBACK = whenAtLeast "4.16" no; # for full whitelist enforcement
+
+  # Randomize allocator freelists.
+  SLAB_FREELIST_RANDOM = yes;
+
+  SLAB_FREELIST_HARDENED = whenAtLeast "4.14" yes;
+
+  # Allow enabling slub/slab free poisoning with slub_debug=P
+  SLUB_DEBUG = yes;
+
+  # Wipe higher-level memory allocations on free() with page_poison=1
+  PAGE_POISONING           = yes;
+  PAGE_POISONING_NO_SANITY = yes;
+  PAGE_POISONING_ZERO      = yes;
+
+  # Reboot devices immediately if kernel experiences an Oops.
+  PANIC_ON_OOPS = yes;
+  PANIC_TIMEOUT = freeform "-1";
+
+  GCC_PLUGINS = yes; # Enable gcc plugin options
+  # Gather additional entropy at boot time for systems that may = no;ot have appropriate entropy sources.
+  GCC_PLUGIN_LATENT_ENTROPY = yes;
+
+  GCC_PLUGIN_STRUCTLEAK = whenAtLeast "4.11" yes; # A port of the PaX structleak plugin
+  GCC_PLUGIN_STRUCTLEAK_BYREF_ALL = whenAtLeast "4.14" yes; # Also cover structs passed by address
+  GCC_PLUGIN_STACKLEAK = whenAtLeast "4.20" yes; # A port of the PaX stackleak plugin
+  GCC_PLUGIN_RANDSTRUCT = whenAtLeast "4.13" yes; # A port of the PaX randstruct plugin
+  GCC_PLUGIN_RANDSTRUCT_PERFORMANCE = whenAtLeast "4.13" yes;
+
+  # Disable various dangerous settings
+  ACPI_CUSTOM_METHOD = no; # Allows writing directly to physical memory
+  PROC_KCORE         = no; # Exposes kernel text image layout
+  INET_DIAG          = no; # Has been used for heap based attacks in the past
+
+  # Use -fstack-protector-strong (gcc 4.9+) for best stack canary coverage.
+  CC_STACKPROTECTOR_REGULAR = whenOlder "4.18" no;
+  CC_STACKPROTECTOR_STRONG  = whenOlder "4.18" yes;
+
+  # Enable compile/run-time buffer overflow detection ala glibc's _FORTIFY_SOURCE
+  FORTIFY_SOURCE = whenAtLeast "4.13" yes;
+
+}
diff --git a/pkgs/test/default.nix b/pkgs/test/default.nix
index 809b2d0b553..9b434da7a84 100644
--- a/pkgs/test/default.nix
+++ b/pkgs/test/default.nix
@@ -24,6 +24,8 @@ with pkgs;
   cc-multilib-gcc = callPackage ./cc-wrapper/multilib.nix { stdenv = gccMultiStdenv; };
   cc-multilib-clang = callPackage ./cc-wrapper/multilib.nix { stdenv = clangMultiStdenv; };
 
+  kernel-config = callPackage ./kernel.nix {};
+
   ld-library-path = callPackage ./ld-library-path {};
 
   macOSSierraShared = callPackage ./macos-sierra-shared {};
diff --git a/pkgs/test/kernel.nix b/pkgs/test/kernel.nix
new file mode 100644
index 00000000000..14a4d5ea104
--- /dev/null
+++ b/pkgs/test/kernel.nix
@@ -0,0 +1,53 @@
+{ stdenv, lib, pkgs }:
+
+with lib.kernel;
+with lib.asserts;
+with lib.modules;
+
+# To test nixos/modules/system/boot/kernel_config.nix;
+let
+  # copied from release-lib.nix
+  assertTrue = bool:
+    if bool
+    then pkgs.runCommand "evaluated-to-true" {} "touch $out"
+    else pkgs.runCommand "evaluated-to-false" {} "false";
+
+  lts_kernel = pkgs.linuxPackages.kernel;
+
+  kernelTestConfig = structuredConfig: (lts_kernel.override {
+    structuredExtraConfig = structuredConfig;
+  }).configfile.structuredConfig;
+
+  mandatoryVsOptionalConfig = mkMerge [
+    { USB_DEBUG = option yes;}
+    { USB_DEBUG = yes;}
+  ];
+
+  freeformConfig = mkMerge [
+    { MMC_BLOCK_MINORS = freeform "32"; } # same as default, won't trigger any error
+    { MMC_BLOCK_MINORS = freeform "64"; } # will trigger an error but the message is not great:
+  ];
+
+  yesWinsOverNoConfig = mkMerge [
+    # default for "8139TOO_PIO" is no
+    { "8139TOO_PIO"  = yes; } # yes wins over no by default
+    { "8139TOO_PIO"  = no; }
+  ];
+in
+{
+  # mandatory flag should win over optional
+  mandatoryCheck = (kernelTestConfig mandatoryVsOptionalConfig);
+
+  # check that freeform options are unique
+  # Should trigger
+  # > The option `settings.MMC_BLOCK_MINORS.freeform' has conflicting definitions, in `<unknown-file>' and `<unknown-file>'
+  freeformCheck = let
+    res = builtins.tryEval ( (kernelTestConfig freeformConfig).MMC_BLOCK_MINORS.freeform);
+  in
+    assertTrue (res.success == false);
+
+  yesVsNoCheck = let
+    res = kernelTestConfig yesWinsOverNoConfig;
+  in
+    assertTrue (res."8139TOO_PIO".tristate == "y");
+}
diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix
index a0ad2ea28d3..6ebc487ce6b 100644
--- a/pkgs/top-level/all-packages.nix
+++ b/pkgs/top-level/all-packages.nix
@@ -14756,7 +14756,7 @@ in
   # Hardened linux
   hardenedLinuxPackagesFor = kernel: linuxPackagesFor (kernel.override {
     features.ia32Emulation = false;
-    extraConfig = import ../os-specific/linux/kernel/hardened-config.nix {
+    structuredExtraConfig = import ../os-specific/linux/kernel/hardened-config.nix {
       inherit stdenv;
       inherit (kernel) version;
     };