Files
snapd/interfaces/utils/path_patterns.go

181 lines
4.4 KiB
Go
Raw Permalink Normal View History

// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2021 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package utils
import (
"fmt"
"regexp"
)
type PathPattern struct {
pattern string
regex *regexp.Regexp
}
const maxGroupDepth = 50
type GlobFlags int
const (
globDefault GlobFlags = 1 << iota
globNull
)
// createRegex converts the apparmor-like glob sequence into a regex. Loosely
// using this as reference:
// https://gitlab.com/apparmor/apparmor/-/blob/master/parser/parser_regex.c#L107
interfaces/utils: allow commas in filepaths (#12697) * interfaces/utils: allow commas in filepaths Some device paths contain commas outside of groups (i.e. {a,b}) or classes (i.e. [,.:;'"]). For example, `/dev/foo,bar` is a valid device path which one might with to use with the custom-device interface. Most filesystems allow commas in filepaths, as does apparmor: https://gitlab.com/apparmor/apparmor/-/blob/master/parser/parser_regex.c#L340 Previously, createRegex() would throw an error if a comma was used outside of a group or class. This commit removes that error and instead treats commas outside of groups and classes as literal commas. The accompanying tests are also adjusted to reflect this change. Signed-off-by: Oliver Calder <oliver.calder@canonical.com> * interfaces/utils: added argument to allow commas in filepaths Rather than allowing any caller of `NewPathPattern()` to successfully validate paths containing commas, this change adds a boolean argument which explicitly specifies whether commas should be allowed in the filepath. There are some risks involved with allowing commas in filepaths (see discussion at https://github.com/snapcore/snapd/pull/12697), so it is desirable to restrict when commas are allowed based on the caller. In particular, superprivileged interfaces (such as `custom-device` and `mount-control`) have valid needs for commas in filepaths, and users of these interfaces are individually verified, so it is safe for them to use `NewPathPattern()` with commas allowed. Other callers (particularly unprivileged interfaces) should probably not allow commas. I was unsure whether `overlord/hookstate/ctlcmd/mount.go` should call `NewPathPattern()` with commas allowed or not, but since commas had previously been disallowed and tests continue to pass with `allowCommas=false`, then I decided to leave it as `false`. Signed-off-by: Oliver Calder <oliver.calder@canonical.com> * interfaces/{builtin,utils}: added named variables for allowCommas Also, switched `overlord/hookstate/ctlcmd/mount.go` to allow commas (previously did not, but this should match what is allowed in `interfaces/builtin/mount_control.go`. Signed-off-by: Oliver Calder <oliver.calder@canonical.com> * interfaces/utils: added unit tests for commas in paths Signed-off-by: Oliver Calder <oliver.calder@canonical.com> * interfaces/utils: remove `QuoteMeta` when adding `","` to path regex Since `,` is not a regex special character, the `QuoteMeta` call is unnecessary. Signed-off-by: Oliver Calder <oliver.calder@canonical.com> * interfaces/utils: renamed TestCommasInRegex to TestCreateRegexWithCommas Signed-off-by: Oliver Calder <oliver.calder@canonical.com> * many: added unit tests for callers of NewPathPattern with allowCommas=true Signed-off-by: Oliver Calder <oliver.calder@canonical.com> --------- Signed-off-by: Oliver Calder <oliver.calder@canonical.com> Co-authored-by: Michael Vogt <mvo@ubuntu.com>
2023-05-19 14:49:40 -05:00
func createRegex(pattern string, glob GlobFlags, allowCommas bool) (string, error) {
regex := "^"
appendGlob := func(defaultGlob, nullGlob string) {
var pattern string
switch glob {
case globDefault:
pattern = defaultGlob
case globNull:
pattern = nullGlob
}
regex += pattern
}
const (
noSlashOrNull = `[^/\x00]`
noSlash = `[^/]`
)
escapeNext := false
currentGroupLevel := 0
inCharClass := false
skipNext := false
itemCountInGroup := new([maxGroupDepth + 1]int)
for i, ch := range pattern {
if escapeNext {
regex += regexp.QuoteMeta(string(ch))
escapeNext = false
continue
}
if skipNext {
skipNext = false
continue
}
if inCharClass && ch != '\\' && ch != ']' {
// no characters are special other than '\' and ']'
regex += string(ch)
continue
}
switch ch {
case '\\':
escapeNext = true
case '*':
if regex[len(regex)-1] == '/' {
// if the * is at the end of the pattern or is followed by a
// '/' we don't want it to match an empty string:
// /foo/* -> should not match /foo/
// /foo/*bar -> should match /foo/bar
// /*/foo -> should not match //foo
pos := i + 1
for len(pattern) > pos && pattern[pos] == '*' {
pos++
}
if len(pattern) <= pos || pattern[pos] == '/' {
appendGlob(noSlashOrNull, noSlash)
}
}
if len(pattern) > i+1 && pattern[i+1] == '*' {
// Handle **
appendGlob("[^\\x00]*", ".*")
skipNext = true
} else {
appendGlob(noSlashOrNull+"*", noSlash+"*")
}
case '?':
appendGlob(noSlashOrNull, noSlash)
case '[':
inCharClass = true
regex += string(ch)
case ']':
if !inCharClass {
return "", fmt.Errorf("pattern contains unmatching ']': %q", pattern)
}
inCharClass = false
regex += string(ch)
case '{':
currentGroupLevel++
if currentGroupLevel > maxGroupDepth {
return "", fmt.Errorf("maximum group depth exceeded: %q", pattern)
}
itemCountInGroup[currentGroupLevel] = 0
regex += "("
case '}':
if currentGroupLevel <= 0 {
return "", fmt.Errorf("invalid closing brace, no matching open { found: %q", pattern)
}
if itemCountInGroup[currentGroupLevel] == 0 {
return "", fmt.Errorf("invalid number of items between {}: %q", pattern)
}
currentGroupLevel--
regex += ")"
case ',':
if currentGroupLevel > 0 {
itemCountInGroup[currentGroupLevel]++
regex += "|"
interfaces/utils: allow commas in filepaths (#12697) * interfaces/utils: allow commas in filepaths Some device paths contain commas outside of groups (i.e. {a,b}) or classes (i.e. [,.:;'"]). For example, `/dev/foo,bar` is a valid device path which one might with to use with the custom-device interface. Most filesystems allow commas in filepaths, as does apparmor: https://gitlab.com/apparmor/apparmor/-/blob/master/parser/parser_regex.c#L340 Previously, createRegex() would throw an error if a comma was used outside of a group or class. This commit removes that error and instead treats commas outside of groups and classes as literal commas. The accompanying tests are also adjusted to reflect this change. Signed-off-by: Oliver Calder <oliver.calder@canonical.com> * interfaces/utils: added argument to allow commas in filepaths Rather than allowing any caller of `NewPathPattern()` to successfully validate paths containing commas, this change adds a boolean argument which explicitly specifies whether commas should be allowed in the filepath. There are some risks involved with allowing commas in filepaths (see discussion at https://github.com/snapcore/snapd/pull/12697), so it is desirable to restrict when commas are allowed based on the caller. In particular, superprivileged interfaces (such as `custom-device` and `mount-control`) have valid needs for commas in filepaths, and users of these interfaces are individually verified, so it is safe for them to use `NewPathPattern()` with commas allowed. Other callers (particularly unprivileged interfaces) should probably not allow commas. I was unsure whether `overlord/hookstate/ctlcmd/mount.go` should call `NewPathPattern()` with commas allowed or not, but since commas had previously been disallowed and tests continue to pass with `allowCommas=false`, then I decided to leave it as `false`. Signed-off-by: Oliver Calder <oliver.calder@canonical.com> * interfaces/{builtin,utils}: added named variables for allowCommas Also, switched `overlord/hookstate/ctlcmd/mount.go` to allow commas (previously did not, but this should match what is allowed in `interfaces/builtin/mount_control.go`. Signed-off-by: Oliver Calder <oliver.calder@canonical.com> * interfaces/utils: added unit tests for commas in paths Signed-off-by: Oliver Calder <oliver.calder@canonical.com> * interfaces/utils: remove `QuoteMeta` when adding `","` to path regex Since `,` is not a regex special character, the `QuoteMeta` call is unnecessary. Signed-off-by: Oliver Calder <oliver.calder@canonical.com> * interfaces/utils: renamed TestCommasInRegex to TestCreateRegexWithCommas Signed-off-by: Oliver Calder <oliver.calder@canonical.com> * many: added unit tests for callers of NewPathPattern with allowCommas=true Signed-off-by: Oliver Calder <oliver.calder@canonical.com> --------- Signed-off-by: Oliver Calder <oliver.calder@canonical.com> Co-authored-by: Michael Vogt <mvo@ubuntu.com>
2023-05-19 14:49:40 -05:00
} else if allowCommas {
// treat commas outside of groups as literal commas
regex += ","
} else {
return "", fmt.Errorf("cannot use ',' outside of group or character class")
}
default:
// take literal character (with quoting if needed)
regex += regexp.QuoteMeta(string(ch))
}
}
if currentGroupLevel > 0 {
return "", fmt.Errorf("missing %d closing brace(s): %q", currentGroupLevel, pattern)
}
if inCharClass {
return "", fmt.Errorf("missing closing bracket ']': %q", pattern)
}
if escapeNext {
return "", fmt.Errorf("expected character after '\\': %q", pattern)
}
regex += "$"
return regex, nil
}
interfaces/utils: allow commas in filepaths (#12697) * interfaces/utils: allow commas in filepaths Some device paths contain commas outside of groups (i.e. {a,b}) or classes (i.e. [,.:;'"]). For example, `/dev/foo,bar` is a valid device path which one might with to use with the custom-device interface. Most filesystems allow commas in filepaths, as does apparmor: https://gitlab.com/apparmor/apparmor/-/blob/master/parser/parser_regex.c#L340 Previously, createRegex() would throw an error if a comma was used outside of a group or class. This commit removes that error and instead treats commas outside of groups and classes as literal commas. The accompanying tests are also adjusted to reflect this change. Signed-off-by: Oliver Calder <oliver.calder@canonical.com> * interfaces/utils: added argument to allow commas in filepaths Rather than allowing any caller of `NewPathPattern()` to successfully validate paths containing commas, this change adds a boolean argument which explicitly specifies whether commas should be allowed in the filepath. There are some risks involved with allowing commas in filepaths (see discussion at https://github.com/snapcore/snapd/pull/12697), so it is desirable to restrict when commas are allowed based on the caller. In particular, superprivileged interfaces (such as `custom-device` and `mount-control`) have valid needs for commas in filepaths, and users of these interfaces are individually verified, so it is safe for them to use `NewPathPattern()` with commas allowed. Other callers (particularly unprivileged interfaces) should probably not allow commas. I was unsure whether `overlord/hookstate/ctlcmd/mount.go` should call `NewPathPattern()` with commas allowed or not, but since commas had previously been disallowed and tests continue to pass with `allowCommas=false`, then I decided to leave it as `false`. Signed-off-by: Oliver Calder <oliver.calder@canonical.com> * interfaces/{builtin,utils}: added named variables for allowCommas Also, switched `overlord/hookstate/ctlcmd/mount.go` to allow commas (previously did not, but this should match what is allowed in `interfaces/builtin/mount_control.go`. Signed-off-by: Oliver Calder <oliver.calder@canonical.com> * interfaces/utils: added unit tests for commas in paths Signed-off-by: Oliver Calder <oliver.calder@canonical.com> * interfaces/utils: remove `QuoteMeta` when adding `","` to path regex Since `,` is not a regex special character, the `QuoteMeta` call is unnecessary. Signed-off-by: Oliver Calder <oliver.calder@canonical.com> * interfaces/utils: renamed TestCommasInRegex to TestCreateRegexWithCommas Signed-off-by: Oliver Calder <oliver.calder@canonical.com> * many: added unit tests for callers of NewPathPattern with allowCommas=true Signed-off-by: Oliver Calder <oliver.calder@canonical.com> --------- Signed-off-by: Oliver Calder <oliver.calder@canonical.com> Co-authored-by: Michael Vogt <mvo@ubuntu.com>
2023-05-19 14:49:40 -05:00
func NewPathPattern(pattern string, allowCommas bool) (*PathPattern, error) {
regexPattern, err := createRegex(pattern, globDefault, allowCommas)
if err != nil {
return nil, err
}
regex := regexp.MustCompile(regexPattern)
pp := &PathPattern{pattern, regex}
return pp, nil
}
func (pp *PathPattern) Matches(path string) bool {
return pp.regex.MatchString(path)
}