Skip to content

Commit ac396fc

Browse files
committed
rootlessnetns: handle empty pid file gracefully
When pasta fails to start, it can leave an empty PID file which causes a crash in readPidFile. This fix handles empty PID files gracefully by returning a specific error and ignoring it during cleanup. Fixes: containers/podman#28441 Signed-off-by: Alessio Attilio <attilio.alessio@protonmail.com>
1 parent 638a7b8 commit ac396fc

File tree

2 files changed

+94
-14
lines changed

2 files changed

+94
-14
lines changed

common/libnetwork/internal/rootlessnetns/netns_linux.go

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,16 @@ const (
4545
resolvConfName = "resolv.conf"
4646
)
4747

48+
var errEmptyPIDFile = errors.New("PID file is empty")
49+
4850
type Netns struct {
4951
// dir used for the rootless netns
5052
dir string
5153

5254
// config contains containers.conf options.
5355
config *config.Config
5456

55-
// info contain information about ip addresses used in the netns.
57+
// info contains information about ip addresses used in the netns.
5658
// A caller can get this info via Info().
5759
info *types.RootlessNetnsInfo
5860
}
@@ -172,7 +174,7 @@ func (n *Netns) getOrCreateNetns() (netns.NetNS, bool, error) {
172174
func (n *Netns) cleanup() error {
173175
if err := fileutils.Exists(n.dir); err != nil {
174176
if errors.Is(err, fs.ErrNotExist) {
175-
// dir does not exists no need for cleanup
177+
// dir does not exist no need for cleanup
176178
return nil
177179
}
178180
return err
@@ -310,18 +312,26 @@ func (n *Netns) setupSlirp4netns(nsPath string) error {
310312

311313
func (n *Netns) cleanupRootlessNetns() error {
312314
pidFile := n.getPath(rootlessNetNsConnPidFile)
315+
313316
pid, err := readPidFile(pidFile)
314-
// do not hard error if the file dos not exists, cleanup should be idempotent
317+
// do not hard error if the file does not exist, cleanup should be idempotent
315318
if errors.Is(err, fs.ErrNotExist) {
316-
logrus.Debugf("Rootless netns conn pid file does not exists %s", pidFile)
319+
logrus.Debugf("Rootless netns conn pid file does not exist %s", pidFile)
317320
return nil
318321
}
319-
if err == nil {
320-
// kill the slirp/pasta process so we do not leak it
321-
err = unix.Kill(pid, unix.SIGTERM)
322-
if err == unix.ESRCH {
323-
err = nil
324-
}
322+
// if the pid file is empty, pasta failed to start so there is nothing to clean up
323+
if errors.Is(err, errEmptyPIDFile) {
324+
logrus.Debugf("Rootless netns conn pid file is empty, nothing to clean up")
325+
return nil
326+
}
327+
if err != nil {
328+
logrus.Warnf("Rootless netns conn pid file is invalid: %v", err)
329+
return nil
330+
}
331+
// kill the slirp/pasta process so we do not leak it
332+
err = unix.Kill(pid, unix.SIGTERM)
333+
if err == unix.ESRCH {
334+
err = nil
325335
}
326336
return err
327337
}
@@ -602,9 +612,12 @@ func (n *Netns) Run(lock *lockfile.LockFile, toRun func() error) error {
602612
}
603613

604614
if count == 0 {
605-
err = n.cleanup()
606-
if err != nil {
607-
return wrapError("cleanup", err)
615+
cleanupErr := n.cleanup()
616+
if cleanupErr != nil {
617+
if inErr != nil {
618+
return wrapError("cleanup", fmt.Errorf("%v: %w", inErr, cleanupErr))
619+
}
620+
return wrapError("cleanup", cleanupErr)
608621
}
609622
}
610623

@@ -652,7 +665,11 @@ func readPidFile(path string) (int, error) {
652665
if err != nil {
653666
return 0, err
654667
}
655-
return strconv.Atoi(strings.TrimSpace(string(b)))
668+
s := strings.TrimSpace(string(b))
669+
if s == "" {
670+
return 0, errEmptyPIDFile
671+
}
672+
return strconv.Atoi(s)
656673
}
657674

658675
func (n *Netns) serializeInfo() error {

common/libnetwork/internal/rootlessnetns/netns_linux_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,66 @@ func Test_refCount(t *testing.T) {
7575
})
7676
}
7777
}
78+
79+
func TestReadPidFile(t *testing.T) {
80+
tests := []struct {
81+
name string
82+
content string
83+
create bool
84+
wantErr error
85+
wantPid int
86+
}{
87+
{
88+
name: "valid pid",
89+
content: "1234\n",
90+
create: true,
91+
wantPid: 1234,
92+
},
93+
{
94+
name: "valid pid no newline",
95+
content: "5678",
96+
create: true,
97+
wantPid: 5678,
98+
},
99+
{
100+
name: "empty pid file",
101+
content: "",
102+
create: true,
103+
wantErr: errEmptyPIDFile,
104+
},
105+
{
106+
name: "only whitespace",
107+
content: " \n ",
108+
create: true,
109+
wantErr: errEmptyPIDFile,
110+
},
111+
{
112+
name: "non-existent file",
113+
create: false,
114+
wantErr: os.ErrNotExist,
115+
},
116+
{
117+
name: "invalid content",
118+
content: "abc",
119+
create: true,
120+
wantErr: strconv.ErrSyntax,
121+
},
122+
}
123+
for _, tt := range tests {
124+
t.Run(tt.name, func(t *testing.T) {
125+
path := filepath.Join(t.TempDir(), "pidfile")
126+
if tt.create {
127+
err := os.WriteFile(path, []byte(tt.content), 0o600)
128+
assert.NoError(t, err)
129+
}
130+
pid, err := readPidFile(path)
131+
if tt.wantErr != nil {
132+
assert.Error(t, err)
133+
assert.ErrorIs(t, err, tt.wantErr)
134+
} else {
135+
assert.NoError(t, err)
136+
assert.Equal(t, tt.wantPid, pid)
137+
}
138+
})
139+
}
140+
}

0 commit comments

Comments
 (0)