From 6643cfbeee181ee1bd543e2b3783bb5a8c01ad13 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Tue, 12 May 2026 15:10:43 +0900 Subject: [PATCH] internal/pkg: optionally measure exec artifact Useful for verifying deterministic output without enabling network access. Signed-off-by: Ophestra --- internal/pkg/exec.go | 48 +++++++++++-------- internal/pkg/exec_test.go | 20 ++++---- .../internal/testtool/expected/sum_amd64.go | 2 +- .../internal/testtool/expected/sum_arm64.go | 2 +- .../internal/testtool/expected/sum_riscv64.go | 2 +- internal/pkg/ir_test.go | 26 +++++++++- internal/rosa/busybox.go | 2 +- internal/rosa/git.go | 2 +- internal/rosa/rosa.go | 11 ++++- 9 files changed, 77 insertions(+), 38 deletions(-) diff --git a/internal/pkg/exec.go b/internal/pkg/exec.go index b91299dd..c0ea7485 100644 --- a/internal/pkg/exec.go +++ b/internal/pkg/exec.go @@ -167,6 +167,9 @@ var _ fmt.Stringer = new(execArtifact) type execNetArtifact struct { checksum Checksum + // Whether to keep host net namespace. + hostNet bool + execArtifact } @@ -175,15 +178,24 @@ var _ KnownChecksum = new(execNetArtifact) // Checksum returns the caller-supplied checksum. func (a *execNetArtifact) Checksum() Checksum { return a.checksum } -// Kind returns the hardcoded [Kind] constant. -func (*execNetArtifact) Kind() Kind { return KindExecNet } +// Kind returns [KindExecNet], or [KindExec] if hostNet is false. +func (a *execNetArtifact) Kind() Kind { + if a == nil || a.hostNet { + return KindExecNet + } + return KindExec +} // Cure cures the [Artifact] in the container described by the caller. The // container retains host networking. func (a *execNetArtifact) Cure(f *FContext) error { - return a.cure(f, true) + return a.cure(f, a.hostNet) } +// ErrNetChecksum is panicked by [NewExec] if host net namespace is requested +// with a nil checksum. +var ErrNetChecksum = errors.New("attempting to keep net namespace without checksum") + // NewExec returns a new [Artifact] that executes the program path in a // container with specified paths bind mounted read-only in order. A private // instance of /proc and /dev is made available to the container. @@ -197,7 +209,7 @@ func (a *execNetArtifact) Cure(f *FContext) error { // regular or symlink. // // If checksum is non-nil, the resulting [Artifact] implements [KnownChecksum] -// and its container runs in the host net namespace. +// and its container optionally runs in the host net namespace. // // The container is allowed to run for the specified duration before the initial // process and all processes originating from it is terminated. A zero or @@ -211,7 +223,7 @@ func NewExec( name, arch string, checksum *Checksum, timeout time.Duration, - exclusive bool, + hostNet, exclusive bool, dir *check.Absolute, env []string, @@ -234,9 +246,12 @@ func NewExec( } a := execArtifact{name, arch, paths, dir, env, pathname, args, timeout, exclusive} if checksum == nil { + if hostNet { + panic(ErrNetChecksum) + } return &a } - return &execNetArtifact{*checksum, a} + return &execNetArtifact{*checksum, hostNet, a} } // Kind returns the hardcoded [Kind] constant. @@ -361,22 +376,17 @@ func readExecArtifact(r *IRReader, net bool) Artifact { exclusive := r.ReadUint32() != 0 checksum, ok := r.Finalise() - var checksumP *Checksum - if net { - if !ok { - panic(ErrExpectedChecksum) - } - checksumVal := checksum.Value() - checksumP = &checksumVal - } else { - if ok { - panic(ErrUnexpectedChecksum) - } + if ok { + checksumP = new(checksum.Value()) + } + + if net && !ok { + panic(ErrExpectedChecksum) } return NewExec( - name, arch, checksumP, timeout, exclusive, dir, env, pathname, args, paths..., + name, arch, checksumP, timeout, net, exclusive, dir, env, pathname, args, paths..., ) } @@ -590,7 +600,7 @@ func (c *Cache) EnterExec( case *execNetArtifact: e = &f.execArtifact - hostNet = true + hostNet = f.hostNet default: return ErrNotExec diff --git a/internal/pkg/exec_test.go b/internal/pkg/exec_test.go index 6e436cae..f9d4158e 100644 --- a/internal/pkg/exec_test.go +++ b/internal/pkg/exec_test.go @@ -57,7 +57,7 @@ func TestExec(t *testing.T) { cureMany(t, c, []cureStep{ {"container", pkg.NewExec( - "exec-offline", "", nil, 0, false, + "exec-offline", "", new(wantOffline.hash()), 0, false, false, pkg.AbsWork, []string{"HAKUREI_TEST=1"}, check.MustAbs("/opt/bin/testtool"), @@ -80,7 +80,7 @@ func TestExec(t *testing.T) { ), ignorePathname, wantOffline, nil}, {"error passthrough", pkg.NewExec( - "", "", nil, 0, true, + "", "", nil, 0, false, true, pkg.AbsWork, []string{"HAKUREI_TEST=1"}, check.MustAbs("/opt/bin/testtool"), @@ -103,7 +103,7 @@ func TestExec(t *testing.T) { }}, {"invalid paths", pkg.NewExec( - "", "", nil, 0, false, + "", "", nil, 0, false, false, pkg.AbsWork, []string{"HAKUREI_TEST=1"}, check.MustAbs("/opt/bin/testtool"), @@ -116,7 +116,7 @@ func TestExec(t *testing.T) { // check init failure passthrough var exitError *exec.ExitError if _, _, err := c.Cure(pkg.NewExec( - "", "", nil, 0, false, + "", "", nil, 0, false, false, pkg.AbsWork, nil, check.MustAbs("/opt/bin/testtool"), @@ -157,7 +157,7 @@ func TestExec(t *testing.T) { } cureMany(t, c, []cureStep{ {"container", pkg.NewExec( - "exec-net", "", new(wantNet.hash()), 0, false, + "exec-net", "", new(wantNet.hash()), 0, true, false, pkg.AbsWork, []string{"HAKUREI_TEST=1"}, check.MustAbs("/opt/bin/testtool"), @@ -206,7 +206,7 @@ func TestExec(t *testing.T) { cureMany(t, c, []cureStep{ {"container", pkg.NewExec( - "exec-overlay-root", "", nil, 0, false, + "exec-overlay-root", "", nil, 0, false, false, pkg.AbsWork, []string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"}, check.MustAbs("/opt/bin/testtool"), @@ -247,7 +247,7 @@ func TestExec(t *testing.T) { cureMany(t, c, []cureStep{ {"container", pkg.NewExec( - "exec-overlay-work", "", nil, 0, false, + "exec-overlay-work", "", nil, 0, false, false, pkg.AbsWork, []string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"}, check.MustAbs("/work/bin/testtool"), @@ -293,7 +293,7 @@ func TestExec(t *testing.T) { cureMany(t, c, []cureStep{ {"container", pkg.NewExec( - "exec-multiple-layers", "", nil, 0, false, + "exec-multiple-layers", "", nil, 0, false, false, pkg.AbsWork, []string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"}, check.MustAbs("/opt/bin/testtool"), @@ -366,7 +366,7 @@ func TestExec(t *testing.T) { cureMany(t, c, []cureStep{ {"container", pkg.NewExec( - "exec-layer-promotion", "", nil, 0, true, + "exec-layer-promotion", "", nil, 0, false, true, pkg.AbsWork, []string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"}, check.MustAbs("/opt/bin/testtool"), @@ -416,7 +416,7 @@ func TestExec(t *testing.T) { cureMany(t, c, []cureStep{ {"container", pkg.NewExec( - "exec-binfmt", "cafe", nil, 0, true, + "exec-binfmt", "cafe", nil, 0, false, true, pkg.AbsWork, []string{"HAKUREI_TEST=1", "HAKUREI_BINFMT=1"}, check.MustAbs("/opt/bin/sample"), diff --git a/internal/pkg/internal/testtool/expected/sum_amd64.go b/internal/pkg/internal/testtool/expected/sum_amd64.go index 7ac5d2d5..b062f601 100644 --- a/internal/pkg/internal/testtool/expected/sum_amd64.go +++ b/internal/pkg/internal/testtool/expected/sum_amd64.go @@ -1,7 +1,7 @@ package expected const ( - Offline = "oe7Uv1u5BwxcuX3HLQzZRg1Q5oetJo6jWiKGMOeqLiqBkaVgyKzvx82N81_IzUAz" + Offline = "q5ktDTq0miP-VvB2blxqXQeaRXCUWgP_KbC18KNtUDtyoaI_h5mHmGuPMArVEBDs" OvlRoot = "NacZGXwuRkTvcHaG08a22ujJ8qCWN0RSoFlRSR5FSt0ZcBbJ28FRvkYsHEtX7G8i" Layers = "WBJDrATtX6rIE5yAu8ePX3WmDF0Tt9kFiue0m3cRnyRoVx1my8a67fh3CAW486oP" Net = "CmYtj2sNB3LHtqiDuck_Lz3MjLLIiwyP8N4NDitQ1Icvv__LVP9p8tm-sHeQaKKp" diff --git a/internal/pkg/internal/testtool/expected/sum_arm64.go b/internal/pkg/internal/testtool/expected/sum_arm64.go index 4f6216ea..ad02f493 100644 --- a/internal/pkg/internal/testtool/expected/sum_arm64.go +++ b/internal/pkg/internal/testtool/expected/sum_arm64.go @@ -1,7 +1,7 @@ package expected const ( - Offline = "K88Nrt1Mt6NZTdAjdq8_Zq2_Jv2mZYyNOfJzkjhTdxiV9G8FY1NtKNh21Kj-H3CO" + Offline = "WapqyoPxbWSnq07dWHt71mHaJXq99pAjJfFlELlJljSiZMhTFqqlzU1_mN86shSj" OvlRoot = "V9anFOiRvjGfAeBhLl14AL8TKdWZyD0WTPYe4fS9mOBw8iW5Lmarvt6TG6MV8uWm" Layers = "tKx7JNRoSBdK_7MdzI-nwTNV2wmiPzwYdcd17oLmXKL_iLmUzUiA79qTqdrTasrv" Net = "aXyDLzBCJ9XltXZIfetEVsEkrqHfcXuD5XE_FcUnYbN3emwL55N6P8LlHzNfGnM5" diff --git a/internal/pkg/internal/testtool/expected/sum_riscv64.go b/internal/pkg/internal/testtool/expected/sum_riscv64.go index 9fd8102a..adc843c0 100644 --- a/internal/pkg/internal/testtool/expected/sum_riscv64.go +++ b/internal/pkg/internal/testtool/expected/sum_riscv64.go @@ -1,7 +1,7 @@ package expected const ( - Offline = "9pGgev_rDp5fUxMjgM-Z67p_IZYYAVv-cwhgS8XUtJeMWhsaf_YycQPfYLQFSfWk" + Offline = "Z6yXE5gOJScL3srmnVMWgCXccDiUNZ5snSrf6RkXuU1_U0rX_kGVwsfHUgNG_awd" OvlRoot = "zYXJHFRLuxvUhuisZEXgGgVvdQd6piMfp5jmtT6jdVjvC2gICXquOq-UTwlrSD5I" Layers = "_F8EDazHbcLeT0sVSQXRN_kn9IjduqJcDYgzXpsT-hpKU4EBcZ0PISN2zchpqMbm" Net = "CA_FAaSIYJgapBEHV40doxpH23PdUEy_6s1TZc7wfSPN0XYqwGpMceXXDSabGveO" diff --git a/internal/pkg/ir_test.go b/internal/pkg/ir_test.go index ad895471..9a465e55 100644 --- a/internal/pkg/ir_test.go +++ b/internal/pkg/ir_test.go @@ -39,7 +39,7 @@ func TestIRRoundtrip(t *testing.T) { )}, {"exec offline", pkg.NewExec( - "exec-offline", "", nil, 0, false, + "exec-offline", "", nil, 0, false, false, pkg.AbsWork, []string{"HAKUREI_TEST=1"}, check.MustAbs("/opt/bin/testtool"), @@ -61,7 +61,7 @@ func TestIRRoundtrip(t *testing.T) { {"exec net", pkg.NewExec( "exec-net", "", (*pkg.Checksum)(bytes.Repeat([]byte{0xfc}, len(pkg.Checksum{}))), - 0, false, + 0, false, false, pkg.AbsWork, []string{"HAKUREI_TEST=1"}, check.MustAbs("/opt/bin/testtool"), @@ -80,6 +80,28 @@ func TestIRRoundtrip(t *testing.T) { )), )}, + {"exec measured", pkg.NewExec( + "exec-measured", "", + (*pkg.Checksum)(bytes.Repeat([]byte{0xfd}, len(pkg.Checksum{}))), + 0, false, false, + pkg.AbsWork, + []string{"HAKUREI_TEST=1"}, + check.MustAbs("/opt/bin/testtool"), + []string{"testtool", "measured"}, + + pkg.MustPath("/file", false, pkg.NewFile("file", []byte( + "stub file", + ))), pkg.MustPath("/.hakurei", false, pkg.NewHTTPGetTar( + nil, "file:///hakurei.tar", + pkg.Checksum(bytes.Repeat([]byte{0xfd}, len(pkg.Checksum{}))), + pkg.TarUncompressed, + )), pkg.MustPath("/opt", false, pkg.NewHTTPGetTar( + nil, "file:///testtool.tar.gz", + pkg.Checksum(bytes.Repeat([]byte{0xfd}, len(pkg.Checksum{}))), + pkg.TarGzip, + )), + )}, + {"file anonymous", pkg.NewFile("", []byte{0})}, {"file", pkg.NewFile("stub", []byte("stub"))}, } diff --git a/internal/rosa/busybox.go b/internal/rosa/busybox.go index fcfe1648..9a78d4d2 100644 --- a/internal/rosa/busybox.go +++ b/internal/rosa/busybox.go @@ -104,7 +104,7 @@ func newBusyboxBin() pkg.Artifact { } return pkg.NewExec( - "busybox-bin-"+version, arch, nil, pkg.ExecTimeoutMax, false, + "busybox-bin-"+version, arch, nil, pkg.ExecTimeoutMax, false, false, fhs.AbsRoot, []string{ "PATH=/system/bin", }, diff --git a/internal/rosa/git.go b/internal/rosa/git.go index 8d0ad141..5a207288 100644 --- a/internal/rosa/git.go +++ b/internal/rosa/git.go @@ -111,7 +111,7 @@ func (t Toolchain) NewViaGit( return t.New(strings.TrimSuffix( path.Base(url), ".git", - )+"-src-"+path.Base(rev), 0, t.AppendPresets(nil, + )+"-src-"+path.Base(rev), THostNet, t.AppendPresets(nil, NSSCACert, Git, ), &checksum, nil, ` diff --git a/internal/rosa/rosa.go b/internal/rosa/rosa.go index 6bf51fcb..740746cf 100644 --- a/internal/rosa/rosa.go +++ b/internal/rosa/rosa.go @@ -220,6 +220,8 @@ const ( TEarly // TNoToolchain excludes the LLVM toolchain. TNoToolchain + // THostNet arranges for a [pkg.KindExecNet] to be created. + THostNet ) var ( @@ -325,7 +327,9 @@ mkdir -vp /work/system/bin } return pkg.NewExec( - name, arch, knownChecksum, pkg.ExecTimeoutMax, flag&TExclusive != 0, + name, arch, knownChecksum, pkg.ExecTimeoutMax, + flag&THostNet != 0, + flag&TExclusive != 0, fhs.AbsRoot, env, AbsSystem.Append("bin", "sh"), []string{"sh", absCureScript.String()}, @@ -408,6 +412,9 @@ type Helper interface { // PackageAttr holds build-system-agnostic attributes. type PackageAttr struct { + // Measure output if populated. Required by [THostNet]. + KnownChecksum *pkg.Checksum + // Mount the source tree writable. Writable bool // Do not pass through [Toolchain.NewPatchedSource]. @@ -545,7 +552,7 @@ cd '/usr/src/` + name + `/' name+"-"+version, attr.Flag, extraRes, - nil, + attr.KnownChecksum, attr.Env, scriptEarly+helper.script(name), slices.Concat(attr.Paths, []pkg.ExecPath{