Skip to content

fix(server): skip auto default profile for rem pipelines and guard nil backend in ToProfile - #101

Open
dust-life wants to merge 1 commit into
chainreactors:devfrom
dust-life:fix/rem-pipeline-default-profile-crash
Open

dust-life wants to merge 1 commit into
chainreactors:devfrom
dust-life:fix/rem-pipeline-default-profile-crash

Conversation

@dust-life

Copy link
Copy Markdown

Summary

Registering a REM pipeline through ListenerRPC.RegisterPipeline crashes the
entire teamserver process with a nil-pointer panic. There is no gRPC recovery
middleware on this path, so the panic is fatal: the teamserver dies and every
connected listener/operator drops.

Reproduction

  1. Start a teamserver and connect a listener.

  2. Through the listener RPC, register a REM pipeline:

    listenerClient.RegisterPipeline(ctx, &clientpb.Pipeline{
        Name:       "rem-1",
        ListenerId: "my-listener",
        Type:       consts.RemPipeline,
        Body: &clientpb.Pipeline_Rem{
            Rem: &clientpb.REM{Name: "rem-1", Link: "tcp://127.0.0.1:12345"},
        },
    })
  3. The teamserver process panics:

    panic: runtime error: invalid memory address or nil pointer dereference
    [signal SIGSEGV: segmentation violation ...]
    
    github.com/chainreactors/malice-network/server/internal/db/models.(*Pipeline).DefaultRemProfile(...)
        server/internal/db/models/pipeline.go:483
    github.com/chainreactors/malice-network/server/internal/db/models.(*Pipeline).ToProfile(...)
        server/internal/db/models/pipeline.go:404
    github.com/chainreactors/malice-network/server/internal/db.NewProfile(...)
        server/internal/db/payload_helper.go:204
    ...registerDefaultProfileForPipeline
        server/rpc/rpc-pipeline.go:137
    ...(*Server).RegisterPipeline
        server/rpc/rpc-pipeline.go:114
    

The same crash is reachable one level down by creating a profile bound to a rem
pipeline without params.rem (db.NewProfile → ToProfile(nil)).

Root cause

RegisterPipeline unconditionally auto-creates a <name>_default profile via
registerDefaultProfileForPipeline → db.NewProfile. In NewProfile, when the
profile params carry no REMPipeline entry, the profile config is derived with
pipelineModel.ToProfile(nil) (payload_helper.go:204). For a rem pipeline this
lands in DefaultRemProfile(nil) (models/pipeline.go:476):

backendPB := backend.ToProtobuf()              // nil receiver -> returns nil
target.Address = backendPB.Ip + ":" + ...      // models/pipeline.go:483 -> panic

(*Pipeline).ToProtobuf is nil-safe and returns nil, but the very next line
dereferences backendPB.Ip, so a nil backend is a guaranteed nil-pointer panic.

An automatic default profile fundamentally cannot work for rem pipelines:
DefaultRemProfile needs the backend tcp pipeline to compute the target
address/pulse, and the register path has no way to know it. The supported flow
is the explicit one: the operator creates the profile with
params.rem: "<rem pipeline>" while binding it to the backend tcp pipeline —
NewProfile already has that branch (payload_helper.go:193-199).

Fix

Two small, complementary changes:

  1. server/rpc/rpc-pipeline.go — registerDefaultProfileForPipeline:
    skip the automatic <name>_default profile for rem pipelines
    (req.Type == consts.RemPipeline || req.GetRem() != nil). tcp/http pipelines
    are unaffected — their default profiles derive from the pipeline itself.

  2. server/internal/db/models/pipeline.go — (*Pipeline).ToProfile:
    for consts.RemPipeline with a nil backend, return an error
    ("rem default profile requires the backend tcp pipeline") instead of falling
    into the nil dereference. ToProfile already returns
    (ProfileConfig, error), so no signature change; callers
    (db.NewProfile) propagate the error.

Tests

Regression tests at the server/rpc layer (same pattern as the existing
pipeline tests — in-memory DB via the test env, core.Listeners.Add for the
listener runtime):

  • TestRegisterRemPipelineSkipsDefaultProfile — registering a rem pipeline no
    longer panics and no <name>_default profile is created.
  • TestRegisterPipelineDefaultProfileUnchanged — tcp and http pipelines still
    get their automatic <name>_default profile, and the generated implant yaml
    targets the pipeline address.
  • TestNewProfileWithREMPipelineParamBuildsRemProfile — the explicit path
    (params.rem) still builds a valid rem profile: implant yaml carries the rem
    link on the target, the backend tcp address, enable_3rd + 3rd_modules: [rem], and the pulse pointing at the backend.
  • TestNewProfileForRemPipelineWithoutBackendReturnsError — profiling a rem
    pipeline without params.rem returns the new backend-required error instead
    of panicking.

go vet ./server/..., CGO_ENABLED=0 go build ./server/..., and
go test ./server/... -count=1 -timeout 600s all pass.

Compatibility

No API or schema change. The only behavioral difference is that rem pipelines no
longer receive an automatic <name>_default profile — such a profile was never
usable anyway, because its creation crashed the server before this fix. Explicit
rem profiles (params.rem) are unchanged.

…il backend

RegisterPipeline unconditionally auto-creates a <name>_default profile
(registerDefaultProfileForPipeline -> db.NewProfile). With no
params.REMPipeline on the profile, NewProfile derives the config via
pipelineModel.ToProfile(nil); for a rem pipeline that lands in
DefaultRemProfile(nil), whose nil-safe ToProtobuf returns nil before
backendPB.Ip dereferences it — a nil-pointer panic with no gRPC
recovery on this path, so the whole teamserver process dies.

An automatic default profile can never work for rem pipelines:
DefaultRemProfile needs the backend tcp pipeline, which the register
path cannot know. registerDefaultProfileForPipeline now skips rem
pipelines (tcp/http behavior unchanged), and ToProfile returns an
error for a rem pipeline with a nil backend instead of panicking, so
the db.NewProfile callers surface a clean error. Operators keep
creating rem profiles explicitly with params.REMPipeline, the existing
NewProfile branch.

Regression tests: registering a rem pipeline no longer panics and
creates no default profile; tcp/http still get theirs with the right
target; the explicit params.rem path yields a valid rem implant yaml;
profiling a rem pipeline without the param errors instead of crashing.
The same crash exists upstream; an independent fix is drafted.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant