Skip to content

Commit 77efb6c

Browse files
owen-mcCopilot
andcommitted
Go: migrate simple guard consumers
Move simple data-flow and security consumers to shared guards, including value-aware barriers and the corrected feature-flag semantics. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent f3d9476 commit 77efb6c

11 files changed

Lines changed: 69 additions & 54 deletions

File tree

‎go/ql/lib/semmle/go/dataflow/ExternalFlow.qll‎

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ overlay[local?]
104104
module;
105105

106106
private import go
107+
private import semmle.go.controlflow.Guards
107108
private import internal.ExternalFlowExtensions::Extensions as Extensions
108109
private import FlowSummary as FlowSummary
109110
private import internal.DataFlowPrivate
@@ -459,24 +460,23 @@ private module Cached {
459460
private newtype TKindModelPair =
460461
TMkPair(string kind, string model) { isBarrierGuardNode(_, _, kind, model) }
461462

462-
private boolean convertAcceptingValue(Public::AcceptingValue av) {
463-
av.isTrue() and result = true
463+
private GuardValue convertAcceptingValue(Public::AcceptingValue av) {
464+
av.isTrue() and result.asBooleanValue() = true
464465
or
465-
av.isFalse() and result = false
466-
// Remaining cases are not supported yet, they depend on the shared Guards library.
467-
// or
468-
// av.isNoException() and result.getDualValue().isThrowsException()
469-
// or
470-
// av.isZero() and result.asIntValue() = 0
471-
// or
472-
// av.isNotZero() and result.getDualValue().asIntValue() = 0
473-
// or
474-
// av.isNull() and result.isNullValue()
475-
// or
476-
// av.isNotNull() and result.isNonNullValue()
466+
av.isFalse() and result.asBooleanValue() = false
467+
or
468+
av.isNoException() and result.getDualValue().isThrowsException()
469+
or
470+
av.isZero() and result.asIntValue() = 0
471+
or
472+
av.isNotZero() and result.getDualValue().asIntValue() = 0
473+
or
474+
av.isNull() and result.isNullValue()
475+
or
476+
av.isNotNull() and result.isNonNullValue()
477477
}
478478

479-
private predicate barrierGuardChecks(DataFlow::Node g, Expr e, boolean gv, TKindModelPair kmp) {
479+
private predicate barrierGuardChecks(Guard g, Expr e, GuardValue gv, TKindModelPair kmp) {
480480
exists(
481481
SourceSinkInterpretationInput::InterpretNode n, Public::AcceptingValue acceptingValue,
482482
string kind, string model
@@ -486,7 +486,7 @@ private module Cached {
486486
kmp = TMkPair(kind, model) and
487487
gv = convertAcceptingValue(acceptingValue)
488488
|
489-
g.asExpr().(CallExpr).getAnArgument() = e // TODO: qualifier?
489+
g.(CallExpr).getAnArgument() = e // TODO: qualifier?
490490
)
491491
}
492492

@@ -500,7 +500,7 @@ private module Cached {
500500
isBarrierNode(n, kind, model) and n.asNode() = node
501501
)
502502
or
503-
DataFlow::ParameterizedBarrierGuard<TKindModelPair, barrierGuardChecks/4>::getABarrierNode(TMkPair(kind,
503+
DataFlow::ParameterizedBarrierGuardValue<TKindModelPair, barrierGuardChecks/4>::getABarrierNode(TMkPair(kind,
504504
model)) = node
505505
}
506506
}

‎go/ql/lib/semmle/go/dataflow/internal/DataFlowPrivate.qll‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ overlay[local?]
22
module;
33

44
private import go
5+
private import semmle.go.controlflow.Guards
56
private import DataFlowUtil
67
private import DataFlowImplCommon
78
private import ContainerFlow
@@ -390,19 +391,21 @@ private class ConstantBooleanArgumentNode extends ArgumentNode, ExprNode {
390391
}
391392

392393
/**
393-
* Returns a guard that will certainly not hold in calling context `call`.
394+
* Holds if `guard` evaluating to `branch` will certainly not happen in calling
395+
* context `call`.
394396
*
395397
* In particular it does not hold because it checks that `param` has value `b`, but
396398
* in context `call` it is known to have value `!b`. Note this is `noinline`d in order
397399
* to avoid a bad join order in `isUnreachableInCall`.
398400
*/
399401
pragma[noinline]
400-
private ControlFlow::ConditionGuardNode getAFalsifiedGuard(DataFlowCall call) {
402+
private predicate falsifiedGuard(DataFlowCall call, Guard guard, boolean branch) {
401403
exists(SsaParameterNode param, ConstantBooleanArgumentNode arg |
402404
// get constant bool argument and parameter for this call
403405
viableParamArg(call, pragma[only_bind_into](param), pragma[only_bind_into](arg)) and
404406
// which is used in a guard controlling `n` with the opposite value of `arg`
405-
result.ensures(param.getAUse(), arg.getBooleanValue().booleanNot())
407+
guard = param.getAUse().asExpr() and
408+
branch = arg.getBooleanValue().booleanNot()
406409
)
407410
}
408411

@@ -416,7 +419,10 @@ class NodeRegion instanceof BasicBlock {
416419
* Holds if the nodes in `nr` are unreachable when the call context is `call`.
417420
*/
418421
predicate isUnreachableInCall(NodeRegion nr, DataFlowCall call) {
419-
getAFalsifiedGuard(call).dominates(nr)
422+
exists(Guard guard, boolean branch |
423+
falsifiedGuard(call, guard, branch) and
424+
guard.controls(nr, branch)
425+
)
420426
}
421427

422428
/**

‎go/ql/lib/semmle/go/security/InsecureFeatureFlag.qll‎

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*/
44

55
import go
6+
private import semmle.go.controlflow.Guards
67

78
/**
89
* Provides classes and predicates relating to flags that may indicate security expectations.
@@ -114,9 +115,30 @@ module InsecureFeatureFlag {
114115
}
115116

116117
/**
117-
* Gets a control-flow node that represents a (likely) security feature-flag check
118+
* Holds if `block` is controlled by a flag of kind `flagKind`.
119+
*
120+
* For a switch case expression, only the matching branch is controlled by that flag. Other
121+
* branches, including the default case, are reached when the flag does not match.
118122
*/
119-
ControlFlow::ConditionGuardNode getASecurityFeatureFlagCheck() {
120-
result.ensures(any(SecurityFeatureFlag f).getAFlag().getANode(), _)
123+
predicate flagControls(FlagKind flagKind, BasicBlock block) {
124+
exists(GVN flag, Guard guard |
125+
flag = flagKind.getAFlag() and
126+
guard = flag.getANode().asExpr() and
127+
(
128+
exists(CaseClause cc, ExpressionSwitchStmt switch |
129+
guard.getParent() = cc and
130+
cc = switch.getACase() and
131+
exists(switch.getExpr()) and
132+
caseExpressionMatchControls(guard, block)
133+
)
134+
or
135+
not exists(CaseClause cc, ExpressionSwitchStmt switch |
136+
guard.getParent() = cc and
137+
cc = switch.getACase() and
138+
exists(switch.getExpr())
139+
) and
140+
guard.controls(block, _)
141+
)
142+
)
121143
}
122144
}

‎go/ql/src/Security/CWE-020/IncompleteHostnameRegexp.ql‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
*/
1414

1515
import go
16+
private import semmle.go.controlflow.Guards
1617

1718
/**
1819
* Holds if `pattern` is a regular expression pattern for URLs with a host matched by `hostPart`,
@@ -70,12 +71,12 @@ predicate regexpGuardsHandler(RegexpPattern regexp, Http::RequestHandler handler
7071

7172
/** Holds if `regexp` guards an HTTP error write. */
7273
predicate regexpGuardsError(RegexpPattern regexp) {
73-
exists(ControlFlow::ConditionGuardNode cond, RegexpMatchFunction match, DataFlow::CallNode call |
74+
exists(Guard cond, RegexpMatchFunction match, DataFlow::CallNode call |
7475
call.getTarget() = match and
7576
match.getRegexp(call) = regexp
7677
|
77-
cond.ensures(match.getResult().getNode(call).getASuccessor*(), true) and
78-
cond.dominates(any(ReachableBasicBlock b | writesHttpError(b)))
78+
cond = match.getResult().getNode(call).getASuccessor*().asExpr() and
79+
cond.controls(any(ReachableBasicBlock b | writesHttpError(b)), true)
7980
)
8081
}
8182

‎go/ql/src/Security/CWE-209/StackTraceExposure.ql‎

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import go
1717
import semmle.go.security.InsecureFeatureFlag::InsecureFeatureFlag
18+
private import semmle.go.controlflow.Guards
1819

1920
/**
2021
* A flag indicating the program is in debug or development mode, or that stack
@@ -56,11 +57,7 @@ module StackTraceExposureConfig implements DataFlow::ConfigSig {
5657
// Sanitize everything controlled by an is-debug-mode check.
5758
// Imprecision: I don't try to guess which arm of a branch is intended
5859
// to mean debug mode, and which is production mode.
59-
exists(ControlFlow::ConditionGuardNode cgn |
60-
cgn.ensures(any(DebugModeFlag f).getAFlag().getANode(), _)
61-
|
62-
cgn.dominates(node.getBasicBlock())
63-
)
60+
flagControls(any(DebugModeFlag f), node.getBasicBlock())
6461
}
6562

6663
predicate observeDiffInformedIncrementalMode() { any() }

‎go/ql/src/Security/CWE-295/DisabledCertificateCheck.ql‎

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import go
2626
import semmle.go.security.InsecureFeatureFlag::InsecureFeatureFlag
27+
private import semmle.go.controlflow.Guards
2728

2829
/**
2930
* Holds if `part` becomes a part of `whole`, either by (local) data flow or by being incorporated
@@ -49,13 +50,6 @@ class InsecureCertificateFlag extends FlagKind {
4950
}
5051
}
5152

52-
/**
53-
* Gets a control-flow node that represents a (likely) flag controlling an insecure certificate setup.
54-
*/
55-
ControlFlow::ConditionGuardNode getAnInsecureCertificateCheck() {
56-
result.ensures(any(InsecureCertificateFlag f).getAFlag().getANode(), _)
57-
}
58-
5953
/**
6054
* Returns flag kinds relevant to this query: a generic security feature flag, or one
6155
* specifically controlling insecure certificate configuration.
@@ -80,7 +74,7 @@ where
8074
f.hasQualifiedName("crypto/tls", "Config", "InsecureSkipVerify") and
8175
rhs.getBoolValue() = true and
8276
// exclude writes guarded by a feature flag
83-
not [getASecurityFeatureFlagCheck(), getAnInsecureCertificateCheck()].dominatesNode(w) and
77+
not flagControls(securityOrTlsVersionFlag(), w.getBasicBlock()) and
8478
// exclude results in functions whose name documents the insecurity
8579
not exists(FuncDef fn | fn = w.getRoot() |
8680
isSecurityOrCertificateConfigFlag(fn.getEnclosingFunction*().getName())

‎go/ql/src/Security/CWE-327/InsecureTLS.ql‎

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import go
1515
import semmle.go.security.InsecureFeatureFlag::InsecureFeatureFlag
16+
private import semmle.go.controlflow.Guards
1617

1718
/**
1819
* Holds if it is insecure to assign TLS version `val` named `name` to `tls.Config` field `fieldName`.
@@ -246,13 +247,6 @@ class LegacyTlsVersionFlag extends FlagKind {
246247
override string getAFlagName() { result.regexpMatch("(?i).*(old|intermediate|legacy).*") }
247248
}
248249

249-
/**
250-
* Gets a control-flow node that represents a (likely) flag controlling TLS version selection.
251-
*/
252-
ControlFlow::ConditionGuardNode getALegacyTlsVersionCheck() {
253-
result.ensures(any(LegacyTlsVersionFlag f).getAFlag().getANode(), _)
254-
}
255-
256250
/**
257251
* Returns flag kinds relevant to this query: a generic security feature flag, or one
258252
* specifically controlling TLS version selection.
@@ -275,8 +269,7 @@ where
275269
isInsecureTlsCipherFlow(source.asPathNode2(), sink.asPathNode2(), message)
276270
) and
277271
// Exclude sources or sinks guarded by a feature or legacy flag
278-
not [getASecurityFeatureFlagCheck(), getALegacyTlsVersionCheck()]
279-
.dominatesNode([source, sink].getNode().asInstruction()) and
272+
not flagControls(securityOrTlsVersionFlag(), [source, sink].getNode().getBasicBlock()) and
280273
// Exclude sources or sinks that occur lexically within a block related to a feature or legacy flag
281274
not astNodeIsFlag([source, sink].getNode().asExpr().getParent*(), securityOrTlsVersionFlag()) and
282275
// Exclude results in functions whose name documents insecurity

‎go/ql/test/library-tests/semmle/go/controlflow/Guards/Guards.expected‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,3 +266,8 @@ defaultCaseResult
266266
| guards.go:164:2:165:32 | case clause | expressionless default | true |
267267
modelBarrierResult
268268
| guards.go:351:27:351:33 | pointer | model bool | model-bool |
269+
| guards.go:354:27:354:33 | pointer | model zero | model-zero |
270+
| guards.go:357:31:357:37 | pointer | model not zero | model-not-zero |
271+
| guards.go:360:27:360:33 | pointer | model null | model-null |
272+
| guards.go:363:31:363:37 | pointer | model not null | model-not-null |
273+
| guards.go:366:34:366:40 | pointer | model no exception | model-no-exception |

‎go/ql/test/library-tests/semmle/go/dataflow/GuardingFunctions/test.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -842,7 +842,7 @@ func test() {
842842
s := source()
843843
isValid := !guardBool(s)
844844
if isValid {
845-
sink(s) // $ SPURIOUS: hasValueFlow="s"
845+
sink(s)
846846
} else {
847847
sink(s) // $ hasValueFlow="s"
848848
}
Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
#select
22
| test.go:18:10:18:12 | buf | test.go:15:28:15:30 | buf [postupdate] | test.go:18:10:18:12 | buf | HTTP response depends on $@ and may be exposed to an external user. | test.go:15:28:15:30 | buf [postupdate] | stack trace information |
3-
| test.go:44:11:44:13 | buf | test.go:15:28:15:30 | buf [postupdate] | test.go:44:11:44:13 | buf | HTTP response depends on $@ and may be exposed to an external user. | test.go:15:28:15:30 | buf [postupdate] | stack trace information |
43
| test.go:46:11:46:13 | buf | test.go:15:28:15:30 | buf [postupdate] | test.go:46:11:46:13 | buf | HTTP response depends on $@ and may be exposed to an external user. | test.go:15:28:15:30 | buf [postupdate] | stack trace information |
54
edges
65
| test.go:15:28:15:30 | buf [postupdate] | test.go:18:10:18:12 | buf | provenance | |
76
| test.go:15:28:15:30 | buf [postupdate] | test.go:21:29:21:31 | buf | provenance | |
8-
| test.go:21:29:21:31 | buf | test.go:44:11:44:13 | buf | provenance | |
97
| test.go:21:29:21:31 | buf | test.go:46:11:46:13 | buf | provenance | |
108
nodes
119
| test.go:15:28:15:30 | buf [postupdate] | semmle.label | buf [postupdate] |
1210
| test.go:18:10:18:12 | buf | semmle.label | buf |
1311
| test.go:21:29:21:31 | buf | semmle.label | buf |
14-
| test.go:44:11:44:13 | buf | semmle.label | buf |
1512
| test.go:46:11:46:13 | buf | semmle.label | buf |
1613
subpaths

0 commit comments

Comments
 (0)