-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathUnreleasedLock.ql
More file actions
137 lines (128 loc) · 4.97 KB
/
UnreleasedLock.ql
File metadata and controls
137 lines (128 loc) · 4.97 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
/**
* @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
* @security-severity 5.0
* @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.Concurrency
predicate lockBlock(LockType t, BasicBlock b, int locks) {
locks = strictcount(int i | b.getNode(i).asExpr() = t.getLockAccess())
}
predicate unlockBlock(LockType t, BasicBlock b, int unlocks) {
unlocks = strictcount(int i | b.getNode(i).asExpr() = 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.asExpr() = t.getLockAccess()
or
exists(SsaExplicitWrite lockbool |
// Using the value of `t.getLockAccess()` ensures that it is a `tryLock` call.
lock.asExpr() = lockbool.getARead() and
lockbool.getValue() = t.getLockAccess()
)
) and
(
lock.getAnExceptionSuccessor() = exblock.getFirstNode() or
lock.(ConditionNode).getAFalseSuccessor() = exblock.getFirstNode()
)
)
}
/**
* 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 = checkblock and
conditionBlock.getTestSuccessor(false) = falsesucc
)
}
/**
* Holds if there is a variable access in `checkblock` that has `falsesucc` as the false successor.
*
* The variable access must have an assigned value that is a lock access on `t`, and
* the true successor of `checkblock` must contain an unlock access.
*/
predicate variableLockStateCheck(LockType t, BasicBlock checkblock, BasicBlock falsesucc) {
exists(ConditionBlock conditionBlock, VarAccess v |
v.getType() instanceof BooleanType and
// Ensure that a lock access is assigned to the variable
v.getVariable().getAnAssignedValue() = t.getLockAccess() and
// Ensure that the `true` successor of the condition block contains an unlock access
conditionBlock.getTestSuccessor(true) = t.getUnlockAccess().getBasicBlock() and
conditionBlock.getCondition() = v
|
conditionBlock = 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.getAPredecessor() |
// 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 or of `variableLockStateCheck`.
not heldByCurrentThreadCheck(t, pred, b) and
not variableLockStateCheck(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, ExitBlock exit, MethodCall lock
where
// Restrict results to those methods that actually attempt to unlock.
t.getUnlockAccess().getEnclosingCallable() = c and
blockIsLocked(t, src, exit, _) and
exit.getEnclosingCallable() = c and
lock = src.getANode().asExpr() and
lock = t.getLockAccess()
select lock, "This lock might not be unlocked or might be locked more times than it is unlocked."