Skip to content

Commit 2dfa85f

Browse files
committed
BUG/MEDIUM: runtime: reject special characters in filenames
Avoid command injection when calling the runtime API by rejecting dangerous characters which should never be used in filenames. The rejected characters: \r\n<>*;$#&{}" Note that we cannot simply add quotes around arguments in runtime commands since HAProxy does not parse them, unlike in the configuration file.
1 parent 7ceff1f commit 2dfa85f

File tree

11 files changed

+86
-0
lines changed

11 files changed

+86
-0
lines changed

runtime/runtime_single_client.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ const (
3333
masterSocket socketType = "master"
3434
)
3535

36+
var ErrRuntimeInvalidChar = errors.New("invalid character found in runtime command")
37+
3638
type socketType string
3739

3840
// SingleRuntime handles one runtime API
@@ -174,6 +176,10 @@ func (s *SingleRuntime) ExecuteMaster(command string) (string, error) {
174176
}
175177

176178
func (s *SingleRuntime) executeRaw(command string, retry int, socket socketType) (string, error) {
179+
// Make sure to only execute a single command.
180+
if strings.ContainsAny(command, ";") {
181+
return "", ErrRuntimeInvalidChar
182+
}
177183
result, err := s.readFromSocket(command, socket)
178184
if err != nil && retry > 0 {
179185
retry--
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2026 HAProxy Technologies
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
16+
package runtime_test
17+
18+
import (
19+
"errors"
20+
"testing"
21+
22+
"github.com/haproxytech/client-native/v6/runtime"
23+
)
24+
25+
func TestSingleRuntime_Execute(t *testing.T) {
26+
tests := []struct {
27+
name string // description of this test case
28+
command string
29+
wantErr error
30+
}{
31+
{
32+
name: "two commands with semicolon",
33+
command: "show ssl cert foo;dump ssl foo",
34+
wantErr: runtime.ErrRuntimeInvalidChar,
35+
},
36+
}
37+
for _, tt := range tests {
38+
t.Run(tt.name, func(t *testing.T) {
39+
var s runtime.SingleRuntime
40+
gotErr := s.Execute(tt.command)
41+
if !errors.Is(gotErr, tt.wantErr) {
42+
t.Errorf("got %v, expected %v", gotErr, tt.wantErr)
43+
}
44+
})
45+
}
46+
}

specification/build/haproxy_spec.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10848,6 +10848,7 @@ parameters:
1084810848
description: Parent name
1084910849
required: true
1085010850
type: string
10851+
pattern: '^[^\r\n&lt;>*;$#&{}"]+$'
1085110852
full_section:
1085210853
name: full_section
1085310854
in: query
@@ -22324,6 +22325,7 @@ paths:
2232422325
- description: ACL file entry ID
2232522326
in: path
2232622327
name: id
22328+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2232722329
required: true
2232822330
type: string
2232922331
produces:
@@ -22416,6 +22418,7 @@ paths:
2241622418
- description: File entry ID
2241722419
in: path
2241822420
name: id
22421+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2241922422
required: true
2242022423
type: string
2242122424
produces:
@@ -22439,6 +22442,7 @@ paths:
2243922442
- description: File entry ID
2244022443
in: path
2244122444
name: id
22445+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2244222446
required: true
2244322447
type: string
2244422448
produces:
@@ -22523,6 +22527,7 @@ paths:
2252322527
- description: Server name
2252422528
in: path
2252522529
name: name
22530+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2252622531
required: true
2252722532
type: string
2252822533
- $ref: '#/parameters/parent_name'
@@ -22545,6 +22550,7 @@ paths:
2254522550
- description: Server name
2254622551
in: path
2254722552
name: name
22553+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2254822554
required: true
2254922555
type: string
2255022556
- $ref: '#/parameters/parent_name'
@@ -22567,6 +22573,7 @@ paths:
2256722573
- description: Server name
2256822574
in: path
2256922575
name: name
22576+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2257022577
required: true
2257122578
type: string
2257222579
- $ref: '#/parameters/parent_name'
@@ -22611,6 +22618,7 @@ paths:
2261122618
- description: Stick table name
2261222619
in: path
2261322620
name: name
22621+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2261422622
required: true
2261522623
type: string
2261622624
responses:
@@ -22634,10 +22642,12 @@ paths:
2263422642
- description: A list of filters in format data.<type> <operator> <value> separated by comma
2263522643
in: query
2263622644
name: filter
22645+
pattern: ^[^\r\n;#]+$
2263722646
type: string
2263822647
- description: Key which we want the entries for
2263922648
in: query
2264022649
name: key
22650+
pattern: ^[^\r\n;#]+$
2264122651
type: string
2264222652
- description: Max number of entries to be returned for pagination
2264322653
in: query
@@ -22670,6 +22680,7 @@ paths:
2267022680
data_type:
2267122681
$ref: '#/definitions/stick_table_entry'
2267222682
key:
22683+
pattern: ^[^\r\n;#]+$
2267322684
type: string
2267422685
required:
2267522686
- key
@@ -22714,6 +22725,7 @@ paths:
2271422725
- description: Map file name
2271522726
in: path
2271622727
name: name
22728+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2271722729
required: true
2271822730
type: string
2271922731
- description: If true, deletes file from disk
@@ -22742,6 +22754,7 @@ paths:
2274222754
- description: Map file name
2274322755
in: path
2274422756
name: name
22757+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2274522758
required: true
2274622759
type: string
2274722760
responses:
@@ -22763,6 +22776,7 @@ paths:
2276322776
- description: Map file name
2276422777
in: path
2276522778
name: name
22779+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2276622780
required: true
2276722781
type: string
2276822782
- default: false
@@ -22842,6 +22856,7 @@ paths:
2284222856
- description: Map id
2284322857
in: path
2284422858
name: id
22859+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2284522860
required: true
2284622861
type: string
2284722862
- $ref: '#/parameters/parent_name'
@@ -22867,6 +22882,7 @@ paths:
2286722882
- description: Map id
2286822883
in: path
2286922884
name: id
22885+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2287022886
required: true
2287122887
type: string
2287222888
- $ref: '#/parameters/parent_name'
@@ -22889,6 +22905,7 @@ paths:
2288922905
- description: Map id
2289022906
in: path
2289122907
name: id
22908+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2289222909
required: true
2289322910
type: string
2289422911
- $ref: '#/parameters/parent_name'

specification/haproxy-spec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,7 @@ parameters:
720720
description: Parent name
721721
required: true
722722
type: string
723+
pattern: '^[^\r\n<>*;$#&{}"]+$'
723724
full_section:
724725
name: full_section
725726
in: query

specification/paths/runtime/acls.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ acls_one:
2828
description: ACL file entry ID
2929
required: true
3030
type: string
31+
pattern: '^[^\r\n<>*;$#&{}"]+$'
3132
responses:
3233
'200':
3334
description: Successful operation

specification/paths/runtime/acls_entries.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ acls_entries_one:
1414
description: File entry ID
1515
required: true
1616
type: string
17+
pattern: '^[^\r\n<>*;$#&{}"]+$'
1718
responses:
1819
'200':
1920
description: Successful operation
@@ -39,6 +40,7 @@ acls_entries_one:
3940
description: File entry ID
4041
required: true
4142
type: string
43+
pattern: '^[^\r\n<>*;$#&{}"]+$'
4244
responses:
4345
'204':
4446
description: Successful operation

specification/paths/runtime/maps.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ maps_one:
3535
description: Map file name
3636
required: true
3737
type: string
38+
pattern: '^[^\r\n<>*;$#&{}"]+$'
3839
responses:
3940
'200':
4041
description: Successful operation
@@ -54,6 +55,7 @@ maps_one:
5455
description: Map file name
5556
required: true
5657
type: string
58+
pattern: '^[^\r\n<>*;$#&{}"]+$'
5759
- name: forceDelete
5860
in: query
5961
description: If true, deletes file from disk
@@ -82,6 +84,7 @@ maps_one:
8284
description: Map file name
8385
required: true
8486
type: string
87+
pattern: '^[^\r\n<>*;$#&{}"]+$'
8588
- name: force_sync
8689
in: query
8790
description: If true, immediately syncs changes to disk

specification/paths/runtime/maps_entries.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ maps_entries_one:
5959
description: Map id
6060
required: true
6161
type: string
62+
pattern: '^[^\r\n<>*;$#&{}"]+$'
6263
- $ref: "#/parameters/parent_name"
6364
responses:
6465
'200':
@@ -81,6 +82,7 @@ maps_entries_one:
8182
description: Map id
8283
required: true
8384
type: string
85+
pattern: '^[^\r\n<>*;$#&{}"]+$'
8486
- $ref: "#/parameters/parent_name"
8587
- name: force_sync
8688
in: query
@@ -121,6 +123,7 @@ maps_entries_one:
121123
description: Map id
122124
required: true
123125
type: string
126+
pattern: '^[^\r\n<>*;$#&{}"]+$'
124127
- $ref: "#/parameters/parent_name"
125128
- name: force_sync
126129
in: query

specification/paths/runtime/servers.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ servers_one:
5454
description: Server name
5555
required: true
5656
type: string
57+
pattern: '^[^\r\n<>*;$#&{}"]+$'
5758
- $ref: "#/parameters/parent_name"
5859
responses:
5960
'200':
@@ -76,6 +77,7 @@ servers_one:
7677
description: Server name
7778
required: true
7879
type: string
80+
pattern: '^[^\r\n<>*;$#&{}"]+$'
7981
- $ref: "#/parameters/parent_name"
8082
- name: data
8183
in: body
@@ -105,6 +107,7 @@ servers_one:
105107
description: Server name
106108
required: true
107109
type: string
110+
pattern: '^[^\r\n<>*;$#&{}"]+$'
108111
- $ref: "#/parameters/parent_name"
109112
responses:
110113
'204':

specification/paths/runtime/stick_table_entries.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ stick_table_entries:
1919
properties:
2020
key:
2121
type: string
22+
pattern: '^[^\r\n;#]+$'
2223
data_type:
2324
$ref: "#/definitions/stick_table_entry"
2425
responses:
@@ -38,10 +39,12 @@ stick_table_entries:
3839
in: query
3940
description: A list of filters in format data.<type> <operator> <value> separated by comma
4041
type: string
42+
pattern: '^[^\r\n;#]+$'
4143
- name: key
4244
in: query
4345
description: Key which we want the entries for
4446
type: string
47+
pattern: '^[^\r\n;#]+$'
4548
- name: count
4649
in: query
4750
description: Max number of entries to be returned for pagination

0 commit comments

Comments
 (0)