Skip to content

Commit fca8e9e

Browse files
committed
fix(agent): match os.ErrNotExist when probing nsenter binary
probeTimeNSJoinable invokes nsenter via the absolute path /usr/bin/nsenter, so exec.Command skips LookPath and can never return exec.ErrNotFound. A missing binary surfaces as *fs.PathError wrapping ENOENT, leaving the "nsenter not found" branch dead. Match os.ErrNotExist instead so the warning actually fires. Also assert session.Exit(1) is called in TestShell_StartPtyError, the behaviour the test documents but did not verify.
1 parent af3cac9 commit fca8e9e

2 files changed

Lines changed: 8 additions & 3 deletions

File tree

agent/server/modes/host/command/command_docker.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ var statFn = os.Stat
3030
// - (false, true): definitive denial (EPERM/EACCES, i.e. *exec.ExitError
3131
// with code other than 126/127); result is safe to cache.
3232
// - (false, false): nsenter or /bin/true not found (exit 126/127 or
33-
// exec.ErrNotFound); logged as a distinct warning; must NOT be cached.
33+
// os.ErrNotExist); logged as a distinct warning; must NOT be cached.
3434
// - (false, false): context deadline exceeded or any other transient error;
3535
// must NOT be cached.
3636
func probeTimeNSJoinable() (result bool, definitive bool) {
@@ -42,8 +42,11 @@ func probeTimeNSJoinable() (result bool, definitive bool) {
4242
return true, true
4343
}
4444

45-
// exec.ErrNotFound means the nsenter binary itself was not located.
46-
if errors.Is(err, exec.ErrNotFound) {
45+
// os.ErrNotExist means the nsenter binary itself was not found at the
46+
// absolute path (/usr/bin/nsenter). When an absolute path is given,
47+
// exec.Command skips LookPath entirely, so exec.ErrNotFound is never
48+
// returned; a missing binary surfaces as an *fs.PathError wrapping ENOENT.
49+
if errors.Is(err, os.ErrNotExist) {
4750
log.WithError(err).Warn("nsenter not found; time namespace join probe skipped")
4851

4952
return false, false

agent/server/modes/host/sessioner_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,8 @@ func TestShell_StartPtyError(t *testing.T) {
193193
assert.NotNil(t, retErr, "Shell() must return a non-nil error when startPty fails")
194194
assert.True(t, strings.Contains(retErr.Error(), "pty"), "error should mention 'pty', got: %s", retErr.Error())
195195
assert.Empty(t, s.cmds, "s.cmds must be empty — the session must not have been registered")
196+
assert.Equal(t, int32(1), atomic.LoadInt32(&sess.exitCalled), "session.Exit must be called once")
197+
assert.Equal(t, int32(1), atomic.LoadInt32(&sess.exitCode), "session.Exit must be called with code 1")
196198
}
197199

198200
// TestExec_InitPtyError verifies that Exec() with sIsPty=true does NOT panic and

0 commit comments

Comments
 (0)