File escape-terminal-special-characters-in-kubectl-112553.patch of Package kubernetes1.25.35089
From dad0e937c0f76344363eb691b2668490ffef8537 Mon Sep 17 00:00:00 2001
From: David Leadbeater <dgl@dgl.cx>
Date: Mon, 31 Oct 2022 01:08:43 +1000
Subject: [PATCH] Escape terminal special characters in kubectl (#112553)
* Escape terminal special characters in kubectl
* Add escaping for kubectl alpha events
---
.../cli-runtime/pkg/printers/tableprinter.go | 13 ++++---
.../pkg/printers/tableprinter_test.go | 12 ++++++
.../cli-runtime/pkg/printers/terminal.go | 39 +++++++++++++++++++
.../kubectl/pkg/cmd/events/event_printer.go | 10 +++--
.../pkg/cmd/events/event_printer_test.go | 34 ++++++++++++++++
.../kubectl/pkg/cmd/get/customcolumn.go | 2 +-
.../kubectl/pkg/cmd/get/customcolumn_test.go | 16 ++++++++
.../k8s.io/kubectl/pkg/describe/describe.go | 7 +++-
.../kubectl/pkg/describe/describe_test.go | 19 +++++++++
9 files changed, 139 insertions(+), 13 deletions(-)
create mode 100644 staging/src/k8s.io/cli-runtime/pkg/printers/terminal.go
diff --git a/staging/src/k8s.io/cli-runtime/pkg/printers/tableprinter.go b/staging/src/k8s.io/cli-runtime/pkg/printers/tableprinter.go
index 87bc3f41428..548596659ea 100644
--- a/staging/src/k8s.io/cli-runtime/pkg/printers/tableprinter.go
+++ b/staging/src/k8s.io/cli-runtime/pkg/printers/tableprinter.go
@@ -212,18 +212,19 @@ func printTable(table *metav1.Table, output io.Writer, options PrintOptions) err
case string:
print := val
truncated := false
- // truncate at newlines
- newline := strings.Index(print, "\n")
- if newline >= 0 {
+ // Truncate at the first newline, carriage return or formfeed
+ // (treated as a newline by tabwriter).
+ breakchar := strings.IndexAny(print, "\f\n\r")
+ if breakchar >= 0 {
truncated = true
- print = print[:newline]
+ print = print[:breakchar]
}
- fmt.Fprint(output, print)
+ WriteEscaped(output, print)
if truncated {
fmt.Fprint(output, "...")
}
default:
- fmt.Fprint(output, val)
+ WriteEscaped(output, fmt.Sprint(val))
}
}
}
diff --git a/staging/src/k8s.io/cli-runtime/pkg/printers/tableprinter_test.go b/staging/src/k8s.io/cli-runtime/pkg/printers/tableprinter_test.go
index b2caaa12838..edbff3ff750 100644
--- a/staging/src/k8s.io/cli-runtime/pkg/printers/tableprinter_test.go
+++ b/staging/src/k8s.io/cli-runtime/pkg/printers/tableprinter_test.go
@@ -769,6 +769,18 @@ test1 20h This is first line which is long and goes for on and on and on an
},
expected: `NAME AGE DESCRIPTION
test1 20h This is first...
+`,
+ },
+ // terminal special character, should be escaped
+ {
+ columns: []metav1.TableColumnDefinition{
+ {Name: "Name", Type: "string"},
+ },
+ rows: []metav1.TableRow{
+ {Cells: []interface{}{"test1\x1b"}},
+ },
+ expected: `NAME
+test1^[
`,
},
}
diff --git a/staging/src/k8s.io/cli-runtime/pkg/printers/terminal.go b/staging/src/k8s.io/cli-runtime/pkg/printers/terminal.go
new file mode 100644
index 00000000000..5a59491e492
--- /dev/null
+++ b/staging/src/k8s.io/cli-runtime/pkg/printers/terminal.go
@@ -0,0 +1,39 @@
+/*
+Copyright 2022 The Kubernetes Authors.
+
+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 printers
+
+import (
+ "io"
+ "strings"
+)
+
+// terminalEscaper replaces ANSI escape sequences and other terminal special
+// characters to avoid terminal escape character attacks (issue #101695).
+var terminalEscaper = strings.NewReplacer("\x1b", "^[", "\r", "\\r")
+
+// WriteEscaped replaces unsafe terminal characters with replacement strings
+// and writes them to the given writer.
+func WriteEscaped(writer io.Writer, output string) error {
+ _, err := terminalEscaper.WriteString(writer, output)
+ return err
+}
+
+// EscapeTerminal escapes terminal special characters in a human readable (but
+// non-reversible) format.
+func EscapeTerminal(in string) string {
+ return terminalEscaper.Replace(in)
+}
diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/events/event_printer.go b/staging/src/k8s.io/kubectl/pkg/cmd/events/event_printer.go
index b9254bb7997..b47e940532f 100644
--- a/staging/src/k8s.io/kubectl/pkg/cmd/events/event_printer.go
+++ b/staging/src/k8s.io/kubectl/pkg/cmd/events/event_printer.go
@@ -26,6 +26,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/duration"
+ "k8s.io/cli-runtime/pkg/printers"
)
// EventPrinter stores required fields to be used for
@@ -72,10 +73,11 @@ func (ep *EventPrinter) printOneEvent(w io.Writer, e corev1.Event) {
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s/%s\t%v\n",
interval,
- e.Type,
- e.Reason,
- e.InvolvedObject.Kind, e.InvolvedObject.Name,
- strings.TrimSpace(e.Message),
+ printers.EscapeTerminal(e.Type),
+ printers.EscapeTerminal(e.Reason),
+ printers.EscapeTerminal(e.InvolvedObject.Kind),
+ printers.EscapeTerminal(e.InvolvedObject.Name),
+ printers.EscapeTerminal(strings.TrimSpace(e.Message)),
)
}
diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/events/event_printer_test.go b/staging/src/k8s.io/kubectl/pkg/cmd/events/event_printer_test.go
index c52eae87efe..4c022d2b591 100644
--- a/staging/src/k8s.io/kubectl/pkg/cmd/events/event_printer_test.go
+++ b/staging/src/k8s.io/kubectl/pkg/cmd/events/event_printer_test.go
@@ -208,6 +208,40 @@ foo 12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica
},
},
expected: `foo 12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
+`,
+ },
+ {
+ printer: EventPrinter{
+ NoHeaders: false,
+ AllNamespaces: false,
+ },
+ obj: &corev1.EventList{
+ Items: []corev1.Event{
+ {
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "bar-000",
+ Namespace: "foo",
+ },
+ InvolvedObject: corev1.ObjectReference{
+ APIVersion: "apps/v1",
+ Kind: "Deployment",
+ Name: "bar\x1b",
+ Namespace: "foo",
+ },
+ Type: "test\x1b",
+ Reason: "test\x1b",
+ Message: "\x1b",
+ ReportingController: "deployment-controller",
+ EventTime: metav1.NewMicroTime(time.Now().Add(-20 * time.Minute)),
+ Series: &corev1.EventSeries{
+ Count: 3,
+ LastObservedTime: metav1.NewMicroTime(time.Now().Add(-1 * time.Minute)),
+ },
+ },
+ },
+ },
+ expected: `LAST SEEN TYPE REASON OBJECT MESSAGE
+60s (x3 over 20m) test^[ test^[ Deployment/bar^[ ^[
`,
},
}
diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/get/customcolumn.go b/staging/src/k8s.io/kubectl/pkg/cmd/get/customcolumn.go
index 2b205667c6b..38024cfa5db 100644
--- a/staging/src/k8s.io/kubectl/pkg/cmd/get/customcolumn.go
+++ b/staging/src/k8s.io/kubectl/pkg/cmd/get/customcolumn.go
@@ -252,7 +252,7 @@ func (s *CustomColumnsPrinter) printOneObject(obj runtime.Object, parsers []*jso
}
for arrIx := range values {
for valIx := range values[arrIx] {
- valueStrings = append(valueStrings, fmt.Sprintf("%v", values[arrIx][valIx].Interface()))
+ valueStrings = append(valueStrings, printers.EscapeTerminal(fmt.Sprint(values[arrIx][valIx].Interface())))
}
}
columns[ix] = strings.Join(valueStrings, ",")
diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/get/customcolumn_test.go b/staging/src/k8s.io/kubectl/pkg/cmd/get/customcolumn_test.go
index e4fb17a8eb7..de40314217c 100644
--- a/staging/src/k8s.io/kubectl/pkg/cmd/get/customcolumn_test.go
+++ b/staging/src/k8s.io/kubectl/pkg/cmd/get/customcolumn_test.go
@@ -311,6 +311,22 @@ foo baz
obj: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, TypeMeta: metav1.TypeMeta{APIVersion: "baz"}},
expectedOutput: `NAME API_VERSION NOT_FOUND
foo baz <none>
+`,
+ },
+ {
+ columns: []Column{
+ {
+ Header: "NAME",
+ FieldSpec: "{.metadata.name}",
+ },
+ },
+ obj: &corev1.PodList{
+ Items: []corev1.Pod{
+ {ObjectMeta: metav1.ObjectMeta{Name: "\x1b \r"}},
+ },
+ },
+ expectedOutput: `NAME
+^[ \r
`,
},
}
diff --git a/staging/src/k8s.io/kubectl/pkg/describe/describe.go b/staging/src/k8s.io/kubectl/pkg/describe/describe.go
index 456c779bd3d..b3c47144446 100644
--- a/staging/src/k8s.io/kubectl/pkg/describe/describe.go
+++ b/staging/src/k8s.io/kubectl/pkg/describe/describe.go
@@ -65,6 +65,7 @@ import (
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/cli-runtime/pkg/genericclioptions"
+ "k8s.io/cli-runtime/pkg/printers"
runtimeresource "k8s.io/cli-runtime/pkg/resource"
"k8s.io/client-go/dynamic"
clientset "k8s.io/client-go/kubernetes"
@@ -148,11 +149,13 @@ func (pw *prefixWriter) Write(level int, format string, a ...interface{}) {
for i := 0; i < level; i++ {
prefix += levelSpace
}
- fmt.Fprintf(pw.out, prefix+format, a...)
+ output := fmt.Sprintf(prefix+format, a...)
+ printers.WriteEscaped(pw.out, output)
}
func (pw *prefixWriter) WriteLine(a ...interface{}) {
- fmt.Fprintln(pw.out, a...)
+ output := fmt.Sprintln(a...)
+ printers.WriteEscaped(pw.out, output)
}
func (pw *prefixWriter) Flush() {
diff --git a/staging/src/k8s.io/kubectl/pkg/describe/describe_test.go b/staging/src/k8s.io/kubectl/pkg/describe/describe_test.go
index 37eee41ece8..adad2a8374f 100644
--- a/staging/src/k8s.io/kubectl/pkg/describe/describe_test.go
+++ b/staging/src/k8s.io/kubectl/pkg/describe/describe_test.go
@@ -5507,3 +5507,22 @@ func TestControllerRef(t *testing.T) {
t.Errorf("unexpected out: %s", out)
}
}
+
+func TestDescribeTerminalEscape(t *testing.T) {
+ fake := fake.NewSimpleClientset(&corev1.ConfigMap{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "mycm",
+ Namespace: "foo",
+ Annotations: map[string]string{"annotation1": "terminal escape: \x1b"},
+ },
+ })
+ c := &describeClient{T: t, Namespace: "foo", Interface: fake}
+ d := ConfigMapDescriber{c}
+ out, err := d.Describe("foo", "mycm", DescriberSettings{ShowEvents: true})
+ if err != nil {
+ t.Errorf("unexpected error: %v", err)
+ }
+ if strings.Contains(out, "\x1b") || !strings.Contains(out, "^[") {
+ t.Errorf("unexpected out: %s", out)
+ }
+}
--
2.46.0