-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathUnreleasedLock.ql
More file actions
157 lines (142 loc) · 5.07 KB
/
UnreleasedLock.ql
File metadata and controls
157 lines (142 loc) · 5.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/**
* @name Unreleased lock
* @description A lock that is acquired one or more times without a matching number of unlocks
* may cause a deadlock.
* @kind problem
* @problem.severity error
* @precision medium
* @id java/unreleased-lock
* @tags reliability
* security
* external/cwe/cwe-764
* external/cwe/cwe-833
*/
import java
import semmle.code.java.controlflow.Guards
import semmle.code.java.dataflow.SSA
import semmle.code.java.frameworks.Mockito
class LockType extends RefType {
LockType() {
this.getAMethod().hasName("lock") and
this.getAMethod().hasName("unlock")
}
Method getLockMethod() {
result.getDeclaringType() = this and
(result.hasName("lock") or result.hasName("tryLock"))
}
Method getUnlockMethod() {
result.getDeclaringType() = this and
result.hasName("unlock")
}
Method getIsHeldByCurrentThreadMethod() {
result.getDeclaringType() = this and
result.hasName("isHeldByCurrentThread")
}
MethodAccess getLockAccess() {
result.getMethod() = this.getLockMethod() and
// Not part of a Mockito verification call
not result instanceof MockitoVerifiedMethodAccess
}
MethodAccess getUnlockAccess() {
result.getMethod() = this.getUnlockMethod() and
// Not part of a Mockito verification call
not result instanceof MockitoVerifiedMethodAccess
}
MethodAccess getIsHeldByCurrentThreadAccess() {
result.getMethod() = this.getIsHeldByCurrentThreadMethod() and
// Not part of a Mockito verification call
not result instanceof MockitoVerifiedMethodAccess
}
}
predicate lockBlock(LockType t, BasicBlock b, int locks) {
locks = strictcount(int i | b.getNode(i) = t.getLockAccess())
}
predicate unlockBlock(LockType t, BasicBlock b, int unlocks) {
unlocks = strictcount(int i | b.getNode(i) = t.getUnlockAccess())
}
/**
* A block `b` that locks and/or unlocks `t` a number of times; `netlocks`
* equals the number of locks minus the number of unlocks.
*/
predicate lockUnlockBlock(LockType t, BasicBlock b, int netlocks) {
lockBlock(t, b, netlocks) and not unlockBlock(t, b, _)
or
exists(int unlocks |
not lockBlock(t, b, _) and unlockBlock(t, b, unlocks) and netlocks = -unlocks
)
or
exists(int locks, int unlocks |
lockBlock(t, b, locks) and unlockBlock(t, b, unlocks) and netlocks = locks - unlocks
)
}
/**
* A call to `lock` or `tryLock` on `t` that fails with an exception or the value `false`
* resulting in a CFG edge from `lockblock` to `exblock`.
*/
predicate failedLock(LockType t, BasicBlock lockblock, BasicBlock exblock) {
exists(ControlFlowNode lock |
lock = lockblock.getLastNode() and
(
lock = t.getLockAccess()
or
exists(SsaExplicitUpdate lockbool |
// Using the value of `t.getLockAccess()` ensures that it is a `tryLock` call.
lock = lockbool.getAUse() and
lockbool.getDefiningExpr().(VariableAssign).getSource() = t.getLockAccess()
)
) and
(
lock.getAnExceptionSuccessor() = exblock or
lock.(ConditionNode).getAFalseSuccessor() = exblock
)
)
}
/**
* A call to `isHeldByCurrentThread` on `t` in `checkblock` that has `falsesucc` as the false
* successor.
*/
predicate heldByCurrentThreadCheck(LockType t, BasicBlock checkblock, BasicBlock falsesucc) {
exists(ConditionBlock conditionBlock |
conditionBlock.getCondition() = t.getIsHeldByCurrentThreadAccess()
|
conditionBlock.getBasicBlock() = checkblock and
conditionBlock.getTestSuccessor(false) = falsesucc
)
}
/**
* A control flow path from a locking call in `src` to `b` such that the number of
* locks minus the number of unlocks along the way is positive and equal to `locks`.
*/
predicate blockIsLocked(LockType t, BasicBlock src, BasicBlock b, int locks) {
lockUnlockBlock(t, b, locks) and src = b and locks > 0
or
exists(BasicBlock pred, int predlocks, int curlocks, int failedlock |
pred = b.getABBPredecessor()
|
// The number of net locks from the `src` block to the predecessor block `pred` is `predlocks`.
blockIsLocked(t, src, pred, predlocks) and
// The recursive call ensures that at least one lock is held, so do not consider the false
// successor of the `isHeldByCurrentThread()` check.
not heldByCurrentThreadCheck(t, pred, b) and
// Count a failed lock as an unlock so the net is zero.
(if failedLock(t, pred, b) then failedlock = 1 else failedlock = 0) and
(
not lockUnlockBlock(t, b, _) and curlocks = 0
or
lockUnlockBlock(t, b, curlocks)
) and
locks = predlocks + curlocks - failedlock and
locks > 0 and
// Arbitrary bound in order to fail gracefully in case of locking in a loop.
locks < 10
)
}
from Callable c, LockType t, BasicBlock src, BasicBlock exit, MethodAccess lock
where
// Restrict results to those methods that actually attempt to unlock.
t.getUnlockAccess().getEnclosingCallable() = c and
blockIsLocked(t, src, exit, _) and
exit.getLastNode() = c and
lock = src.getANode() and
lock = t.getLockAccess()
select lock, "This lock might not be unlocked or might be locked more times than it is unlocked."