Headline
CVE-2020-15186: Merge pull request from GHSA-m54r-vrmv-hw33 · helm/helm@809e2d9
In Helm before versions 2.16.11 and 3.3.2 plugin names are not sanitized properly. As a result, a malicious plugin author could use characters in a plugin name that would result in unexpected behavior, such as duplicating the name of another plugin or spoofing the output to helm --help
. This issue has been patched in Helm 3.3.2. A possible workaround is to not install untrusted Helm plugins. Examine the name
field in the plugin.yaml
file for a plugin, looking for characters outside of the [a-zA-Z0-9._-] range.
@@ -20,9 +20,11 @@ import ( “io/ioutil” “os” “path/filepath” “regexp” “runtime” “strings”
“github.com/pkg/errors” “sigs.k8s.io/yaml”
“helm.sh/helm/v3/pkg/cli” @@ -157,18 +159,51 @@ func (p *Plugin) PrepareCommand(extraArgs []string) (string, []string, error) { return main, baseArgs, nil }
// validPluginName is a regular expression that validates plugin names. // // Plugin names can only contain the ASCII characters a-z, A-Z, 0-9, _ and -. var validPluginName = regexp.MustCompile(“^[A-Za-z0-9_-]+$”)
// validatePluginData validates a plugin’s YAML data. func validatePluginData(plug *Plugin, filepath string) error { if !validPluginName.MatchString(plug.Metadata.Name) { return fmt.Errorf("invalid plugin name at %q", filepath) } // We could also validate SemVer, executable, and other fields should we so choose. return nil }
func detectDuplicates(plugs []*Plugin) error { names := map[string]string{}
for _, plug := range plugs { if oldpath, ok := names[plug.Metadata.Name]; ok { return fmt.Errorf( "two plugins claim the name %q at %q and %q", plug.Metadata.Name, oldpath, plug.Dir, ) } names[plug.Metadata.Name] = plug.Dir }
return nil }
// LoadDir loads a plugin from the given directory. func LoadDir(dirname string) (*Plugin, error) { data, err := ioutil.ReadFile(filepath.Join(dirname, PluginFileName)) pluginfile := filepath.Join(dirname, PluginFileName) data, err := ioutil.ReadFile(pluginfile) if err != nil { return nil, err return nil, errors.Wrapf(err, "failed to read plugin at %q", pluginfile) }
plug := &Plugin{Dir: dirname} if err := yaml.Unmarshal(data, &plug.Metadata); err != nil { return nil, err return nil, errors.Wrapf(err, "failed to load plugin at %q", pluginfile) } return plug, nil return plug, validatePluginData(plug, pluginfile) }
// LoadAll loads all plugins found beneath the base directory. @@ -180,7 +215,7 @@ func LoadAll(basedir string) ([]*Plugin, error) { scanpath := filepath.Join(basedir, "*", PluginFileName) matches, err := filepath.Glob(scanpath) if err != nil { return plugins, err return plugins, errors.Wrapf(err, "failed to find plugins in %q", scanpath) }
if matches == nil { @@ -195,7 +230,7 @@ func LoadAll(basedir string) ([]*Plugin, error) { } plugins = append(plugins, p) } return plugins, nil return plugins, detectDuplicates(plugins) }
// FindPlugins returns a list of YAML files that describe plugins.