Skip to content

Commit 62834a2

Browse files
authored
Asserting every domain is an collection type before evaluation (#6763)
Fixing an issue where a non-collection `every`-domain didn’t fail evaluation. Removing a possible attack surface, where an attacker with the ability to craft portions of the input document could replace a value with an expected collection type, that is known to be processed by an `every`-statement, with a non-collection value and thereby would cause the policy to accept a query that should otherwise be rejected. Fixes: #6762 Signed-off-by: Johan Fylling <[email protected]>
1 parent 27da341 commit 62834a2

6 files changed

Lines changed: 329 additions & 21 deletions

File tree

internal/compiler/wasm/wasm.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,6 +1139,17 @@ func (c *Compiler) compileBlock(block *ir.Block) ([]instruction.Instruction, err
11391139
instrs = append(instrs, instruction.Br{Index: 0})
11401140
break
11411141
}
1142+
case *ir.IsSetStmt:
1143+
if loc, ok := stmt.Source.Value.(ir.Local); ok {
1144+
instrs = append(instrs, instruction.GetLocal{Index: c.local(loc)})
1145+
instrs = append(instrs, instruction.Call{Index: c.function(opaValueType)})
1146+
instrs = append(instrs, instruction.I32Const{Value: opaTypeSet})
1147+
instrs = append(instrs, instruction.I32Ne{})
1148+
instrs = append(instrs, instruction.BrIf{Index: 0})
1149+
} else {
1150+
instrs = append(instrs, instruction.Br{Index: 0})
1151+
break
1152+
}
11421153
case *ir.IsUndefinedStmt:
11431154
instrs = append(instrs, instruction.GetLocal{Index: c.local(stmt.Source)})
11441155
instrs = append(instrs, instruction.I32Const{Value: 0})

internal/planner/planner.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -893,6 +893,40 @@ func (p *Planner) planExprEvery(e *ast.Expr, iter planiter) error {
893893
})
894894

