Skip to content

Commit 609b44c

Browse files
authored
fix(caddy): parse thread and request counts as int (#2722)
`num_threads`, `max_threads`, `max_requests`, the worker `num` and `max_threads` options, and `php-server --worker file,N` are parsed with `strconv.ParseUint(s, 10, 32)` and then converted to `int`. On 32-bit platforms (we ship `linux/386`, `linux/arm/v6` and `linux/arm/v7` images), values between 2^31 and 2^32-1 wrap to negative numbers: `max_threads 4294967295` becomes `-1`, which means `auto`. These values are now parsed with `strconv.Atoi` through a `parseNonNegativeInt` helper. Nothing gets converted, so nothing can wrap, and out-of-range input returns a range error on every platform. Negative values are still rejected explicitly, because the runtime treats them as sentinels (auto thread count, default worker count). 64-bit builds accept the same values as before. Reported by CodeQL (`go/incorrect-integer-conversion`) while setting it up in #2720.
1 parent 5e3e36c commit 609b44c

5 files changed

Lines changed: 44 additions & 19 deletions

File tree

‎caddy/app.go‎

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"fmt"
77
"log/slog"
88
"path/filepath"
9-
"strconv"
109
"sync"
1110
"sync/atomic"
1211
"time"
@@ -308,12 +307,12 @@ func (f *FrankenPHPApp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
308307
return d.ArgErr()
309308
}
310309

311-
v, err := strconv.ParseUint(d.Val(), 10, 32)
310+
v, err := parseNonNegativeInt(d)
312311
if err != nil {
313312
return err
314313
}
315314

316-
f.NumThreads = int(v)
315+
f.NumThreads = v
317316
case "max_threads":
318317
if !d.NextArg() {
319318
return d.ArgErr()
@@ -324,12 +323,12 @@ func (f *FrankenPHPApp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
324323
continue
325324
}
326325

327-
v, err := strconv.ParseUint(d.Val(), 10, 32)
326+
v, err := parseNonNegativeInt(d)
328327
if err != nil {
329328
return err
330329
}
331330

332-
f.MaxThreads = int(v)
331+
f.MaxThreads = v
333332
case "max_wait_time":
334333
if !d.NextArg() {
335334
return d.ArgErr()
@@ -357,12 +356,12 @@ func (f *FrankenPHPApp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
357356
return d.ArgErr()
358357
}
359358

360-
v, err := strconv.ParseUint(d.Val(), 10, 32)
359+
v, err := parseNonNegativeInt(d)
361360
if err != nil {
362-
return d.WrapErr(err)
361+
return err
363362
}
364363

365-
f.MaxRequests = int(v)
364+
f.MaxRequests = v
366365
case "php_ini":
367366
parseIniLine := func(d *caddyfile.Dispenser) error {
368367
key := d.Val()

‎caddy/caddy.go‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ package caddy
66
import (
77
"context"
88
"fmt"
9+
"strconv"
910
"time"
1011

1112
"github.com/caddyserver/caddy/v2"
13+
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
1214
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
1315
"github.com/dunglas/frankenphp"
1416
)
@@ -51,3 +53,16 @@ func init() {
5153
func wrongSubDirectiveError(module string, allowedDirectives string, wrongValue string) error {
5254
return fmt.Errorf("unknown %q subdirective: %s (allowed directives are: %s)", module, wrongValue, allowedDirectives)
5355
}
56+
57+
// parseNonNegativeInt rejects negative values, which the runtime treats as sentinels (auto, default).
58+
func parseNonNegativeInt(d *caddyfile.Dispenser) (int, error) {
59+
v, err := strconv.Atoi(d.Val())
60+
if err != nil {
61+
return 0, d.WrapErr(err)
62+
}
63+
if v < 0 {
64+
return 0, d.Errf("%q must not be negative", d.Val())
65+
}
66+
67+
return v, nil
68+
}

‎caddy/config_test.go‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,17 @@ func TestWorkerOptionsKeepProvisionedOptions(t *testing.T) {
2525
require.Len(t, opts, base+1, "worker options set during provisioning must be forwarded")
2626
}
2727

28+
// negative counts are runtime sentinels (auto, default) and must not be reachable from the Caddyfile
29+
func TestAppRejectsNegativeCounts(t *testing.T) {
30+
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 }"} {
31+
t.Run(directive, func(t *testing.T) {
32+
d := caddyfile.NewTestDispenser("frankenphp {\n" + directive + "\n}")
33+
34+
require.ErrorContains(t, (&FrankenPHPApp{}).UnmarshalCaddyfile(d), "must not be negative")
35+
})
36+
}
37+
}
38+
2839
func TestModuleRequestBodyTimeout(t *testing.T) {
2940
d := caddyfile.NewTestDispenser(`
3041
{

‎caddy/php-server.go‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,12 @@ func cmdPHPServer(fs caddycmd.Flags) (int, error) {
9191
for _, worker := range workers {
9292
parts := strings.SplitN(worker, ",", 2)
9393

94-
var num uint64
94+
var num int
9595
if len(parts) > 1 {
96-
num, _ = strconv.ParseUint(parts[1], 10, 32)
96+
num, _ = strconv.Atoi(parts[1])
9797
}
9898

99-
workersOption = append(workersOption, workerConfig{FileName: parts[0], Num: int(num)})
99+
workersOption = append(workersOption, workerConfig{FileName: parts[0], Num: num})
100100
}
101101
workersOption[0].Watch = watch
102102
}

‎caddy/workerconfig.go‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,12 @@ func unmarshalWorker(d *caddyfile.Dispenser) (workerConfig, error) {
5252
if d.Val() == "watch" {
5353
wc.Watch = append(wc.Watch, defaultWatchPattern)
5454
} else {
55-
v, err := strconv.ParseUint(d.Val(), 10, 32)
55+
v, err := parseNonNegativeInt(d)
5656
if err != nil {
5757
return wc, err
5858
}
5959

60-
wc.Num = int(v)
60+
wc.Num = v
6161
}
6262
}
6363

@@ -82,23 +82,23 @@ func unmarshalWorker(d *caddyfile.Dispenser) (workerConfig, error) {
8282
return wc, d.ArgErr()
8383
}
8484

85-
v, err := strconv.ParseUint(d.Val(), 10, 32)
85+
v, err := parseNonNegativeInt(d)
8686
if err != nil {
87-
return wc, d.WrapErr(err)
87+
return wc, err
8888
}
8989

90-
wc.Num = int(v)
90+
wc.Num = v
9191
case "max_threads":
9292
if !d.NextArg() {
9393
return wc, d.ArgErr()
9494
}
9595

96-
v, err := strconv.ParseUint(d.Val(), 10, 32)
96+
v, err := parseNonNegativeInt(d)
9797
if err != nil {
98-
return wc, d.WrapErr(err)
98+
return wc, err
9999
}
100100

101-
wc.MaxThreads = int(v)
101+
wc.MaxThreads = v
102102
case "env":
103103
args := d.RemainingArgs()
104104
if len(args) != 2 {

0 commit comments

Comments
 (0)