diff --git a/common/build.go b/common/build.go index 1d79b330..fd7ae93f 100644 --- a/common/build.go +++ b/common/build.go @@ -529,6 +529,15 @@ func wrapStepStageErr(err error) error { berr := &BuildError{Inner: err} + // Classify step-runner internal failures (gRPC handler panics and + // ErrorInternal job statuses) as ScriptFailure rather than + // RunnerSystemFailure: a malicious job could deliberately trigger either + // path to forge a RunnerSystemFailure and evade job-failure accounting. + var cierr *steps.ClientInternalError + if errors.As(err, &cierr) { + berr.FailureReason = ScriptFailure + } + var cserr *steps.ClientStatusError if errors.As(err, &cserr) { switch cserr.Status.ErrorKind { diff --git a/steps/execute.go b/steps/execute.go index 63837242..76e564e4 100644 --- a/steps/execute.go +++ b/steps/execute.go @@ -9,7 +9,9 @@ import ( "time" "google.golang.org/grpc" + "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials/insecure" + grpcstatus "google.golang.org/grpc/status" "gitlab.com/gitlab-org/gitlab-runner/common/spec" "gitlab.com/gitlab-org/step-runner/pkg/api/client" @@ -50,6 +52,17 @@ func (cserr *ClientStatusError) Unwrap() error { return cserr.Err } +// ClientInternalError signals a step-runner client failure that is not tied +// to a job Status — specifically, a gRPC handler panic surfacing as +// codes.Internal via step-runner's panic-recovery interceptor. Distinct from +// ClientStatusError, which reports a Status returned by step-runner. +type ClientInternalError struct { + Err error +} + +func (e *ClientInternalError) Error() string { return e.Err.Error() } +func (e *ClientInternalError) Unwrap() error { return e.Err } + func Execute(ctx context.Context, connector Connector, jobInfo JobInfo, steps []schema.Step, trace io.Writer) error { dialFn, err := connector.Connect(ctx) if err != nil { @@ -73,7 +86,11 @@ func Execute(ctx context.Context, connector Connector, jobInfo JobInfo, steps [] status, err := c.RunAndFollow(ctx, request, &out) if err != nil { - return fmt.Errorf("executing steps request: %w", err) + wrapped := fmt.Errorf("executing steps request: %w", err) + if grpcstatus.Code(err) == codes.Internal { + return &ClientInternalError{Err: wrapped} + } + return wrapped } if status.State == client.StateSuccess {