From 90cba194cc4173482b83ef4ad0da03d327535b18 Mon Sep 17 00:00:00 2001 From: Indrajith <29561593+indrajithek@users.noreply.github.com> Date: Tue, 20 Oct 2026 05:17:21 +0000 Subject: [PATCH 1/4] fix(linux): pass the application command to urunit in its config The Linux kernel command line cannot carry an arbitrary argument list: an empty argument disappears, a literal `--` cuts off the rest, the list is truncated at 2048 bytes and more than 32 arguments panic the kernel. When urunit is the init, write the application command in the urunit configuration as `ARC`/`ARV` entries, as FreeBSD guests already do, and stop passing it on the kernel command line. Also return a clear error when an argument contains a newline. This fix requires a urunit that reads `ARC`/`ARV`. Fixes: #1067 Signed-off-by: Indrajith <29561593+indrajithek@users.noreply.github.com> --- pkg/unikontainers/unikernels/linux.go | 22 +++++- pkg/unikontainers/unikernels/linux_test.go | 82 ++++++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 pkg/unikontainers/unikernels/linux_test.go diff --git a/pkg/unikontainers/unikernels/linux.go b/pkg/unikontainers/unikernels/linux.go index 69fc17edc..6cdc833e9 100644 --- a/pkg/unikontainers/unikernels/linux.go +++ b/pkg/unikontainers/unikernels/linux.go @@ -42,6 +42,7 @@ const ( type Linux struct { App string Command string + Args []string // application command, verbatim, for urunit Monitor string Env []string Net LinuxNet @@ -123,7 +124,11 @@ func (l *Linux) CommandString() (string, error) { bootParams += " URUNIT_DEFROUTE=1" } if l.App != "" { - initParams := rdinit + "init=" + l.App + " -- " + l.Command + initParams := rdinit + "init=" + l.App + // urunit takes the application command from its configuration + if !l.InitrdConf { + initParams += " -- " + l.Command + } bootParams += " " + initParams } @@ -275,6 +280,7 @@ func (l *Linux) parseCmdLine(cmdLine []string) error { } l.App = normalizedArgs[0] + l.Args = cmdLine[1:] if len(normalizedArgs) > 1 { l.Command = strings.Join(normalizedArgs[1:], " ") } else { @@ -293,6 +299,12 @@ func (l *Linux) configureNetwork(net types.NetDevParams) { // setupUrunitConfig creates the urunit configuration file with environment variables. func (l *Linux) setupUrunitConfig(rfs types.RootfsParams) error { + // The configuration is line based, so an argument cannot hold a newline + for _, arg := range l.Args { + if strings.Contains(arg, "\n") { + return fmt.Errorf("failed to setup urunit config: argument %q contains a newline", arg) + } + } urunitConfig := l.buildUrunitConfig() var err error @@ -337,6 +349,14 @@ func (l *Linux) buildUrunitConfig() string { sb.WriteString("WD:") sb.WriteString(l.ProcConfig.WorkDir) sb.WriteString("\n") + sb.WriteString("ARC:") + sb.WriteString(strconv.Itoa(len(l.Args))) + sb.WriteString("\n") + for _, arg := range l.Args { + sb.WriteString("ARV:") + sb.WriteString(arg) + sb.WriteString("\n") + } sb.WriteString(lpcEndMarker) sb.WriteString("\n") sb.WriteString(blkStartMarker) diff --git a/pkg/unikontainers/unikernels/linux_test.go b/pkg/unikontainers/unikernels/linux_test.go new file mode 100644 index 000000000..0d92a5d4b --- /dev/null +++ b/pkg/unikontainers/unikernels/linux_test.go @@ -0,0 +1,82 @@ +// Copyright (c) 2023-2026, Nubificus LTD +// +// 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 unikernels + +import ( + "strconv" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestLinuxUrunitArgs covers the arguments that the kernel command line +// mangles: an empty argument and a literal "--". +func TestLinuxUrunitArgs(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + cmdLine []string + expected string + }{ + { + name: "empty argument", + cmdLine: []string{"/urunit", "/app", "a", "", "b"}, + expected: "ARC:4\nARV:/app\nARV:a\nARV:\nARV:b\nUCE\n", + }, + { + name: "literal --", + cmdLine: []string{"/urunit", "/app", "a", "--", "b", "c"}, + expected: "ARC:5\nARV:/app\nARV:a\nARV:--\nARV:b\nARV:c\nUCE\n", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + l := &Linux{InitrdConf: true, RootFsType: "virtiofs"} + require.NoError(t, l.parseCmdLine(tc.cmdLine)) + assert.Contains(t, l.buildUrunitConfig(), tc.expected) + + cmd, err := l.CommandString() + require.NoError(t, err) + assert.True(t, strings.HasSuffix(cmd, " init=/urunit"), "unexpected command line: %s", cmd) + }) + } +} + +// TestLinuxUrunitLongArgs covers an argument list longer than the kernel +// command line (2048 bytes) with more than 32 arguments. +func TestLinuxUrunitLongArgs(t *testing.T) { + t.Parallel() + + cmdLine := []string{"/urunit", "/app"} + for i := range 40 { + cmdLine = append(cmdLine, strconv.Itoa(i)+strings.Repeat("x", 100)) + } + l := &Linux{InitrdConf: true, RootFsType: "virtiofs"} + require.NoError(t, l.parseCmdLine(cmdLine)) + conf := l.buildUrunitConfig() + + assert.Contains(t, conf, "\nARC:41\n") + assert.Equal(t, 41, strings.Count(conf, "\nARV:")) + assert.Contains(t, conf, "\nARV:"+cmdLine[41]+"\nUCE\n") + + cmd, err := l.CommandString() + require.NoError(t, err) + assert.True(t, strings.HasSuffix(cmd, " init=/urunit"), "unexpected command line: %s", cmd) +} From 0282936be878dbd5bc4fc37a6bb04d52648f39b4 Mon Sep 17 00:00:00 2001 From: Indrajith <29561593+indrajithek@users.noreply.github.com> Date: Tue, 20 Oct 2026 05:17:21 +0000 Subject: [PATCH 2/4] chore: add indrajithek to contributors Signed-off-by: Indrajith <29561593+indrajithek@users.noreply.github.com> --- .github/contributors.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/contributors.yaml b/.github/contributors.yaml index ee3ba7e4a..6716b95a6 100644 --- a/.github/contributors.yaml +++ b/.github/contributors.yaml @@ -161,3 +161,6 @@ users: Inuth0603: name: Divyam Agarwal email: iitdivyamagarwal@gmail.com + indrajithek: + name: Indrajith + email: 29561593+indrajithek@users.noreply.github.com From 6dd22f1cb368d83cce71d98aea52090b4722ee20 Mon Sep 17 00:00:00 2001 From: Indrajith <29561593+indrajithek@users.noreply.github.com> Date: Tue, 20 Oct 2026 05:17:21 +0000 Subject: [PATCH 3/4] docs: describe the application command in the urunit config Add the `ARC`/`ARV` entries to the documented urunit configuration format and update the list of actions urunit performs. Ref: #1067 Signed-off-by: Indrajith <29561593+indrajithek@users.noreply.github.com> --- docs/tutorials/existing-container-linux.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/tutorials/existing-container-linux.md b/docs/tutorials/existing-container-linux.md index 0dde898de..620dd9aea 100644 --- a/docs/tutorials/existing-container-linux.md +++ b/docs/tutorials/existing-container-linux.md @@ -77,7 +77,7 @@ designed specifically for `urunc`. It performs the following actions: the container in a Kubernetes cluster, where there is a high chance that the gateway of the container might be in a different subnet than the IP. As a result, the Linux kernel will fail to set the gateway. -2. Groups multi-word arguments correctly. +2. Reads the application command and its arguments from the configuration. 3. Mounts `/proc` and `/sys`. 4. Reads and sets the environment variables for the application execution 5. Reads and sets the uid,gid and working directory for the application execution @@ -95,6 +95,8 @@ UCS UID: GID: WD: +ARC: +ARV: /* one line per argument */ UCE ``` From 1f63e920aef84c5180bb1782b50e5352564f51b6 Mon Sep 17 00:00:00 2001 From: Indrajith <29561593+indrajithek@users.noreply.github.com> Date: Fri, 6 Nov 2026 16:15:09 +0100 Subject: [PATCH 4/4] fix: remove the newline check from this PR Noticed #1068 is also addressing arguments containing a newline. Removing it from this PR to avoid conflicts. Signed-off-by: Indrajith <29561593+indrajithek@users.noreply.github.com> --- pkg/unikontainers/unikernels/linux.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pkg/unikontainers/unikernels/linux.go b/pkg/unikontainers/unikernels/linux.go index 6cdc833e9..35571bb82 100644 --- a/pkg/unikontainers/unikernels/linux.go +++ b/pkg/unikontainers/unikernels/linux.go @@ -299,12 +299,6 @@ func (l *Linux) configureNetwork(net types.NetDevParams) { // setupUrunitConfig creates the urunit configuration file with environment variables. func (l *Linux) setupUrunitConfig(rfs types.RootfsParams) error { - // The configuration is line based, so an argument cannot hold a newline - for _, arg := range l.Args { - if strings.Contains(arg, "\n") { - return fmt.Errorf("failed to setup urunit config: argument %q contains a newline", arg) - } - } urunitConfig := l.buildUrunitConfig() var err error