Skip to content

Commit 482cf6f

Browse files
committed
plugin: restrict characters in plugin names
Thanks to ⬡-49016 for reporting this issue. Fixes GHSA-32gq-x56h-299c
1 parent cda3988 commit 482cf6f

File tree

3 files changed

+45
-0
lines changed

3 files changed

+45
-0
lines changed

cmd/age/testdata/plugin.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@ age -d -i long-key.txt test.age
1010
cmp stdout input
1111
! stderr .
1212

13+
# check that path separators are rejected
14+
chmod 755 age-plugin-pwn/pwn
15+
mkdir $TMPDIR/age-plugin-pwn
16+
cp age-plugin-pwn/pwn $TMPDIR/age-plugin-pwn/pwn
17+
! age -r age1pwn/pwn19gt89dfz input
18+
! age -d -i pwn-identity.txt test.age
19+
! age -d -j pwn/pwn test.age
20+
! exists pwn
21+
1322
-- input --
1423
test
1524
-- key.txt --
@@ -18,3 +27,8 @@ AGE-PLUGIN-TEST-10Q32NLXM
1827
age1test10pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7qj6rl8p
1928
-- long-key.txt --
2029
AGE-PLUGIN-TEST-10PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7Q5U8SUD
30+
-- pwn-identity.txt --
31+
AGE-PLUGIN-PWN/PWN-19GYK4WLY
32+
-- age-plugin-pwn/pwn --
33+
#!/bin/sh
34+
touch "$WORK/pwn"

plugin/client.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"os"
1616
"path/filepath"
1717
"strconv"
18+
"strings"
1819
"time"
1920

2021
exec "golang.org/x/sys/execabs"
@@ -178,6 +179,9 @@ func NewIdentity(s string, ui *ClientUI) (*Identity, error) {
178179

179180
func NewIdentityWithoutData(name string, ui *ClientUI) (*Identity, error) {
180181
s := EncodeIdentity(name, nil)
182+
if s == "" {
183+
return nil, fmt.Errorf("invalid plugin name: %q", name)
184+
}
181185
return &Identity{
182186
name: name, encoding: s, ui: ui,
183187
}, nil
@@ -390,6 +394,8 @@ func openClientConnection(name, protocol string) (*clientConnection, error) {
390394
path := "age-plugin-" + name
391395
if testOnlyPluginPath != "" {
392396
path = filepath.Join(testOnlyPluginPath, path)
397+
} else if strings.ContainsRune(name, os.PathSeparator) {
398+
return nil, fmt.Errorf("invalid plugin name: %q", name)
393399
}
394400
cmd := exec.Command(path, "--age-plugin="+protocol)
395401

plugin/encode.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import (
1414
// EncodeIdentity encodes a plugin identity string for a plugin with the given
1515
// name. If the name is invalid, it returns an empty string.
1616
func EncodeIdentity(name string, data []byte) string {
17+
if !validPluginName(name) {
18+
return ""
19+
}
1720
s, _ := bech32.Encode("AGE-PLUGIN-"+strings.ToUpper(name)+"-", data)
1821
return s
1922
}
@@ -30,12 +33,18 @@ func ParseIdentity(s string) (name string, data []byte, err error) {
3033
}
3134
name = strings.TrimSuffix(strings.TrimPrefix(hrp, "AGE-PLUGIN-"), "-")
3235
name = strings.ToLower(name)
36+
if !validPluginName(name) {
37+
return "", nil, fmt.Errorf("invalid plugin name: %q", name)
38+
}
3339
return name, data, nil
3440
}
3541

3642
// EncodeRecipient encodes a plugin recipient string for a plugin with the given
3743
// name. If the name is invalid, it returns an empty string.
3844
func EncodeRecipient(name string, data []byte) string {
45+
if !validPluginName(name) {
46+
return ""
47+
}
3948
s, _ := bech32.Encode("age1"+strings.ToLower(name), data)
4049
return s
4150
}
@@ -51,5 +60,21 @@ func ParseRecipient(s string) (name string, data []byte, err error) {
5160
return "", nil, fmt.Errorf("not a plugin recipient: %v", err)
5261
}
5362
name = strings.TrimPrefix(hrp, "age1")
63+
if !validPluginName(name) {
64+
return "", nil, fmt.Errorf("invalid plugin name: %q", name)
65+
}
5466
return name, data, nil
5567
}
68+
69+
func validPluginName(name string) bool {
70+
if name == "" {
71+
return false
72+
}
73+
allowed := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-._"
74+
for _, r := range name {
75+
if !strings.ContainsRune(allowed, r) {
76+
return false
77+
}
78+
}
79+
return true
80+
}

0 commit comments

Comments
 (0)