mirror of
https://github.com/lima-vm/lima.git
synced 2026-10-28 11:44:35 +08:00
strip control characters from guest cloud-init log lines
The hostagent forwards cloud-init log lines read from the guest into CloudInitProgress events, which limactl start and limactl watch print to the operator terminal with a bare %s. A guest controls those bytes, so it can emit ANSI/OSC escape sequences to spoof the terminal. Strip non-printable runes at the single emit choke point emitCloudInitProgressEvent. The control-character stripper is moved from pkg/cidata to strutil.RemoveControlChars so both cidata and the hostagent path share it. Signed-off-by: nashit hayyat <nashit@bugqore.com>
This commit is contained in:
1 parent
1281ca54bc
commit
d9238b3e17
4 files changed
+63
-12
No files matched your search
+2
-12
@@ -19,7 +19,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/docker/go-units"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -36,6 +35,7 @@ import (
|
||||
"github.com/lima-vm/lima/v2/pkg/networks/usernet"
|
||||
"github.com/lima-vm/lima/v2/pkg/osutil"
|
||||
"github.com/lima-vm/lima/v2/pkg/sshutil"
|
||||
"github.com/lima-vm/lima/v2/pkg/strutil"
|
||||
)
|
||||
|
||||
var netLookupIP = func(host string) []net.IP {
|
||||
@@ -132,7 +132,7 @@ func templateArgs(ctx context.Context, bootScripts bool, instDir, name string, i
|
||||
Name: name,
|
||||
Hostname: hostname.FromInstName(name), // TODO: support customization
|
||||
User: *instConfig.User.Name,
|
||||
Comment: removeControlChars(*instConfig.User.Comment),
|
||||
Comment: strutil.RemoveControlChars(*instConfig.User.Comment),
|
||||
Home: *instConfig.User.Home,
|
||||
Shell: *instConfig.User.Shell,
|
||||
UID: *instConfig.User.UID,
|
||||
@@ -496,16 +496,6 @@ func GenerateISO9660(ctx context.Context, drv driver.Driver, instDir, name strin
|
||||
return args.IID, iso9660util.Write(filepath.Join(instDir, filenames.CIDataISO), "cidata", layout, iso9660Options...)
|
||||
}
|
||||
|
||||
func removeControlChars(s string) string {
|
||||
out := make([]rune, 0, len(s))
|
||||
for _, r := range s {
|
||||
if unicode.IsPrint(r) {
|
||||
out = append(out, r)
|
||||
}
|
||||
}
|
||||
return string(out)
|
||||
}
|
||||
|
||||
func getCert(content string) Cert {
|
||||
lines := []string{}
|
||||
for line := range strings.SplitSeq(content, "\n") {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// SPDX-FileCopyrightText: Copyright The Lima Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package hostagent
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
"unicode"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
|
||||
"github.com/lima-vm/lima/v2/pkg/hostagent/events"
|
||||
)
|
||||
|
||||
func TestEmitCloudInitProgressEventStripsControlChars(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
a := &HostAgent{eventEnc: json.NewEncoder(&buf)}
|
||||
|
||||
// A compromised guest can put arbitrary bytes in its cloud-init log, including
|
||||
// an ANSI escape sequence that would rewrite the operator's terminal.
|
||||
const payload = "starting\x1b[2Kspoofed\x07 line"
|
||||
a.emitCloudInitProgressEvent(t.Context(), &events.CloudInitProgress{LogLine: payload})
|
||||
|
||||
var ev events.Event
|
||||
assert.NilError(t, json.Unmarshal(buf.Bytes(), &ev))
|
||||
assert.Assert(t, ev.Status.CloudInitProgress != nil)
|
||||
got := ev.Status.CloudInitProgress.LogLine
|
||||
for _, r := range got {
|
||||
assert.Assert(t, unicode.IsPrint(r), "control char %#q survived in %#q", r, got)
|
||||
}
|
||||
assert.Equal(t, got, "starting[2Kspoofed line")
|
||||
assert.Assert(t, !strings.ContainsRune(got, '\x1b'))
|
||||
}
|
||||
@@ -48,6 +48,7 @@ import (
|
||||
"github.com/lima-vm/lima/v2/pkg/portfwd"
|
||||
"github.com/lima-vm/lima/v2/pkg/sshutil"
|
||||
"github.com/lima-vm/lima/v2/pkg/store"
|
||||
"github.com/lima-vm/lima/v2/pkg/strutil"
|
||||
"github.com/lima-vm/lima/v2/pkg/version/versionutil"
|
||||
)
|
||||
|
||||
@@ -353,6 +354,12 @@ func (a *HostAgent) emitEvent(_ context.Context, ev events.Event) {
|
||||
}
|
||||
|
||||
func (a *HostAgent) emitCloudInitProgressEvent(ctx context.Context, progress *events.CloudInitProgress) {
|
||||
// LogLine is read verbatim from the guest's cloud-init output over SSH, so an
|
||||
// untrusted guest controls its bytes. limactl start/watch print it to the
|
||||
// operator's terminal, so strip control characters here to keep the guest
|
||||
// from injecting ANSI/OSC escape sequences.
|
||||
progress.LogLine = strutil.RemoveControlChars(progress.LogLine)
|
||||
|
||||
a.statusMu.RLock()
|
||||
currentStatus := a.currentStatus
|
||||
a.statusMu.RUnlock()
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// SPDX-FileCopyrightText: Copyright The Lima Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package strutil
|
||||
|
||||
import "unicode"
|
||||
|
||||
// RemoveControlChars drops non-printable runes so guest-controlled text is safe
|
||||
// to print to the operator's terminal.
|
||||
func RemoveControlChars(s string) string {
|
||||
out := make([]rune, 0, len(s))
|
||||
for _, r := range s {
|
||||
if unicode.IsPrint(r) {
|
||||
out = append(out, r)
|
||||
}
|
||||
}
|
||||
return string(out)
|
||||
}
|
||||
Reference in new issue
Block a user