diff --git a/caddy/app.go b/caddy/app.go index b4f2d012b3..266322d788 100644 --- a/caddy/app.go +++ b/caddy/app.go @@ -6,7 +6,6 @@ import ( "fmt" "log/slog" "path/filepath" - "strconv" "sync" "sync/atomic" "time" @@ -308,12 +307,12 @@ func (f *FrankenPHPApp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { return d.ArgErr() } - v, err := strconv.ParseUint(d.Val(), 10, 32) + v, err := parseNonNegativeInt(d) if err != nil { return err } - f.NumThreads = int(v) + f.NumThreads = v case "max_threads": if !d.NextArg() { return d.ArgErr() @@ -324,12 +323,12 @@ func (f *FrankenPHPApp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { continue } - v, err := strconv.ParseUint(d.Val(), 10, 32) + v, err := parseNonNegativeInt(d) if err != nil { return err } - f.MaxThreads = int(v) + f.MaxThreads = v case "max_wait_time": if !d.NextArg() { return d.ArgErr() @@ -357,12 +356,12 @@ func (f *FrankenPHPApp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { return d.ArgErr() } - v, err := strconv.ParseUint(d.Val(), 10, 32) + v, err := parseNonNegativeInt(d) if err != nil { - return d.WrapErr(err) + return err } - f.MaxRequests = int(v) + f.MaxRequests = v case "php_ini": parseIniLine := func(d *caddyfile.Dispenser) error { key := d.Val() diff --git a/caddy/caddy.go b/caddy/caddy.go index 933e1186d9..05ca03ba25 100644 --- a/caddy/caddy.go +++ b/caddy/caddy.go @@ -6,9 +6,11 @@ package caddy import ( "context" "fmt" + "strconv" "time" "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" "github.com/dunglas/frankenphp" ) @@ -51,3 +53,16 @@ func init() { func wrongSubDirectiveError(module string, allowedDirectives string, wrongValue string) error { return fmt.Errorf("unknown %q subdirective: %s (allowed directives are: %s)", module, wrongValue, allowedDirectives) } + +// parseNonNegativeInt rejects negative values, which the runtime treats as sentinels (auto, default). +func parseNonNegativeInt(d *caddyfile.Dispenser) (int, error) { + v, err := strconv.Atoi(d.Val()) + if err != nil { + return 0, d.WrapErr(err) + } + if v < 0 { + return 0, d.Errf("%q must not be negative", d.Val()) + } + + return v, nil +} diff --git a/caddy/config_test.go b/caddy/config_test.go index 0598b42530..041c2a47bc 100644 --- a/caddy/config_test.go +++ b/caddy/config_test.go @@ -25,6 +25,17 @@ func TestWorkerOptionsKeepProvisionedOptions(t *testing.T) { require.Len(t, opts, base+1, "worker options set during provisioning must be forwarded") } +// negative counts are runtime sentinels (auto, default) and must not be reachable from the Caddyfile +func TestAppRejectsNegativeCounts(t *testing.T) { + for _, directive := range []string{"num_threads -1", "max_threads -1", "max_requests -1", "worker index.php -1", "worker {\n num -1\n }", "worker {\n max_threads -1\n }"} { + t.Run(directive, func(t *testing.T) { + d := caddyfile.NewTestDispenser("frankenphp {\n" + directive + "\n}") + + require.ErrorContains(t, (&FrankenPHPApp{}).UnmarshalCaddyfile(d), "must not be negative") + }) + } +} + func TestModuleRequestBodyTimeout(t *testing.T) { d := caddyfile.NewTestDispenser(` { diff --git a/caddy/php-server.go b/caddy/php-server.go index bebd8cbd8b..34d52d717f 100644 --- a/caddy/php-server.go +++ b/caddy/php-server.go @@ -91,12 +91,12 @@ func cmdPHPServer(fs caddycmd.Flags) (int, error) { for _, worker := range workers { parts := strings.SplitN(worker, ",", 2) - var num uint64 + var num int if len(parts) > 1 { - num, _ = strconv.ParseUint(parts[1], 10, 32) + num, _ = strconv.Atoi(parts[1]) } - workersOption = append(workersOption, workerConfig{FileName: parts[0], Num: int(num)}) + workersOption = append(workersOption, workerConfig{FileName: parts[0], Num: num}) } workersOption[0].Watch = watch } diff --git a/caddy/workerconfig.go b/caddy/workerconfig.go index ac022a3a6f..f229c71487 100644 --- a/caddy/workerconfig.go +++ b/caddy/workerconfig.go @@ -52,12 +52,12 @@ func unmarshalWorker(d *caddyfile.Dispenser) (workerConfig, error) { if d.Val() == "watch" { wc.Watch = append(wc.Watch, defaultWatchPattern) } else { - v, err := strconv.ParseUint(d.Val(), 10, 32) + v, err := parseNonNegativeInt(d) if err != nil { return wc, err } - wc.Num = int(v) + wc.Num = v } } @@ -82,23 +82,23 @@ func unmarshalWorker(d *caddyfile.Dispenser) (workerConfig, error) { return wc, d.ArgErr() } - v, err := strconv.ParseUint(d.Val(), 10, 32) + v, err := parseNonNegativeInt(d) if err != nil { - return wc, d.WrapErr(err) + return wc, err } - wc.Num = int(v) + wc.Num = v case "max_threads": if !d.NextArg() { return wc, d.ArgErr() } - v, err := strconv.ParseUint(d.Val(), 10, 32) + v, err := parseNonNegativeInt(d) if err != nil { - return wc, d.WrapErr(err) + return wc, err } - wc.MaxThreads = int(v) + wc.MaxThreads = v case "env": args := d.RemainingArgs() if len(args) != 2 {