Repository navigation
Conversation
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Registering a REM pipeline through
ListenerRPC.RegisterPipelinecrashes theentire 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
Start a teamserver and connect a listener.
Through the listener RPC, register a REM pipeline:
The teamserver process panics:
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
RegisterPipelineunconditionally auto-creates a<name>_defaultprofile viaregisterDefaultProfileForPipeline→db.NewProfile. InNewProfile, when theprofile params carry no
REMPipelineentry, the profile config is derived withpipelineModel.ToProfile(nil)(payload_helper.go:204). For a rem pipeline thislands in
DefaultRemProfile(nil)(models/pipeline.go:476):(*Pipeline).ToProtobufis nil-safe and returnsnil, but the very next linedereferences
backendPB.Ip, so a nil backend is a guaranteed nil-pointer panic.An automatic default profile fundamentally cannot work for rem pipelines:
DefaultRemProfileneeds the backend tcp pipeline to compute the targetaddress/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 —NewProfilealready has that branch (payload_helper.go:193-199).Fix
Two small, complementary changes:
server/rpc/rpc-pipeline.go—registerDefaultProfileForPipeline:skip the automatic
<name>_defaultprofile for rem pipelines(
req.Type == consts.RemPipeline || req.GetRem() != nil). tcp/http pipelinesare unaffected — their default profiles derive from the pipeline itself.
server/internal/db/models/pipeline.go—(*Pipeline).ToProfile:for
consts.RemPipelinewith a nilbackend, return an error("rem default profile requires the backend tcp pipeline") instead of falling
into the nil dereference.
ToProfilealready returns(ProfileConfig, error), so no signature change; callers(
db.NewProfile) propagate the error.Tests
Regression tests at the
server/rpclayer (same pattern as the existingpipeline tests — in-memory DB via the test env,
core.Listeners.Addfor thelistener runtime):
TestRegisterRemPipelineSkipsDefaultProfile— registering a rem pipeline nolonger panics and no
<name>_defaultprofile is created.TestRegisterPipelineDefaultProfileUnchanged— tcp and http pipelines stillget their automatic
<name>_defaultprofile, and the generated implant yamltargets the pipeline address.
TestNewProfileWithREMPipelineParamBuildsRemProfile— the explicit path(
params.rem) still builds a valid rem profile: implant yaml carries the remlink on the target, the backend tcp address,
enable_3rd+3rd_modules: [rem], and the pulse pointing at the backend.TestNewProfileForRemPipelineWithoutBackendReturnsError— profiling a rempipeline without
params.remreturns the new backend-required error insteadof panicking.
go vet ./server/...,CGO_ENABLED=0 go build ./server/..., andgo test ./server/... -count=1 -timeout 600sall pass.Compatibility
No API or schema change. The only behavioral difference is that rem pipelines no
longer receive an automatic
<name>_defaultprofile — such a profile was neverusable anyway, because its creation crashed the server before this fix. Explicit
rem profiles (
params.rem) are unchanged.