Merge pull request #24825 from giuseppe/simplify-systemd-parser

systemd: simplify parser and fix infinite loop
This commit is contained in:
openshift-merge-bot[bot] 2024-12-13 18:47:03 +00:00 committed by GitHub
commit 3cffc6bcaf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 66 additions and 44 deletions

View File

@ -48,7 +48,6 @@ type UnitFileParser struct {
currentGroup *unitGroup currentGroup *unitGroup
pendingComments []*unitLine pendingComments []*unitLine
lineNr int
} }
func newUnitLine(key string, value string, isComment bool) *unitLine { func newUnitLine(key string, value string, isComment bool) *unitLine {
@ -347,7 +346,7 @@ func (p *UnitFileParser) parseKeyValuePair(line string) error {
return nil return nil
} }
func (p *UnitFileParser) parseLine(line string) error { func (p *UnitFileParser) parseLine(line string, lineNr int) error {
switch { switch {
case lineIsComment(line): case lineIsComment(line):
return p.parseComment(line) return p.parseComment(line)
@ -356,7 +355,7 @@ func (p *UnitFileParser) parseLine(line string) error {
case lineIsKeyValuePair(line): case lineIsKeyValuePair(line):
return p.parseKeyValuePair(line) return p.parseKeyValuePair(line)
default: default:
return fmt.Errorf("file contains line %d: “%s” which is not a key-value pair, group, or comment", p.lineNr, line) return fmt.Errorf("file contains line %d: “%s” which is not a key-value pair, group, or comment", lineNr, line)
} }
} }
@ -376,53 +375,39 @@ func (p *UnitFileParser) flushPendingComments(toComment bool) {
} }
} }
func nextLine(data string, afterPos int) (string, string) {
rest := data[afterPos:]
if i := strings.Index(rest, "\n"); i >= 0 {
return strings.TrimSpace(data[:i+afterPos]), data[i+afterPos+1:]
}
return data, ""
}
func trimSpacesFromLines(data string) string {
lines := strings.Split(data, "\n")
for i, line := range lines {
lines[i] = strings.TrimSpace(line)
}
return strings.Join(lines, "\n")
}
// Parse an already loaded unit file (in the form of a string) // Parse an already loaded unit file (in the form of a string)
func (f *UnitFile) Parse(data string) error { func (f *UnitFile) Parse(data string) error {
p := &UnitFileParser{ p := &UnitFileParser{
file: f, file: f,
lineNr: 1,
} }
data = trimSpacesFromLines(data) lines := strings.Split(strings.TrimSuffix(data, "\n"), "\n")
remaining := ""
for len(data) > 0 { for lineNr, line := range lines {
origdata := data line = strings.TrimSpace(line)
nLines := 1 if lineIsComment(line) {
var line string // ignore the comment is inside a continuation line.
line, data = nextLine(data, 0) if remaining != "" {
continue
if !lineIsComment(line) { }
// Handle multi-line continuations } else {
// Note: This doesn't support comments in the middle of the continuation, which systemd does if strings.HasSuffix(line, "\\") {
if lineIsKeyValuePair(line) { line = line[:len(line)-1]
for len(data) > 0 && line[len(line)-1] == '\\' { if lineNr != len(lines)-1 {
line, data = nextLine(origdata, len(line)+1) remaining += line
nLines++ continue
} }
} }
// check whether the line is a continuation of the previous line
if remaining != "" {
line = remaining + line
remaining = ""
} }
}
if err := p.parseLine(line); err != nil { if err := p.parseLine(line, lineNr+1); err != nil {
return err return err
} }
p.lineNr += nLines
} }
if p.currentGroup == nil { if p.currentGroup == nil {
@ -690,7 +675,6 @@ func (f *UnitFile) LookupInt(groupName string, key string, defaultValue int64) i
} }
intVal, err := convertNumber(v) intVal, err := convertNumber(v)
if err != nil { if err != nil {
return defaultValue return defaultValue
} }

View File

@ -2,6 +2,7 @@ package parser
import ( import (
"reflect" "reflect"
"strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -119,7 +120,10 @@ After=dbus.socket
[Service] [Service]
BusName=org.freedesktop.login1 BusName=org.freedesktop.login1
CapabilityBoundingSet=CAP_SYS_ADMIN CAP_MAC_ADMIN CAP_AUDIT_CONTROL CAP_CHOWN CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE CAP_FOWNER CAP_SYS_TTY_CONFIG CAP_LINUX_IMMUTABLE CapabilityBoundingSet=CAP_SYS_ADMIN CAP_MAC_ADMIN CAP_AUDIT_CONTROL CAP_CHOWN CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE \
# comment inside a continuation line \
CAP_FOWNER \
CAP_SYS_TTY_CONFIG CAP_LINUX_IMMUTABLE
DeviceAllow=block-* r DeviceAllow=block-* r
DeviceAllow=char-/dev/console rw DeviceAllow=char-/dev/console rw
DeviceAllow=char-drm rw DeviceAllow=char-drm rw
@ -158,8 +162,8 @@ SystemCallFilter=@system-service
# Increase the default a bit in order to allow many simultaneous logins since # Increase the default a bit in order to allow many simultaneous logins since
# we keep one fd open per session. # we keep one fd open per session.
LimitNOFILE=524288 LimitNOFILE=524288 \`
`
const systemdnetworkdService = `# SPDX-License-Identifier: LGPL-2.1-or-later const systemdnetworkdService = `# SPDX-License-Identifier: LGPL-2.1-or-later
# #
# This file is part of systemd. # This file is part of systemd.
@ -264,6 +268,23 @@ var sampleDropinPaths = map[string][]string{
sampleDropinTemplateInstance: sampleDropinTemplateInstancePaths, sampleDropinTemplateInstance: sampleDropinTemplateInstancePaths,
} }
func filterComments(input string) string {
lines := strings.Split(input, "\n")
filtered := make([]string, 0, len(lines))
for _, line := range lines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") {
continue
}
filtered = append(filtered, line)
}
// merge continuation lines
joined := strings.ReplaceAll(strings.Join(filtered, "\n"), "\\\n", "")
// and remove any trailing new line, backslash or space
return strings.TrimRight(joined, "\n\\ ")
}
func TestRanges_Roundtrip(t *testing.T) { func TestRanges_Roundtrip(t *testing.T) {
for i := range samples { for i := range samples {
sample := samples[i] sample := samples[i]
@ -278,7 +299,7 @@ func TestRanges_Roundtrip(t *testing.T) {
panic(e) panic(e)
} }
assert.Equal(t, sample, asStr) assert.Equal(t, filterComments(sample), filterComments(asStr))
} }
} }
@ -292,3 +313,16 @@ func TestUnitDropinPaths_Search(t *testing.T) {
assert.True(t, reflect.DeepEqual(expectedPaths, generatedPaths)) assert.True(t, reflect.DeepEqual(expectedPaths, generatedPaths))
} }
} }
func FuzzParser(f *testing.F) {
for _, sample := range samples {
f.Add([]byte(sample))
}
f.Fuzz(func(t *testing.T, orig []byte) {
unitFile := NewUnitFile()
unitFile.Path = "foo/bar"
unitFile.Filename = "bar"
_ = unitFile.Parse(string(orig))
})
}

View File

@ -0,0 +1,4 @@
[Container]
Exec=true \
# must have a blank line above, but this line can be anything (including another blank line)