{ lib
, stdenv
, runCommand
, runCommandWith
, runCommandCC
, hello
, debian-devscripts
}:

let
  # writeCBin from trivial-builders won't let us choose
  # our own stdenv
  writeCBinWithStdenv = codePath: stdenv': env: runCommandWith {
    name = "test-bin";
    stdenv = stdenv';
    derivationArgs = {
      inherit codePath;
      preferLocalBuild = true;
      allowSubstitutes = false;
    } // env;
  } ''
    [ -n "$postConfigure" ] && eval "$postConfigure"
    [ -n "$preBuild" ] && eval "$preBuild"
    n=$out/bin/test-bin
    mkdir -p "$(dirname "$n")"
    cp "$codePath" code.c
    NIX_DEBUG=1 $CC -x c code.c -O1 $TEST_EXTRA_FLAGS -o "$n"
  '';

  f1exampleWithStdEnv = writeCBinWithStdenv ./fortify1-example.c;
  f2exampleWithStdEnv = writeCBinWithStdenv ./fortify2-example.c;
  f3exampleWithStdEnv = writeCBinWithStdenv ./fortify3-example.c;

  # for when we need a slightly more complicated program
  helloWithStdEnv = stdenv': env: (hello.override { stdenv = stdenv'; }).overrideAttrs ({
    preBuild = ''
      export CFLAGS="$TEST_EXTRA_FLAGS"
    '';
    NIX_DEBUG = "1";
    postFixup = ''
      cp $out/bin/hello $out/bin/test-bin
    '';
  } // env);

  stdenvUnsupport = additionalUnsupported: stdenv.override {
    cc = stdenv.cc.override {
      cc = (lib.extendDerivation true rec {
        # this is ugly - have to cross-reference from
        # hardeningUnsupportedFlagsByTargetPlatform to hardeningUnsupportedFlags
        # because the finalAttrs mechanism that hardeningUnsupportedFlagsByTargetPlatform
        # implementations use to do this won't work with lib.extendDerivation.
        # but it's simplified by the fact that targetPlatform is already fixed
        # at this point.
        hardeningUnsupportedFlagsByTargetPlatform = _: hardeningUnsupportedFlags;
        hardeningUnsupportedFlags = (
          if stdenv.cc.cc ? hardeningUnsupportedFlagsByTargetPlatform
          then stdenv.cc.cc.hardeningUnsupportedFlagsByTargetPlatform stdenv.targetPlatform
          else (stdenv.cc.cc.hardeningUnsupportedFlags or [])
        ) ++ additionalUnsupported;
      } stdenv.cc.cc);
    };
    allowedRequisites = null;
  };

  checkTestBin = testBin: {
    # can only test flags that are detectable by hardening-check
    ignoreBindNow ? true,
    ignoreFortify ? true,
    ignorePie ? true,
    ignoreRelRO ? true,
    ignoreStackProtector ? true,
    ignoreStackClashProtection ? true,
    expectFailure ? false,
  }: let
    stackClashStr = "Stack clash protection: yes";
    expectFailureClause = lib.optionalString expectFailure
      " && echo 'ERROR: Expected hardening-check to fail, but it passed!' >&2 && false";
  in runCommandCC "check-test-bin" {
    nativeBuildInputs = [ debian-devscripts ];
    buildInputs = [ testBin ];
    meta.platforms = if ignoreStackClashProtection
      then lib.platforms.linux  # ELF-reliant
      else [ "x86_64-linux" ];  # stackclashprotection test looks for x86-specific instructions
  } (''
    if ${lib.optionalString (!expectFailure) "!"} {
      hardening-check --nocfprotection \
        ${lib.optionalString ignoreBindNow "--nobindnow"} \
        ${lib.optionalString ignoreFortify "--nofortify"} \
        ${lib.optionalString ignorePie "--nopie"} \
        ${lib.optionalString ignoreRelRO "--norelro"} \
        ${lib.optionalString ignoreStackProtector "--nostackprotector"} \
        $(PATH=$HOST_PATH type -P test-bin) | tee $out
  '' + lib.optionalString (!ignoreStackClashProtection) ''
      # stack clash protection doesn't actually affect the exit code of
      # hardening-check (likely authors think false negatives too common)
      { grep -F '${stackClashStr}' $out || { echo "Didn't find '${stackClashStr}' in output" && false ;} ;}
  '' + ''
    } ; then
  '' + lib.optionalString expectFailure ''
      echo 'ERROR: Expected hardening-check to fail, but it passed!' >&2
  '' + ''
      exit 2
    fi
  '');

  nameDrvAfterAttrName = builtins.mapAttrs (name: drv:
    drv.overrideAttrs (_: { name = "test-${name}"; })
  );

  # returning a specific exit code when aborting due to a fortify
  # check isn't mandated. so it's better to just ensure that a
  # nonzero exit code is returned when we go a single byte beyond
  # the buffer, with the example programs being designed to be
  # unlikely to genuinely segfault for such a small overflow.
  fortifyExecTest = testBin: runCommand "exec-test" {
    buildInputs = [
      testBin
    ];
    meta.broken = !(stdenv.buildPlatform.canExecute stdenv.hostPlatform);
  } ''
    (
      export PATH=$HOST_PATH
      echo "Saturated buffer:" # check program isn't completly broken
      test-bin 012345 7
      echo "One byte too far:" # eighth byte being the null terminator
      (! test-bin 0123456 7) || (echo 'Expected failure, but succeeded!' && exit 1)
    )
    echo "Expected behaviour observed"
    touch $out
  '';

  brokenIf = cond: drv: if cond then drv.overrideAttrs (old: { meta = old.meta or {} // { broken = true; }; }) else drv;

in nameDrvAfterAttrName ({
  bindNowExplicitEnabled = brokenIf stdenv.hostPlatform.isStatic (checkTestBin (f2exampleWithStdEnv stdenv {
    hardeningEnable = [ "bindnow" ];
  }) {
    ignoreBindNow = false;
  });

  # musl implementation undetectable by this means even if present
  fortifyExplicitEnabled = brokenIf stdenv.hostPlatform.isMusl (checkTestBin (f2exampleWithStdEnv stdenv {
    hardeningEnable = [ "fortify" ];
  }) {
    ignoreFortify = false;
  });

  fortify1ExplicitEnabledExecTest = fortifyExecTest (f1exampleWithStdEnv stdenv {
    hardeningEnable = [ "fortify" ];
  });

  # musl implementation is effectively FORTIFY_SOURCE=1-only,
  # clang-on-glibc also only appears to support FORTIFY_SOURCE=1 (!)
  fortifyExplicitEnabledExecTest = brokenIf (
    stdenv.hostPlatform.isMusl || (stdenv.cc.isClang && stdenv.hostPlatform.libc == "glibc")
  ) (fortifyExecTest (f2exampleWithStdEnv stdenv {
    hardeningEnable = [ "fortify" ];
  }));

  fortify3ExplicitEnabled = brokenIf (
    stdenv.hostPlatform.isMusl || !stdenv.cc.isGNU || lib.versionOlder stdenv.cc.version "12"
  ) (checkTestBin (f3exampleWithStdEnv stdenv {
    hardeningEnable = [ "fortify3" ];
  }) {
    ignoreFortify = false;
  });

  # musl implementation is effectively FORTIFY_SOURCE=1-only
  fortify3ExplicitEnabledExecTest = brokenIf (
    stdenv.hostPlatform.isMusl || !stdenv.cc.isGNU || lib.versionOlder stdenv.cc.version "12"
  ) (fortifyExecTest (f3exampleWithStdEnv stdenv {
    hardeningEnable = [ "fortify3" ];
  }));

  pieExplicitEnabled = brokenIf stdenv.hostPlatform.isStatic (checkTestBin (f2exampleWithStdEnv stdenv {
    hardeningEnable = [ "pie" ];
  }) {
    ignorePie = false;
  });

  relROExplicitEnabled = checkTestBin (f2exampleWithStdEnv stdenv {
    hardeningEnable = [ "relro" ];
  }) {
    ignoreRelRO = false;
  };

  stackProtectorExplicitEnabled = brokenIf stdenv.hostPlatform.isStatic (checkTestBin (f2exampleWithStdEnv stdenv {
    hardeningEnable = [ "stackprotector" ];
  }) {
    ignoreStackProtector = false;
  });

  # protection patterns generated by clang not detectable?
  stackClashProtectionExplicitEnabled = brokenIf stdenv.cc.isClang (checkTestBin (helloWithStdEnv stdenv {
    hardeningEnable = [ "stackclashprotection" ];
  }) {
    ignoreStackClashProtection = false;
  });

  bindNowExplicitDisabled = checkTestBin (f2exampleWithStdEnv stdenv {
    hardeningDisable = [ "bindnow" ];
  }) {
    ignoreBindNow = false;
    expectFailure = true;
  };

  fortifyExplicitDisabled = checkTestBin (f2exampleWithStdEnv stdenv {
    hardeningDisable = [ "fortify" ];
  }) {
    ignoreFortify = false;
    expectFailure = true;
  };

  fortify3ExplicitDisabled = checkTestBin (f3exampleWithStdEnv stdenv {
    hardeningDisable = [ "fortify3" ];
  }) {
    ignoreFortify = false;
    expectFailure = true;
  };

  fortifyExplicitDisabledDisablesFortify3 = checkTestBin (f3exampleWithStdEnv stdenv {
    hardeningEnable = [ "fortify3" ];
    hardeningDisable = [ "fortify" ];
  }) {
    ignoreFortify = false;
    expectFailure = true;
  };

  fortify3ExplicitDisabledDoesntDisableFortify = checkTestBin (f2exampleWithStdEnv stdenv {
    hardeningEnable = [ "fortify" ];
    hardeningDisable = [ "fortify3" ];
  }) {
    ignoreFortify = false;
  };

  pieExplicitDisabled = brokenIf (
    stdenv.hostPlatform.isMusl && stdenv.cc.isClang
  ) (checkTestBin (f2exampleWithStdEnv stdenv {
    hardeningDisable = [ "pie" ];
  }) {
    ignorePie = false;
    expectFailure = true;
  });

  # can't force-disable ("partial"?) relro
  relROExplicitDisabled = brokenIf true (checkTestBin (f2exampleWithStdEnv stdenv {
    hardeningDisable = [ "pie" ];
  }) {
    ignoreRelRO = false;
    expectFailure = true;
  });

  stackProtectorExplicitDisabled = checkTestBin (f2exampleWithStdEnv stdenv {
    hardeningDisable = [ "stackprotector" ];
  }) {
    ignoreStackProtector = false;
    expectFailure = true;
  };

  stackClashProtectionExplicitDisabled = checkTestBin (helloWithStdEnv stdenv {
    hardeningDisable = [ "stackclashprotection" ];
  }) {
    ignoreStackClashProtection = false;
    expectFailure = true;
  };

  # most flags can't be "unsupported" by compiler alone and
  # binutils doesn't have an accessible hardeningUnsupportedFlags
  # mechanism, so can only test a couple of flags through altered
  # stdenv trickery

  fortifyStdenvUnsupp = checkTestBin (f2exampleWithStdEnv (stdenvUnsupport ["fortify" "fortify3"]) {
    hardeningEnable = [ "fortify" ];
  }) {
    ignoreFortify = false;
    expectFailure = true;
  };

  fortify3StdenvUnsupp = checkTestBin (f3exampleWithStdEnv (stdenvUnsupport ["fortify3"]) {
    hardeningEnable = [ "fortify3" ];
  }) {
    ignoreFortify = false;
    expectFailure = true;
  };

  fortifyStdenvUnsuppUnsupportsFortify3 = checkTestBin (f3exampleWithStdEnv (stdenvUnsupport ["fortify"]) {
    hardeningEnable = [ "fortify3" ];
  }) {
    ignoreFortify = false;
    expectFailure = true;
  };

  # musl implementation undetectable by this means even if present
  fortify3StdenvUnsuppDoesntUnsuppFortify1 = brokenIf stdenv.hostPlatform.isMusl (checkTestBin (f1exampleWithStdEnv (stdenvUnsupport ["fortify3"]) {
    hardeningEnable = [ "fortify" ];
  }) {
    ignoreFortify = false;
  });

  fortify3StdenvUnsuppDoesntUnsuppFortify1ExecTest = fortifyExecTest (f1exampleWithStdEnv (stdenvUnsupport ["fortify3"]) {
    hardeningEnable = [ "fortify" ];
  });

  stackProtectorStdenvUnsupp = checkTestBin (f2exampleWithStdEnv (stdenvUnsupport ["stackprotector"]) {
    hardeningEnable = [ "stackprotector" ];
  }) {
    ignoreStackProtector = false;
    expectFailure = true;
  };

  stackClashProtectionStdenvUnsupp = checkTestBin (helloWithStdEnv (stdenvUnsupport ["stackclashprotection"]) {
    hardeningEnable = [ "stackclashprotection" ];
  }) {
    ignoreStackClashProtection = false;
    expectFailure = true;
  };

  # NIX_HARDENING_ENABLE set in the shell overrides hardeningDisable
  # and hardeningEnable

  stackProtectorReenabledEnv = checkTestBin (f2exampleWithStdEnv stdenv {
    hardeningDisable = [ "stackprotector" ];
    postConfigure = ''
      export NIX_HARDENING_ENABLE="stackprotector"
    '';
  }) {
    ignoreStackProtector = false;
  };

  stackProtectorReenabledFromAllEnv = checkTestBin (f2exampleWithStdEnv stdenv {
    hardeningDisable = [ "all" ];
    postConfigure = ''
      export NIX_HARDENING_ENABLE="stackprotector"
    '';
  }) {
    ignoreStackProtector = false;
  };

  stackProtectorRedisabledEnv = checkTestBin (f2exampleWithStdEnv stdenv {
    hardeningEnable = [ "stackprotector" ];
    postConfigure = ''
      export NIX_HARDENING_ENABLE=""
    '';
  }) {
    ignoreStackProtector = false;
    expectFailure = true;
  };

  # musl implementation undetectable by this means even if present
  fortify3EnabledEnvEnablesFortify1 = brokenIf stdenv.hostPlatform.isMusl (checkTestBin (f1exampleWithStdEnv stdenv {
    hardeningDisable = [ "fortify" "fortify3" ];
    postConfigure = ''
      export NIX_HARDENING_ENABLE="fortify3"
    '';
  }) {
    ignoreFortify = false;
  });

  fortify3EnabledEnvEnablesFortify1ExecTest = fortifyExecTest (f1exampleWithStdEnv stdenv {
    hardeningDisable = [ "fortify" "fortify3" ];
    postConfigure = ''
      export NIX_HARDENING_ENABLE="fortify3"
    '';
  });

  fortifyEnabledEnvDoesntEnableFortify3 = checkTestBin (f3exampleWithStdEnv stdenv {
    hardeningDisable = [ "fortify" "fortify3" ];
    postConfigure = ''
      export NIX_HARDENING_ENABLE="fortify"
    '';
  }) {
    ignoreFortify = false;
    expectFailure = true;
  };

  # NIX_HARDENING_ENABLE can't enable an unsupported feature
  stackProtectorUnsupportedEnabledEnv = checkTestBin (f2exampleWithStdEnv (stdenvUnsupport ["stackprotector"]) {
    postConfigure = ''
      export NIX_HARDENING_ENABLE="stackprotector"
    '';
  }) {
    ignoreStackProtector = false;
    expectFailure = true;
  };

  # current implementation prevents the command-line from disabling
  # fortify if cc-wrapper is enabling it.

  # undetectable by this means on static even if present
  fortify1ExplicitEnabledCmdlineDisabled = brokenIf stdenv.hostPlatform.isStatic (checkTestBin (f1exampleWithStdEnv stdenv {
    hardeningEnable = [ "fortify" ];
    postConfigure = ''
      export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=0'
    '';
  }) {
    ignoreFortify = false;
    expectFailure = false;
  });

  # current implementation doesn't force-disable fortify if
  # command-line enables it even if we use hardeningDisable.

  # musl implementation undetectable by this means even if present
  fortify1ExplicitDisabledCmdlineEnabled = brokenIf (
    stdenv.hostPlatform.isMusl || stdenv.hostPlatform.isStatic
  ) (checkTestBin (f1exampleWithStdEnv stdenv {
    hardeningDisable = [ "fortify" ];
    postConfigure = ''
      export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=1'
    '';
  }) {
    ignoreFortify = false;
  });

  fortify1ExplicitDisabledCmdlineEnabledExecTest = fortifyExecTest (f1exampleWithStdEnv stdenv {
    hardeningDisable = [ "fortify" ];
    postConfigure = ''
      export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=1'
    '';
  });

  fortify1ExplicitEnabledCmdlineDisabledNoWarn = f1exampleWithStdEnv stdenv {
    hardeningEnable = [ "fortify" ];
    postConfigure = ''
      export TEST_EXTRA_FLAGS='-D_FORTIFY_SOURCE=0 -Werror'
    '';
  };

} // (let
  tb = f2exampleWithStdEnv stdenv {
    hardeningDisable = [ "all" ];
    hardeningEnable = [ "fortify" "pie" ];
  };
in {

  allExplicitDisabledBindNow = checkTestBin tb {
    ignoreBindNow = false;
    expectFailure = true;
  };

  allExplicitDisabledFortify = checkTestBin tb {
    ignoreFortify = false;
    expectFailure = true;
  };

  allExplicitDisabledPie = brokenIf (
    stdenv.hostPlatform.isMusl && stdenv.cc.isClang
  ) (checkTestBin tb {
    ignorePie = false;
    expectFailure = true;
  });

  # can't force-disable ("partial"?) relro
  allExplicitDisabledRelRO = brokenIf true (checkTestBin tb {
    ignoreRelRO = false;
    expectFailure = true;
  });

  allExplicitDisabledStackProtector = checkTestBin tb {
    ignoreStackProtector = false;
    expectFailure = true;
  };

  allExplicitDisabledStackClashProtection = checkTestBin tb {
    ignoreStackClashProtection = false;
    expectFailure = true;
  };
}))
