Source file src/runtime/signal_linux_test.go

     1  // Copyright 2026 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package runtime_test
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"errors"
    11  	"internal/testenv"
    12  	"io"
    13  	"os"
    14  	"os/exec"
    15  	"strings"
    16  	"syscall"
    17  	"testing"
    18  )
    19  
    20  // TestSignalPid1 verifies that a Go program running as PID 1 with no
    21  // SIGTERM handler provides a sane exit code upon receiving SIGTERM.
    22  //
    23  // The test is Linux-specific because it uses CLONE_NEWPID to run as PID 1.
    24  func TestSignalPid1(t *testing.T) {
    25  	t.Parallel()
    26  
    27  	exe, err := buildTestProg(t, "testprog")
    28  	if err != nil {
    29  		t.Fatal(err)
    30  	}
    31  
    32  	cmd := testenv.Command(t, exe, "SignalPid1")
    33  	cmd.SysProcAttr = &syscall.SysProcAttr{
    34  		Cloneflags: syscall.CLONE_NEWPID | syscall.CLONE_NEWUSER,
    35  		UidMappings: []syscall.SysProcIDMap{
    36  			{ContainerID: 0, HostID: os.Getuid(), Size: 1},
    37  		},
    38  		GidMappings: []syscall.SysProcIDMap{
    39  			{ContainerID: 0, HostID: os.Getgid(), Size: 1},
    40  		},
    41  	}
    42  	stdout, err := cmd.StdoutPipe()
    43  	if err != nil {
    44  		t.Fatal(err)
    45  	}
    46  	var stderr bytes.Buffer
    47  	cmd.Stderr = &stderr
    48  	if err := cmd.Start(); err != nil {
    49  		t.Skipf("cannot create PID namespace (may require unprivileged user namespaces): %v", err)
    50  	}
    51  
    52  	waited := false
    53  	defer func() {
    54  		if !waited {
    55  			cmd.Process.Kill()
    56  			cmd.Wait()
    57  		}
    58  	}()
    59  
    60  	// Wait for child to signal readiness.
    61  	r := bufio.NewReader(stdout)
    62  	line, err := r.ReadString('\n')
    63  	if err != nil {
    64  		t.Fatalf("reading from child: %v", err)
    65  	}
    66  	if strings.TrimRight(line, "\n") != "ready" {
    67  		t.Fatalf("unexpected output from child: %q", line)
    68  	}
    69  	go io.Copy(io.Discard, r) // Drain any further output.
    70  
    71  	const (
    72  		signal      = syscall.SIGTERM
    73  		expExitCode = int(128 + signal)
    74  	)
    75  	// Send signal from outside the child PID namespace.
    76  	if err := cmd.Process.Signal(signal); err != nil {
    77  		t.Fatalf("sending signal %d (%q): %v", signal, signal, err)
    78  	}
    79  
    80  	err = cmd.Wait()
    81  	waited = true
    82  	t.Logf("child: %v", err)
    83  	if s := stderr.String(); s != "" {
    84  		t.Fatalf("child stderr: %s", s)
    85  	}
    86  	if exitErr, ok := errors.AsType[*exec.ExitError](err); ok {
    87  		if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
    88  			if ec := status.ExitStatus(); ec == expExitCode {
    89  				return // PASS.
    90  			}
    91  		}
    92  	}
    93  
    94  	t.Errorf("Want child exited with %d, got: %+v", expExitCode, err)
    95  }
    96  

View as plain text