Skip to content

Commit 44244c3

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 44244c3

2 files changed

Lines changed: 89 additions & 11 deletions

File tree

common/libnetwork/internal/rootlessnetns/netns_linux.go

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

48+
// ErrEmptyPIDFile is returned when the PID file is empty.
49+
var ErrEmptyPIDFile = errors.New("PID file is empty")
50+
4851
type Netns struct {
4952
// dir used for the rootless netns
5053
dir string
5154

5255
// config contains containers.conf options.
5356
config *config.Config
5457

55-
// info contain information about ip addresses used in the netns.
58+
// info contains information about ip addresses used in the netns.
5659
// A caller can get this info via Info().
5760
info *types.RootlessNetnsInfo
5861
}
@@ -172,7 +175,7 @@ func (n *Netns) getOrCreateNetns() (netns.NetNS, bool, error) {
172175
func (n *Netns) cleanup() error {
173176
if err := fileutils.Exists(n.dir); err != nil {
174177
if errors.Is(err, fs.ErrNotExist) {
175-
// dir does not exists no need for cleanup
178+
// dir does not exist no need for cleanup
176179
return nil
177180
}
178181
return err
@@ -310,18 +313,26 @@ func (n *Netns) setupSlirp4netns(nsPath string) error {
310313

311314
func (n *Netns) cleanupRootlessNetns() error {
312315
pidFile := n.getPath(rootlessNetNsConnPidFile)
316+
313317
pid, err := readPidFile(pidFile)
314-
// do not hard error if the file dos not exists, cleanup should be idempotent
318+
// do not hard error if the file does not exist, cleanup should be idempotent
315319
if errors.Is(err, fs.ErrNotExist) {
316-
logrus.Debugf("Rootless netns conn pid file does not exists %s", pidFile)
320+
logrus.Debugf("Rootless netns conn pid file does not exist %s", pidFile)
317321
return nil
318322
}
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-
}
323+
// if the pid file is empty, pasta failed to start so there is nothing to clean up
324+
if errors.Is(err, ErrEmptyPIDFile) {
325+
logrus.Debugf("Rootless netns conn pid file is empty, nothing to clean up")
326+
return nil
327+
}
328+
if err != nil {
329+
logrus.Warnf("Rootless netns conn pid file is invalid: %v", err)
330+
return nil
331+
}
332+
// kill the slirp/pasta process so we do not leak it
333+
err = unix.Kill(pid, unix.SIGTERM)
334+
if err == unix.ESRCH {
335+
err = nil
325336
}
326337
return err
327338
}
@@ -652,7 +663,11 @@ func readPidFile(path string) (int, error) {
652663
if err != nil {
653664
return 0, err
654665
}
655-
return strconv.Atoi(strings.TrimSpace(string(b)))
666+
s := strings.TrimSpace(string(b))
667+
if s == "" {
668+
return 0, ErrEmptyPIDFile
669+
}
670+
return strconv.Atoi(s)
656671
}
657672

658673
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)