Skip to content

Commit e01e43a

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 e01e43a

File tree

3 files changed

+50
-42
lines changed

3 files changed

+50
-42
lines changed

pkg/systemd/parser/unitfile.go

+18-38
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,34 @@ 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

387+
for lineNr, line := range lines {
410388
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++
389+
line = strings.TrimSpace(line)
390+
if strings.HasSuffix(line, "\\") {
391+
line = line[:len(line)-1]
392+
// check whether the line is a continuation of the previous line
393+
if lineNr != len(lines)-1 {
394+
remaining += line
395+
continue
417396
}
418397
}
398+
if remaining != "" {
399+
line = remaining + line
400+
remaining = ""
401+
}
419402
}
420-
421-
if err := p.parseLine(line); err != nil {
403+
if err := p.parseLine(line, lineNr+1); err != nil {
422404
return err
423405
}
424-
425-
p.lineNr += nLines
426406
}
427407

428408
if p.currentGroup == nil {

pkg/systemd/parser/unitfile_test.go

+28-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,9 @@ 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 # comment inside a continuation line \
124+
CAP_FOWNER \
125+
CAP_SYS_TTY_CONFIG CAP_LINUX_IMMUTABLE
123126
DeviceAllow=block-* r
124127
DeviceAllow=char-/dev/console rw
125128
DeviceAllow=char-drm rw
@@ -158,8 +161,8 @@ SystemCallFilter=@system-service
158161
159162
# Increase the default a bit in order to allow many simultaneous logins since
160163
# we keep one fd open per session.
161-
LimitNOFILE=524288
162-
`
164+
LimitNOFILE=524288 \`
165+
163166
const systemdnetworkdService = `# SPDX-License-Identifier: LGPL-2.1-or-later
164167
#
165168
# This file is part of systemd.
@@ -264,6 +267,14 @@ var sampleDropinPaths = map[string][]string{
264267
sampleDropinTemplateInstance: sampleDropinTemplateInstancePaths,
265268
}
266269

270+
func filterComments(input string) string {
271+
// merge continuation lines
272+
joined := strings.ReplaceAll(input, "\\\n", "")
273+
274+
// remove trailing newlines and backslashes
275+
return strings.TrimSuffix(strings.TrimSuffix(joined, "\n"), "\\")
276+
}
277+
267278
func TestRanges_Roundtrip(t *testing.T) {
268279
for i := range samples {
269280
sample := samples[i]
@@ -278,7 +289,7 @@ func TestRanges_Roundtrip(t *testing.T) {
278289
panic(e)
279290
}
280291

281-
assert.Equal(t, sample, asStr)
292+
assert.Equal(t, filterComments(sample), filterComments(asStr))
282293
}
283294
}
284295

@@ -292,3 +303,16 @@ func TestUnitDropinPaths_Search(t *testing.T) {
292303
assert.True(t, reflect.DeepEqual(expectedPaths, generatedPaths))
293304
}
294305
}
306+
307+
func FuzzParser(f *testing.F) {
308+
for _, sample := range samples {
309+
f.Add([]byte(sample))
310+
}
311+
312+
f.Fuzz(func(t *testing.T, orig []byte) {
313+
unitFile := NewUnitFile()
314+
unitFile.Path = "foo/bar"
315+
unitFile.Filename = "bar"
316+
_ = unitFile.Parse(string(orig))
317+
})
318+
}

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)