Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,50 +38,12 @@ func SignalContainerd(socket string) error {

// Wrap the logic to perform the SIGHUP in a function so we can retry it on failure
retriable := func() error {
conn, err := net.Dial("unix", socket)
pid, err := getContainerdPid(socket)
if err != nil {
return fmt.Errorf("unable to dial: %v", err)
return fmt.Errorf("failed to get containerd pid: %w", err)
}
defer conn.Close()

sconn, err := conn.(*net.UnixConn).SyscallConn()
if err != nil {
return fmt.Errorf("unable to get syscall connection: %v", err)
}

err1 := sconn.Control(func(fd uintptr) {
err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_PASSCRED, 1)
})
if err1 != nil {
return fmt.Errorf("unable to issue call on socket fd: %v", err1)
}
if err != nil {
return fmt.Errorf("unable to SetsockoptInt on socket fd: %v", err)
}

_, _, err = conn.(*net.UnixConn).WriteMsgUnix([]byte(socketMessageToGetPID), nil, nil)
if err != nil {
return fmt.Errorf("unable to WriteMsgUnix on socket fd: %v", err)
}

oob := make([]byte, 1024)
_, oobn, _, _, err := conn.(*net.UnixConn).ReadMsgUnix(nil, oob)
if err != nil {
return fmt.Errorf("unable to ReadMsgUnix on socket fd: %v", err)
}

oob = oob[:oobn]
scm, err := syscall.ParseSocketControlMessage(oob)
if err != nil {
return fmt.Errorf("unable to ParseSocketControlMessage from message received on socket fd: %v", err)
}

ucred, err := syscall.ParseUnixCredentials(&scm[0])
if err != nil {
return fmt.Errorf("unable to ParseUnixCredentials from message received on socket fd: %v", err)
}

err = syscall.Kill(int(ucred.Pid), syscall.SIGHUP)
err = syscall.Kill(pid, syscall.SIGHUP)
if err != nil {
return fmt.Errorf("unable to send SIGHUP to 'containerd' process: %v", err)
}
Expand Down Expand Up @@ -111,3 +73,58 @@ func SignalContainerd(socket string) error {

return nil
}

// getContainerdPid resolves the PID of the containerd daemon from the peer
// credentials of its socket. SO_PASSCRED must be enabled before connect():
// the kernel only records credentials on data queued while it is set, and the
// server writes immediately on accept, so data queued earlier carries pid 0 —
// which kill(2) would interpret as the calling process's own process group.
func getContainerdPid(socket string) (int, error) {
dialer := net.Dialer{
Control: func(network, address string, c syscall.RawConn) error {
var serr error
if cerr := c.Control(func(fd uintptr) {
serr = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_PASSCRED, 1)
}); cerr != nil {
return fmt.Errorf("unable to issue call on socket fd: %w", cerr)
}
if serr != nil {
return fmt.Errorf("unable to SetsockoptInt on socket fd: %w", serr)
}
return nil
},
}
conn, err := dialer.Dial("unix", socket)
if err != nil {
return 0, fmt.Errorf("unable to dial: %w", err)
}
defer conn.Close()

_, _, err = conn.(*net.UnixConn).WriteMsgUnix([]byte(socketMessageToGetPID), nil, nil)
if err != nil {
return 0, fmt.Errorf("unable to WriteMsgUnix on socket fd: %w", err)
}

oob := make([]byte, 1024)
_, oobn, _, _, err := conn.(*net.UnixConn).ReadMsgUnix(nil, oob)
if err != nil {
return 0, fmt.Errorf("unable to ReadMsgUnix on socket fd: %w", err)
}

oob = oob[:oobn]
scm, err := syscall.ParseSocketControlMessage(oob)
if err != nil {
return 0, fmt.Errorf("unable to ParseSocketControlMessage from message received on socket fd: %w", err)
}

ucred, err := syscall.ParseUnixCredentials(&scm[0])
if err != nil {
return 0, fmt.Errorf("unable to ParseUnixCredentials from message received on socket fd: %w", err)
}

if ucred.Pid <= 0 {
return 0, fmt.Errorf("received invalid peer pid %d from socket credentials", ucred.Pid)
}

return int(ucred.Pid), nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
# SPDX-FileCopyrightText: Copyright (c) NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
**/

package containerd

import (
"net"
"os"
"path/filepath"
"testing"
"time"

"github.com/stretchr/testify/require"
)

// startMockServer listens on a unix socket and, like containerd's gRPC
// server, writes data immediately on accept.
func startMockServer(t *testing.T) string {
t.Helper()

socket := filepath.Join(t.TempDir(), "mock.sock")
l, err := net.Listen("unix", socket)
require.NoError(t, err)
t.Cleanup(func() { _ = l.Close() })

go func() {
for {
conn, err := l.Accept()
if err != nil {
return
}
go func(conn net.Conn) {
defer conn.Close()
_, _ = conn.Write([]byte("\x00\x00\x00\x04\x00\x00\x00\x00\x00"))
_ = conn.SetReadDeadline(time.Now().Add(5 * time.Second))
buf := make([]byte, 64)
_, _ = conn.Read(buf)
}(conn)
}
}()

return socket
}

func TestGetContainerdPid(t *testing.T) {
socket := startMockServer(t)

pid, err := getContainerdPid(socket)
require.NoError(t, err)
require.Equal(t, os.Getpid(), pid)
}

// TestGetContainerdPidNeverZero is a regression test for resolving pid 0 from
// data the server queued before SO_PASSCRED was enabled.
func TestGetContainerdPidNeverZero(t *testing.T) {
socket := startMockServer(t)

for i := range 200 {
pid, err := getContainerdPid(socket)
require.NoError(t, err, "attempt %d", i)
require.Equal(t, os.Getpid(), pid, "attempt %d", i)
}
}
Loading