895895
err := p.planTerm(every.Domain, func() error {
896+
// Assert that the domain is a collection type:
897+
// block outer
898+
// block a
899+
// isArray
900+
// br 1: break outer, and continue
901+
// block b
902+
// isObject
903+
// br 1: break outer, and continue
904+
// block c
905+
// isSet
906+
// br 1: break outer, and continue
907+
// br 1: invalid domain, break every
908+
909+
aBlock := &ir.Block{}
910+
p.appendStmtToBlock(&ir.IsArrayStmt{Source: p.ltarget}, aBlock)
911+
p.appendStmtToBlock(&ir.BreakStmt{Index: 1}, aBlock)
912+
913+
bBlock := &ir.Block{}
914+
p.appendStmtToBlock(&ir.IsObjectStmt{Source: p.ltarget}, bBlock)
915+
p.appendStmtToBlock(&ir.BreakStmt{Index: 1}, bBlock)
916+
917+
cBlock := &ir.Block{}
918+
p.appendStmtToBlock(&ir.IsSetStmt{Source: p.ltarget}, cBlock)
919+
p.appendStmtToBlock(&ir.BreakStmt{Index: 1}, cBlock)
920+
921+
outerBlock := &ir.BlockStmt{Blocks: []*ir.Block{
922+
{
923+
Stmts: []ir.Stmt{
924+
&ir.BlockStmt{Blocks: []*ir.Block{aBlock, bBlock, cBlock}},
925+
&ir.BreakStmt{Index: 1}},
926+
},
927+
}}
928+
p.appendStmt(outerBlock)
929+
896930
return p.planScan(every.Key, func(ir.Local) error {
897931
p.appendStmt(&ir.ResetLocalStmt{
898932
Target: cond1,

ir/ir.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,13 @@ type IsObjectStmt struct {
364364
Location
365365
}
366366

367+
// IsSetStmt represents a dynamic type check on a local variable.
368+
type IsSetStmt struct {
369+
Source Operand `json:"source"`
370+
371+
Location
372+
}
373+
367374
// IsDefinedStmt represents a check of whether a local variable is defined.
368375
type IsDefinedStmt struct {
369376
Source Local `json:"source"`

test/cases/testdata/every/every.yaml

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,50 @@ cases:
99
p {
1010
every x in [] { x != x }
1111
}
12-
note: every/empty domain
12+
note: every/empty domain (array)
13+
query: data.test.p = x
14+
want_result:
15+
- x: true
16+
- data:
17+
modules:
18+
- |
19+
package test
20+
import future.keywords.every
21+
22+
p {
23+
every x in set() { x != x }
24+
}
25+
note: every/empty domain (set)
26+
query: data.test.p = x
27+
want_result:
28+
- x: true
29+
- data:
30+
modules:
31+
- |
32+
package test
33+
import future.keywords.every
34+
35+
p {
36+
every x in {} { x != x }
37+
}
38+
note: every/empty domain (object)
39+
query: data.test.p = x
40+
want_result:
41+
- x: true
42+
- data:
43+
modules:
44+
- |
45+
package test
46+
import future.keywords.every
47+
48+
l[1] {
49+
false
50+
}
51+
52+
p {
53+
every x in l { x != x }
54+
}
55+
note: every/empty domain (partial rule ref)
1356
query: data.test.p = x
1457
want_result:
1558
- x: true
@@ -22,7 +65,19 @@ cases:
2265
p {
2366
every _ in input { true }
2467
}
25-
note: every/domain undefined
68+
note: every/domain undefined (input)
69+
query: data.test.p = x
70+
want_result: []
71+
- data:
72+
modules:
73+
- |
74+
package test
75+
import future.keywords.every
76+
77+
p {
78+
every _ in data.foo { true }
79+
}
80+
note: every/domain undefined (data ref)
2681
query: data.test.p = x
2782
want_result: []
2883
- data:
@@ -57,6 +112,40 @@ cases:
57112
package test
58113
import future.keywords.every
59114
115+
p {
116+
every k, v in {1, 2} { k == v }
117+
}
118+
note: every/simple key/val (set)
119+
query: data.test.p = x
120+
want_result:
121+
- x: true
122+
- data:
123+
modules:
124+
- |
125+
package test
126+
import future.keywords.every
127+
128+
l[1] {
129+
true
130+
}
131+
132+
l[2] {
133+
true
134+
}
135+
136+
p {
137+
every k, v in l { k == v }
138+
}
139+
note: every/simple key/val (partial rule ref)
140+
query: data.test.p = x
141+
want_result:
142+
- x: true
143+
- data:
144+
modules:
145+
- |
146+
package test
147+
import future.keywords.every
148+
60149
p {
61150
i := 10
62151
every k, v in [1, 2] { k+v != i }
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
---
2+
cases:
3+
- note: "every/non-iter domain: int"
4+
modules:
5+
- |
6+
package test
7+
import future.keywords.every
8+
9+
default p := 1
10+
11+
p := 2 {
12+
every v in 42 { v > 1 }
13+
}
14+
query: data.test.p = x
15+
want_result:
16+
- x: 1
17+
- note: "every/non-iter domain: string"
18+
modules:
19+
- |
20+
package test
21+
import future.keywords.every
22+
23+
default p := 1
24+
25+
p := 2 {
26+
every v in "foobar" { v > 1 }
27+
}
28+
query: data.test.p = x
29+
want_result:
30+
- x: 1
31+
- note: "every/non-iter domain: bool"
32+
modules:
33+
- |
34+
package test
35+
import future.keywords.every
36+
37+
default p := 1
38+
39+
p := 2 {
40+
every v in true { v > 1 }
41+
}
42+
query: data.test.p = x
43+
want_result:
44+
- x: 1
45+
- note: "every/non-iter domain: null"
46+
modules:
47+
- |
48+
package test
49+
import future.keywords.every
50+
51+
default p := 1
52+
53+
p := 2 {
54+
every v in null { v > 1 }
55+
}
56+
query: data.test.p = x
57+
want_result:
58+
- x: 1
59+
- note: "every/non-iter domain: built-in call"
60+
modules:
61+
- |
62+
package test
63+
import future.keywords.every
64+
65+
default p := 1
66+
67+
p := 2 {
68+
every v in floor(13.37) { v > 1 }
69+
}
70+
query: data.test.p = x
71+
want_result:
72+
- x: 1
73+
- note: "every/non-iter domain: function call"
74+
modules:
75+
- |
76+
package test
77+
import future.keywords.every
78+
79+
default p := 1
80+
81+
p := 2 {
82+
every v in foo(1, 2) { v > 1 }
83+
}
84+
85+
foo(a, b) := a + b
86+
query: data.test.p = x
87+
want_result:
88+
- x: 1
89+
- note: "every/non-iter domain: rule ref"
90+
modules:
91+
- |
92+
package test
93+
import future.keywords.every
94+
95+
default p := 1
96+
97+
p := 2 {
98+
every v in q { v > 1 }
99+
}
100+
101+
q := 1
102+
query: data.test.p = x
103+
want_result:
104+
- x: 1
105+
- note: "every/non-iter domain: data int"
106+
modules:
107+
- |
108+
package test
109+
import future.keywords.every
110+
111+
default p := 1
112+
113+
p := 2 {
114+
every v in data.iterate_me { v > 1 }
115+
}
116+
query: data.test.p = x
117+
data:
118+
iterate_me: 1
119+
want_result:
120+
- x: 1
121+
- note: "every/non-iter domain: input int"
122+
modules:
123+
- |
124+
package test
125+
import future.keywords.every
126+
127+
default p := 1
128+
129+
p := 2 {
130+
every v in input.iterate_me { v > 1 }
131+
}
132+
query: data.test.p = x
133+
input:
134+
iterate_me: 1
135+
want_result:
136+
- x: 1
137+
- note: "every/non-iter domain: input int (1st level)"
138+
modules:
139+
- |
140+
package test
141+
import future.keywords.every
142+
143+
default p := 1
144+
145+
p := 2 {
146+
every v in input { v > 1 }
147+
}
148+
query: data.test.p = x
149+
input: 1
150+
want_result:
151+
- x: 1

0 commit comments

Comments
 (0)