From e73b7bfc68006ebe95ae4835c1d94c97ad5b1c35 Mon Sep 17 00:00:00 2001 From: Luna Nova Date: Wed, 5 Nov 2025 07:07:55 -0800 Subject: [PATCH 1/3] go: move tests to separate file for sharing between versions --- pkgs/development/compilers/go/1.24.nix | 17 ++++------- pkgs/development/compilers/go/1.25.nix | 38 ++++------------------- pkgs/development/compilers/go/tests.nix | 40 +++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 45 deletions(-) create mode 100644 pkgs/development/compilers/go/tests.nix diff --git a/pkgs/development/compilers/go/1.24.nix b/pkgs/development/compilers/go/1.24.nix index b7c23511bc5e..005e92264d63 100644 --- a/pkgs/development/compilers/go/1.24.nix +++ b/pkgs/development/compilers/go/1.24.nix @@ -9,16 +9,13 @@ buildPackages, pkgsBuildTarget, targetPackages, - testers, - skopeo, buildGo124Module, + callPackage, }: let goBootstrap = buildPackages.callPackage ./bootstrap122.nix { }; - skopeoTest = skopeo.override { buildGoModule = buildGo124Module; }; - # We need a target compiler which is still runnable at build time, # to handle the cross-building case where build != host == target targetCC = pkgsBuildTarget.targetPackages.stdenv.cc; @@ -195,14 +192,10 @@ stdenv.mkDerivation (finalAttrs: { disallowedReferences = [ goBootstrap ]; passthru = { - inherit goBootstrap skopeoTest; - tests = { - skopeo = testers.testVersion { package = skopeoTest; }; - version = testers.testVersion { - package = finalAttrs.finalPackage; - command = "go version"; - version = "go${finalAttrs.version}"; - }; + inherit goBootstrap; + tests = callPackage ./tests.nix { + go = finalAttrs.finalPackage; + buildGoModule = buildGo124Module; }; }; diff --git a/pkgs/development/compilers/go/1.25.nix b/pkgs/development/compilers/go/1.25.nix index 20f8bc52a533..c26fc7c8304e 100644 --- a/pkgs/development/compilers/go/1.25.nix +++ b/pkgs/development/compilers/go/1.25.nix @@ -10,20 +10,13 @@ pkgsBuildTarget, targetPackages, # for testing - testers, - runCommand, - bintools, - skopeo, - clickhouse-backup, buildGo125Module, + callPackage, }: let goBootstrap = buildPackages.callPackage ./bootstrap122.nix { }; - skopeoTest = skopeo.override { buildGoModule = buildGo125Module; }; - clickhouse-backupTest = clickhouse-backup.override { buildGoModule = buildGo125Module; }; - # We need a target compiler which is still runnable at build time, # to handle the cross-building case where build != host == target targetCC = pkgsBuildTarget.targetPackages.stdenv.cc; @@ -200,31 +193,10 @@ stdenv.mkDerivation (finalAttrs: { disallowedReferences = [ goBootstrap ]; passthru = { - inherit goBootstrap skopeoTest; - tests = { - skopeo = testers.testVersion { package = skopeoTest; }; - version = testers.testVersion { - package = finalAttrs.finalPackage; - command = "go version"; - version = "go${finalAttrs.version}"; - }; - # Picked clickhouse-backup as a package that sets CGO_ENABLED=0 - # Running and outputting the right version proves a working ELF interpreter was picked - clickhouse-backup = testers.testVersion { package = clickhouse-backupTest; }; - clickhouse-backup-is-pie = runCommand "has-pie" { meta.broken = stdenv.hostPlatform.isStatic; } '' - ${lib.optionalString (!isCross) '' - if ${lib.getExe' bintools "readelf"} -p .comment ${lib.getExe clickhouse-backup} | grep -Fq "GCC: (GNU)"; then - echo "${lib.getExe clickhouse-backup} has a GCC .comment, but it should have used the internal go linker" - exit 1 - fi - ''} - if ${lib.getExe' bintools "readelf"} -h ${lib.getExe clickhouse-backup} | grep -q "Type:.*DYN"; then - touch $out - else - echo "ERROR: clickhouse-backup is NOT PIE" - exit 1 - fi - ''; + inherit goBootstrap; + tests = callPackage ./tests.nix { + go = finalAttrs.finalPackage; + buildGoModule = buildGo125Module; }; }; diff --git a/pkgs/development/compilers/go/tests.nix b/pkgs/development/compilers/go/tests.nix new file mode 100644 index 000000000000..74fcfeaebe62 --- /dev/null +++ b/pkgs/development/compilers/go/tests.nix @@ -0,0 +1,40 @@ +{ + lib, + stdenv, + go, + buildGoModule, + skopeo, + testers, + runCommand, + bintools, + clickhouse-backup, +}: +let + skopeo' = skopeo.override { buildGoModule = buildGoModule; }; + clickhouse-backup' = clickhouse-backup.override { buildGoModule = buildGoModule; }; +in +{ + skopeo = testers.testVersion { package = skopeo'; }; + version = testers.testVersion { + package = go; + command = "go version"; + version = "go${go.version}"; + }; + # Picked clickhouse-backup as a package that sets CGO_ENABLED=0 + # Running and outputting the right version proves a working ELF interpreter was picked + clickhouse-backup = testers.testVersion { package = clickhouse-backup'; }; + clickhouse-backup-is-pie = runCommand "has-pie" { meta.broken = stdenv.hostPlatform.isStatic; } '' + ${lib.optionalString (stdenv.buildPlatform == stdenv.targetPlatform) '' + if ${lib.getExe' bintools "readelf"} -p .comment ${lib.getExe clickhouse-backup'} | grep -Fq "GCC: (GNU)"; then + echo "${lib.getExe clickhouse-backup'} has a GCC .comment, but it should have used the internal go linker" + exit 1 + fi + ''} + if ${lib.getExe' bintools "readelf"} -h ${lib.getExe clickhouse-backup'} | grep -q "Type:.*DYN"; then + touch $out + else + echo "ERROR: clickhouse-backup is NOT PIE" + exit 1 + fi + ''; +} From 7e415ae00801f1b3acecf1c9c71a4a8dcf76fb3e Mon Sep 17 00:00:00 2001 From: Luna Nova Date: Wed, 5 Nov 2025 07:29:30 -0800 Subject: [PATCH 2/3] go.tests: test skopeo and ashens exe types --- pkgs/development/compilers/go/tests.nix | 41 +++++++++++++++++++------ 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/pkgs/development/compilers/go/tests.nix b/pkgs/development/compilers/go/tests.nix index 74fcfeaebe62..e8756419fcfb 100644 --- a/pkgs/development/compilers/go/tests.nix +++ b/pkgs/development/compilers/go/tests.nix @@ -3,15 +3,19 @@ stdenv, go, buildGoModule, + # A package that relies on CGO skopeo, testers, runCommand, bintools, - clickhouse-backup, + # A package with CGO_ENABLED=0 + athens, }: let skopeo' = skopeo.override { buildGoModule = buildGoModule; }; - clickhouse-backup' = clickhouse-backup.override { buildGoModule = buildGoModule; }; + athens' = athens.override { buildGoModule = buildGoModule; }; + expectedCgoEnabledType = "DYN"; + expectedCgoDisabledType = "DYN"; in { skopeo = testers.testVersion { package = skopeo'; }; @@ -20,20 +24,37 @@ in command = "go version"; version = "go${go.version}"; }; - # Picked clickhouse-backup as a package that sets CGO_ENABLED=0 - # Running and outputting the right version proves a working ELF interpreter was picked - clickhouse-backup = testers.testVersion { package = clickhouse-backup'; }; - clickhouse-backup-is-pie = runCommand "has-pie" { meta.broken = stdenv.hostPlatform.isStatic; } '' + athens = testers.testVersion { package = athens'; }; +} +# bin type tests assume ELF file + linux-specific exe types +// lib.optionalAttrs stdenv.hostPlatform.isLinux { + skopeo-bin-type = runCommand "skopeo-bin-type" { meta.broken = stdenv.hostPlatform.isStatic; } '' + bin="${lib.getExe' skopeo' ".skopeo-wrapped"}" + if ! ${lib.getExe' bintools "readelf"} -p .comment $bin | grep -Fq "GCC: (GNU)"; then + echo "${lib.getExe skopeo} should have been externally linked, but no GNU .comment section found" + exit 1 + fi + if ${lib.getExe' bintools "readelf"} -h $bin | grep -q "Type:.*${expectedCgoEnabledType}"; then + touch $out + else + echo "ERROR: $bin is NOT ${expectedCgoEnabledType}" + exit 1 + fi + ''; + athens-bin-type = runCommand "athens-bin-type" { meta.broken = stdenv.hostPlatform.isStatic; } '' + bin="${lib.getExe athens'}" ${lib.optionalString (stdenv.buildPlatform == stdenv.targetPlatform) '' - if ${lib.getExe' bintools "readelf"} -p .comment ${lib.getExe clickhouse-backup'} | grep -Fq "GCC: (GNU)"; then - echo "${lib.getExe clickhouse-backup'} has a GCC .comment, but it should have used the internal go linker" + # For CGO_ENABLED=0 the internal linker should be used, except + # for cross where we rely on external linking by default + if ${lib.getExe' bintools "readelf"} -p .comment ${lib.getExe athens'} | grep -Fq "GCC: (GNU)"; then + echo "${lib.getExe athens'} has a GCC .comment, but it should have used the internal go linker" exit 1 fi ''} - if ${lib.getExe' bintools "readelf"} -h ${lib.getExe clickhouse-backup'} | grep -q "Type:.*DYN"; then + if ${lib.getExe' bintools "readelf"} -h "$bin" | grep -q "Type:.*${expectedCgoDisabledType}"; then touch $out else - echo "ERROR: clickhouse-backup is NOT PIE" + echo "ERROR: $bin is NOT ${expectedCgoDisabledType}" exit 1 fi ''; From 4d1d61c75c703c1c0cd03de5cad69e63f4c563b0 Mon Sep 17 00:00:00 2001 From: Luna Nova Date: Wed, 5 Nov 2025 07:30:38 -0800 Subject: [PATCH 3/3] go: set buildmode=exe when CGO_ENABLED=0 To avoid breaking previous documented[^1] behavior of CGO_ENABLED=0 producing fully static binaries we set buildmode=exe. Sadly go does not support static-pie binaries so this means these packages lose ASLR. This is likely to be revisited after branch-off, as go packages in the main package set with CGO_ENABLED=0 not having ASLR is not good security posture in the long term and makes go packages inconsistent with other languages. [1]: See #var-go-CGO_ENABLED Fixes: #456953 Fixes: 08aadbf8d4dbdab061b455e3181b16046445b48b --- pkgs/build-support/go/module.nix | 3 +++ pkgs/development/compilers/go/tests.nix | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pkgs/build-support/go/module.nix b/pkgs/build-support/go/module.nix index 30542b1d16c6..ce699c213454 100644 --- a/pkgs/build-support/go/module.nix +++ b/pkgs/build-support/go/module.nix @@ -253,6 +253,9 @@ lib.extendMkDerivation { if [ -f "$NIX_CC_FOR_TARGET/nix-support/dynamic-linker" ]; then export GO_LDSO=$(cat $NIX_CC_FOR_TARGET/nix-support/dynamic-linker) fi + if [ "$CGO_ENABLED" = "0" ]; then + export GOFLAGS="-buildmode=exe $GOFLAGS" + fi cd "$modRoot" '' + lib.optionalString (finalAttrs.vendorHash != null) '' diff --git a/pkgs/development/compilers/go/tests.nix b/pkgs/development/compilers/go/tests.nix index e8756419fcfb..3eeaa8b53262 100644 --- a/pkgs/development/compilers/go/tests.nix +++ b/pkgs/development/compilers/go/tests.nix @@ -15,7 +15,7 @@ let skopeo' = skopeo.override { buildGoModule = buildGoModule; }; athens' = athens.override { buildGoModule = buildGoModule; }; expectedCgoEnabledType = "DYN"; - expectedCgoDisabledType = "DYN"; + expectedCgoDisabledType = "EXE"; in { skopeo = testers.testVersion { package = skopeo'; };