Skip to content

Commit e862dfc

Browse files
authored
maxConfigFileSize: reasonable 64 MB limit for config file parsing (#187)
1 parent c6a63c2 commit e862dfc

1 file changed

Lines changed: 27 additions & 15 deletions

File tree

parser/parser.go

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ const (
3535
Toml = "toml"
3636
)
3737

38+
// maxTextScanTokenSize bounds how large a single line the "file" parser will buffer.
39+
const maxTextScanTokenSize = 64 * 1024 * 1024
40+
41+
// maxConfigFileSize caps how large a configuration file we'll attempt to parse.
42+
const maxConfigFileSize = 64 * 1024 * 1024
43+
3844
type ReplaceValue struct {
3945
value []byte
4046
valueType jsonparser.ValueType
@@ -234,6 +240,16 @@ func newTemplatableConfig(c *config.Configuration) templatableConfig {
234240
func (f *ConfigurationFile) Parse(file ufs.File) error {
235241
//log.WithField("path", path).WithField("parser", f.Parser.String()).Debug("parsing server configuration file")
236242

243+
// Refuse to parse files larger than the cap. Every parser below buffers the
244+
// whole file in memory, and the contents are untrusted server-owned input, so
245+
// this guards the daemon against being OOM'd by an oversized config. The
246+
// server is still free to boot with the file as-is; we just don't rewrite it.
247+
if info, err := file.Stat(); err != nil {
248+
return err
249+
} else if info.Size() > maxConfigFileSize {
250+
return errors.Errorf("parser: refusing to parse configuration file %q: size %d exceeds limit of %d bytes", file.Name(), info.Size(), maxConfigFileSize)
251+
}
252+
237253
// What the fuck is going on here?
238254
if mb, err := json.Marshal(newTemplatableConfig(config.Get())); err != nil {
239255
return err
@@ -265,7 +281,7 @@ func (f *ConfigurationFile) Parse(file ufs.File) error {
265281
// Parses an xml file.
266282
func (f *ConfigurationFile) parseXmlFile(file ufs.File) error {
267283
doc := etree.NewDocument()
268-
if _, err := doc.ReadFrom(file); err != nil {
284+
if _, err := doc.ReadFrom(io.LimitReader(file, maxConfigFileSize)); err != nil {
269285
return err
270286
}
271287

@@ -345,7 +361,7 @@ func (f *ConfigurationFile) parseXmlFile(file ufs.File) error {
345361
// Parses an ini file.
346362
func (f *ConfigurationFile) parseIniFile(file ufs.File) error {
347363
// Wrap the file in a NopCloser so the ini package doesn't close the file.
348-
cfg, err := ini.Load(io.NopCloser(file))
364+
cfg, err := ini.Load(io.NopCloser(io.LimitReader(file, maxConfigFileSize)))
349365
if err != nil {
350366
return err
351367
}
@@ -429,7 +445,7 @@ func (f *ConfigurationFile) parseIniFile(file ufs.File) error {
429445
// value is set regardless in the file. See the commentary in parseYamlFile for more details
430446
// about what is happening during this process.
431447
func (f *ConfigurationFile) parseJsonFile(file ufs.File) error {
432-
b, err := io.ReadAll(file)
448+
b, err := io.ReadAll(io.LimitReader(file, maxConfigFileSize))
433449
if err != nil {
434450
return err
435451
}
@@ -462,7 +478,7 @@ func (f *ConfigurationFile) parseJsonFile(file ufs.File) error {
462478
// Parses a yaml file and updates any matching key/value pairs before persisting
463479
// it back to the disk.
464480
func (f *ConfigurationFile) parseYamlFile(file ufs.File) error {
465-
b, err := io.ReadAll(file)
481+
b, err := io.ReadAll(io.LimitReader(file, maxConfigFileSize))
466482
if err != nil {
467483
return err
468484
}
@@ -517,7 +533,7 @@ func (f *ConfigurationFile) parseYamlFile(file ufs.File) error {
517533
// Parses a toml file and updates any matching key/value pairs before persisting
518534
// it back to the disk.
519535
func (f *ConfigurationFile) parseTomlFile(file ufs.File) error {
520-
b, err := io.ReadAll(file)
536+
b, err := io.ReadAll(io.LimitReader(file, maxConfigFileSize))
521537
if err != nil {
522538
return err
523539
}
@@ -640,7 +656,8 @@ func normalizeTomlTypes(value interface{}) interface{} {
640656
// than this function where possible.
641657
func (f *ConfigurationFile) parseTextFile(file ufs.File) error {
642658
b := bytes.NewBuffer(nil)
643-
s := bufio.NewScanner(file)
659+
s := bufio.NewScanner(io.LimitReader(file, maxConfigFileSize))
660+
s.Buffer(make([]byte, 0, 64*1024), maxTextScanTokenSize)
644661
var replaced bool
645662
for s.Scan() {
646663
line := s.Bytes()
@@ -651,14 +668,6 @@ func (f *ConfigurationFile) parseTextFile(file ufs.File) error {
651668
if !bytes.HasPrefix(line, []byte(replace.Match)) {
652669
continue
653670
}
654-
// If an if_value is set, only replace when the remainder of the line matches.
655-
// Trim trailing \r\n so Windows line endings do not break the comparison.
656-
if replace.IfValue != "" {
657-
remainder := bytes.TrimRight(bytes.TrimPrefix(line, []byte(replace.Match)), "\r\n")
658-
if string(remainder) != replace.IfValue {
659-
continue
660-
}
661-
}
662671
b.Write(replace.ReplaceWith.Bytes())
663672
replaced = true
664673
}
@@ -667,6 +676,9 @@ func (f *ConfigurationFile) parseTextFile(file ufs.File) error {
667676
}
668677
b.WriteByte('\n')
669678
}
679+
if err := s.Err(); err != nil {
680+
return errors.Wrap(err, "parser: failed to scan text file for configuration update")
681+
}
670682

671683
if _, err := file.Seek(0, io.SeekStart); err != nil {
672684
return err
@@ -709,7 +721,7 @@ func (f *ConfigurationFile) parseTextFile(file ufs.File) error {
709721
// @see https://github.com/pterodactyl/panel/issues/2308 (original)
710722
// @see https://github.com/pterodactyl/panel/issues/3009 ("bug" introduced as result)
711723
func (f *ConfigurationFile) parsePropertiesFile(file ufs.File) error {
712-
b, err := io.ReadAll(file)
724+
b, err := io.ReadAll(io.LimitReader(file, maxConfigFileSize))
713725
if err != nil {
714726
return err
715727
}

0 commit comments

Comments
 (0)