Skip to content

Commit 56f05ed

Browse files
committed
(feat): Default net.ipv4.ip_unprivileged_port_start to 0 inside containers
Signed-off-by: Yash Kukrecha <[email protected]>
1 parent 3696771 commit 56f05ed

File tree

2 files changed

+58
-0
lines changed

2 files changed

+58
-0
lines changed

cmd/nerdctl/container/container_run_runtime_linux_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,31 @@ func TestRunSysctl(t *testing.T) {
2727
base := testutil.NewBase(t)
2828
base.Cmd("run", "--rm", "--sysctl", "net.ipv4.ip_forward=1", testutil.AlpineImage, "cat", "/proc/sys/net/ipv4/ip_forward").AssertOutExactly("1\n")
2929
}
30+
31+
func TestRunSysctl_DefaultUnprivilegedPortStart(t *testing.T) {
32+
t.Parallel()
33+
base := testutil.NewBase(t)
34+
35+
// No --sysctl flags, default network mode (non-host).
36+
// We expect net.ipv4.ip_unprivileged_port_start=0 inside the container,
37+
// because withDefaultUnprivilegedPortSysctl should apply the default.
38+
base.Cmd(
39+
"run", "--rm",
40+
testutil.AlpineImage,
41+
"cat", "/proc/sys/net/ipv4/ip_unprivileged_port_start",
42+
).AssertOutExactly("0\n")
43+
}
44+
45+
func TestRunSysctl_UnprivilegedPortStartOverride(t *testing.T) {
46+
t.Parallel()
47+
base := testutil.NewBase(t)
48+
49+
// User explicitly sets net.ipv4.ip_unprivileged_port_start=1000.
50+
// We must NOT override this; the container should see "1000".
51+
base.Cmd(
52+
"run", "--rm",
53+
"--sysctl", "net.ipv4.ip_unprivileged_port_start=1000",
54+
testutil.AlpineImage,
55+
"cat", "/proc/sys/net/ipv4/ip_unprivileged_port_start",
56+
).AssertOutExactly("1000\n")
57+
}

pkg/cmd/container/create.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"path/filepath"
2828
"reflect"
2929
"runtime"
30+
"slices"
3031
"strconv"
3132
"strings"
3233

@@ -330,6 +331,10 @@ func Create(ctx context.Context, client *containerd.Client, args []string, netMa
330331
}
331332
opts = append(opts, umaskOpts...)
332333

334+
if !isHostNetwork(netLabelOpts) {
335+
opts = append(opts, withDefaultUnprivilegedPortSysctl())
336+
}
337+
333338
rtCOpts, err := generateRuntimeCOpts(options.GOptions.CgroupManager, options.Runtime)
334339
if err != nil {
335340
return nil, generateRemoveOrphanedDirsFunc(ctx, id, dataStore, internalLabels), err
@@ -563,6 +568,31 @@ func GenerateLogURI(dataStore string) (*url.URL, error) {
563568
return cio.LogURIGenerator("binary", selfExe, args)
564569
}
565570

571+
func isHostNetwork(netOpts types.NetworkOptions) bool {
572+
return slices.Contains(netOpts.NetworkSlice, "host")
573+
}
574+
575+
// withDefaultUnprivilegedPortSysctl ensures that containers can bind to
576+
// privileged ports (<1024) without requiring CAP_NET_BIND_SERVICE inside
577+
// the container by defaulting net.ipv4.ip_unprivileged_port_start to 0
578+
// in the container's network namespace.
579+
func withDefaultUnprivilegedPortSysctl() oci.SpecOpts {
580+
const key = "net.ipv4.ip_unprivileged_port_start"
581+
return func(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error {
582+
if s.Linux == nil {
583+
s.Linux = &specs.Linux{}
584+
}
585+
if s.Linux.Sysctl == nil {
586+
s.Linux.Sysctl = make(map[string]string)
587+
}
588+
589+
if _, exists := s.Linux.Sysctl[key]; !exists {
590+
s.Linux.Sysctl[key] = "0"
591+
}
592+
return nil
593+
}
594+
}
595+
566596
func withNerdctlOCIHook(cmd string, args []string) (oci.SpecOpts, error) {
567597
if rootlessutil.IsRootless() {
568598
detachedNetNS, err := rootlessutil.DetachedNetNS()

0 commit comments

Comments
 (0)