Skip to content

Commit a26a2ff

Browse files
committed
systemd: simplify parser and fix infinite loop
This commit simplifies the systemd parser logic, and it solves an infinite loop when using a continuation line. Closes: containers#24810 Signed-off-by: Giuseppe Scrivano <[email protected]>
1 parent bf1661c commit a26a2ff

File tree

3 files changed

+66
-44
lines changed

3 files changed

+66
-44
lines changed

pkg/systemd/parser/unitfile.go

+24-40
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ type UnitFileParser struct {
4848

4949
currentGroup *unitGroup
5050
pendingComments []*unitLine
51-
lineNr int
5251
}
5352

5453
func newUnitLine(key string, value string, isComment bool) *unitLine {
@@ -347,7 +346,7 @@ func (p *UnitFileParser) parseKeyValuePair(line string) error {
347346
return nil
348347
}
349348

350-
func (p *UnitFileParser) parseLine(line string) error {
349+
func (p *UnitFileParser) parseLine(line string, lineNr int) error {
351350
switch {
352351
case lineIsComment(line):
353352
return p.parseComment(line)
@@ -356,7 +355,7 @@ func (p *UnitFileParser) parseLine(line string) error {
356355
case lineIsKeyValuePair(line):
357356
return p.parseKeyValuePair(line)
358357
default:
359-
return fmt.Errorf("file contains line %d: “%s” which is not a key-value pair, group, or comment", p.lineNr, line)
358+
return fmt.Errorf("file contains line %d: “%s” which is not a key-value pair, group, or comment", lineNr, line)
360359
}
361360
}
362361

@@ -376,53 +375,39 @@ func (p *UnitFileParser) flushPendingComments(toComment bool) {
376375
}
377376
}
378377

379-
func nextLine(data string, afterPos int) (string, string) {
380-
rest := data[afterPos:]
381-
if i := strings.Index(rest, "\n"); i >= 0 {
382-
return strings.TrimSpace(data[:i+afterPos]), data[i+afterPos+1:]
383-
}
384-
return data, ""
385-
}
386-
387-
func trimSpacesFromLines(data string) string {
388-
lines := strings.Split(data, "\n")
389-
for i, line := range lines {
390-
lines[i] = strings.TrimSpace(line)
391-
}
392-
return strings.Join(lines, "\n")
393-
}
394-
395378
// Parse an already loaded unit file (in the form of a string)
396379
func (f *UnitFile) Parse(data string) error {
397380
p := &UnitFileParser{
398-
file: f,
399-
lineNr: 1,
381+
file: f,
400382
}
401383

402-
data = trimSpacesFromLines(data)
403-
404-
for len(data) > 0 {
405-
origdata := data
406-
nLines := 1
407-
var line string
408-
line, data = nextLine(data, 0)
384+
lines := strings.Split(strings.TrimSuffix(data, "\n"), "\n")
385+
remaining := ""
409386

410-
if !lineIsComment(line) {
411-
// Handle multi-line continuations
412-
// Note: This doesn't support comments in the middle of the continuation, which systemd does
413-
if lineIsKeyValuePair(line) {
414-
for len(data) > 0 && line[len(line)-1] == '\\' {
415-
line, data = nextLine(origdata, len(line)+1)
416-
nLines++
387+
for lineNr, line := range lines {
388+
if lineIsComment(line) {
389+
// ignore the comment is inside a continuation line.
390+
if remaining != "" {
391+
continue
392+
}
393+
} else {
394+
line = strings.TrimSpace(line)
395+
if strings.HasSuffix(line, "\\") {
396+
line = line[:len(line)-1]
397+
// check whether the line is a continuation of the previous line
398+
if lineNr != len(lines)-1 {
399+
remaining += line
400+
continue
417401
}
418402
}
403+
if remaining != "" {
404+
line = remaining + line
405+
remaining = ""
406+
}
419407
}
420-
421-
if err := p.parseLine(line); err != nil {
408+
if err := p.parseLine(line, lineNr+1); err != nil {
422409
return err
423410
}
424-
425-
p.lineNr += nLines
426411
}
427412

428413
if p.currentGroup == nil {
@@ -690,7 +675,6 @@ func (f *UnitFile) LookupInt(groupName string, key string, defaultValue int64) i
690675
}
691676

692677
intVal, err := convertNumber(v)
693-
694678
if err != nil {
695679
return defaultValue
696680
}

pkg/systemd/parser/unitfile_test.go

+38-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package parser
22

33
import (
44
"reflect"
5+
"strings"
56
"testing"
67

78
"github.com/stretchr/testify/assert"
@@ -119,7 +120,10 @@ After=dbus.socket
119120
120121
[Service]
121122
BusName=org.freedesktop.login1
122-
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
123+
CapabilityBoundingSet=CAP_SYS_ADMIN CAP_MAC_ADMIN CAP_AUDIT_CONTROL CAP_CHOWN CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE \
124+
# comment inside a continuation line \
125+
CAP_FOWNER \
126+
CAP_SYS_TTY_CONFIG CAP_LINUX_IMMUTABLE
123127
DeviceAllow=block-* r
124128
DeviceAllow=char-/dev/console rw
125129
DeviceAllow=char-drm rw
@@ -158,8 +162,8 @@ SystemCallFilter=@system-service
158162
159163
# Increase the default a bit in order to allow many simultaneous logins since
160164
# we keep one fd open per session.
161-
LimitNOFILE=524288
162-
`
165+
LimitNOFILE=524288 \`
166+
163167
const systemdnetworkdService = `# SPDX-License-Identifier: LGPL-2.1-or-later
164168
#
165169
# This file is part of systemd.
@@ -264,6 +268,23 @@ var sampleDropinPaths = map[string][]string{
264268
sampleDropinTemplateInstance: sampleDropinTemplateInstancePaths,
265269
}
266270

271+
func filterComments(input string) string {
272+
lines := strings.Split(input, "\n")
273+
filtered := make([]string, 0, len(lines))
274+
for _, line := range lines {
275+
line = strings.TrimSpace(line)
276+
if strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") {
277+
continue
278+
}
279+
filtered = append(filtered, line)
280+
}
281+
// merge continuation lines
282+
joined := strings.ReplaceAll(strings.Join(filtered, "\n"), "\\\n", "")
283+
284+
// and remove any trailing new line, backslash or space
285+
return strings.TrimRight(joined, "\n\\ ")
286+
}
287+
267288
func TestRanges_Roundtrip(t *testing.T) {
268289
for i := range samples {
269290
sample := samples[i]
@@ -278,7 +299,7 @@ func TestRanges_Roundtrip(t *testing.T) {
278299
panic(e)
279300
}
280301

281-
assert.Equal(t, sample, asStr)
302+
assert.Equal(t, filterComments(sample), filterComments(asStr))
282303
}
283304
}
284305

@@ -292,3 +313,16 @@ func TestUnitDropinPaths_Search(t *testing.T) {
292313
assert.True(t, reflect.DeepEqual(expectedPaths, generatedPaths))
293314
}
294315
}
316+
317+
func FuzzParser(f *testing.F) {
318+
for _, sample := range samples {
319+
f.Add([]byte(sample))
320+
}
321+
322+
f.Fuzz(func(t *testing.T, orig []byte) {
323+
unitFile := NewUnitFile()
324+
unitFile.Path = "foo/bar"
325+
unitFile.Filename = "bar"
326+
_ = unitFile.Parse(string(orig))
327+
})
328+
}

test/e2e/quadlet/emptyline.container

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

0 commit comments

Comments
 (0)