Wiki Markup |
---|
To avoid data corruption in multithreaded Java programs, shared data must be protected from concurrent modifications and accesses. This can be performed at the object level by using {{synchronized |
...
}} methods or blocks. However, excessive use of synchronization may result in deadlocks (See [CON08-J. Do not call alien methods or constructors that synchronize on the same object as the calling class]). |
...
According to the Java Language Specification \[[JLS 05|AA. Java References#JLS 05]\], "The Java programming language neither prevents nor requires detection of deadlock conditions." |
...
When a fine-grained locking approach is employed by using member locks, deadlocks can arise unless each thread always requests locks in the same order.
Noncompliant Code Example
This noncompliant code example shows a subtle deadlock issue that manifests itself when synchronization is implemented incorrectly. Assume that an attacker has two bank accounts and is capable of requesting two deposit()
operations in succession, as implemented by the two threads started in main()
.
Code Block | ||
---|---|---|
| ||
class BadLocksDeadlocks can arise unless each thread requests and releases locks in the same order. h2. Noncompliant Code Example This noncompliant code example shows a subtle deadlock issue that manifests itself when synchronization is implemented incorrectly. Assume that an attacker has two bank accounts and is capable of requesting two {{depositAllAmount()}} operations in succession, from the two threads started in {{main()}}. {code:bgColor=#FFcccc} class BankAccount { private int balancebalanceAmount; // Total amount in bank account private BadLocksBankAccount(int balance) { this.balancebalanceAmount = balance; } private synchronized void withdrawwithdrawAllAmount() { System.out.println("Withdrawing all amount..."); this.balancebalanceAmount = 0; } // Deposits the amount from this object instance to BankAccount instance argument ba private synchronized void depositdepositAllAmount(BadLocksBankAccount blba) { bl.balance += this.balanceSystem.out.println("Depositing all amount..."); bl.withdraw(); } public static void main(String[] args) throws Exception { final BadLocks a = new BadLocks(5000); final BadLocks b = new BadLocks(6000); // These two threads correspond to two malicious requests triggered by the attacker Thread t1 = new Thread(new Runnable() { public void run() { a.deposit(b); } }); Thread t2ba.balanceAmount += this.balanceAmount; withdrawAllAmount(); ba.displayAllAmount(); // Display the new balanceAmount in ba (may cause deadlock) } private synchronized void displayAllAmount() { System.out.println(balanceAmount); } public static void main(String[] args) throws Exception { final BankAccount a = new BankAccount(5000); final BankAccount b = new BankAccount(6000); // These two threads correspond to two malicious requests triggered by the attacker Thread t1 = new Thread(new Runnable() { public void run() { ba.depositdepositAllAmount(ab); } }); Thread t1.start();t2 = new Thread(new Runnable() { t2.start public void run(); { } } |
Objects a
and b
are constructed such that the amount will be transferred from a
to b
by first depositing the amount from a
to b
and then zeroing out a
(and vice-versa during the call to b.deposit()
).
Both the threads invoke the synchronized deposit
method on objects a
and b
respectively and there is no interleaving of locks. However, the deposit
method is designed to request a lock on object a
for the first thread and object b
for the second. If both threads acquire separate locks in different orders, for instance, if the call to withdraw()
in one thread requests an already held lock in the other thread, a deadlock situation arises. If Thread t1
finishes executing before Thread 2
, there would not be any issues. Sequences where the threads alternate, such as, T1, T2, T1, T2 can result in a deadlock (In Tx, 'x' denotes Thread number).
Compliant Solution (1) (acquire locks in proper order)
To be compliant, do not request a lock when it is already held by another object where that object is waiting for the requesting object to release its own lock. This can be achieved by invoking the withdraw()
method without qualifying it with the bl
parameter. This allows the withdraw()
method of the current thread to be called.
Code Block | ||
---|---|---|
| ||
private synchronized void deposit(BadLocks bl) {
bl.balance += this.balance;
withdraw(); // Call same instance's withdraw() method
}
|
Compliant Solution (2) (avoid excessive synchronization)
The deadlock can also be avoided by declaring the balance field as volatile
and removing the synchronized
keyword from the withdraw()
method.
Code Block | ||
---|---|---|
| ||
private volatile int balance; // Total amount
//...
private void withdraw(BadLocks bl) {
this.balance = 0;
}
|
The withdraw()
method does not require additional synchronization because it atomically makes the balance zero.
Noncompliant Code Example
In this noncompliant code example, a deadlock occurs if the transfer()
method acquires the locks in decreasing numeric order, while the sumHelper()
method uses an increasing numeric order to acquire its locks.
Code Block | ||
---|---|---|
| ||
class Stocks implements FundConstants {
static int[] balances = new int[noOfStocks];
static Object[] locks = new Object[noOfStocks];
static {
for (int n = 0; n < noOfStocks; n++) {
balances[n] = 10000;
locks[n] = new Object();
}
}
static void transfer(Transfer t) {
int lo, hi;
if (t.fundFrom > t.fundTo) { // Acquires the locks in decreasing numeric order
lo = t.fundFrom;
hi = t.fundTo;
} else {
lo = t.fundTo;
hi = t.fundFrom;
}
synchronized (locks[lo]) {
synchronized (locks[hi]) {
balances[t.fundFrom] -= t.amount;
balances[t.fundTo] += t.amount;
}
}
}
static int sumHelper(int next) {
synchronized (locks[next]) { // Acquires the locks in increasing numeric order
if (next == (noOfStocks - 1)) {
return balances[next];
} else {
return balances[next] + sumHelper(next + 1); // Increasing numeric order
}
}
}
static void checkSystem() {
int actual = 0;
actual = sumHelper(0);
System.out.println("Balance is " + actual);
}
}
|
Compliant Solution
To implement a fine-grained locking strategy, request a separate lock for each position in the balances
array. As it is not possible to lock on primitive types, a direct lock on the items in the balances
array cannot be obtained. Instead, an array of raw Objects
(locks
) can be used.
This compliant solution avoids deadlocks because every thread requests the locks in the same order and as a result, the locks are acquired (and released) correctly.
Code Block | ||
---|---|---|
| ||
class Stocks implements FundConstants {
static int[] balances = new int[noOfStocks];
static Object[] locks = new Object[noOfStocks];
static {
for (int n = 0; n < noOfStocks; n++) {
balances[n] = 10000;
locks[n] = new Object();
}
}
static void transfer(Transfer t) {
int lo, hi;
if (t.fundFrom < t.fundTo) { // Acquires the locks in increasing numeric order
lo = t.fundFrom;
hi = t.fundTo;
} else {
lo = t.fundTo;
hi = t.fundFrom;
}
synchronized (locks[lo]) {
synchronized (locks[hi]) {
balances[t.fundFrom] -= t.amount;
balances[t.fundTo] += t.amount;
}
}
}
static int sumHelper (int next) {
synchronized (locks[next]) { // Acquires the locks in increasing numeric order
if (next == (noOfStocks - 1)) {
return balances[next];
} else {
return balances[next] + sumHelper(next + 1); // Increasing numeric order
}
}
}
static void checkSystem() {
int actual = 0;
actual = sumHelper(0);
System.out.println("Balance is " + actual);
}
}
|
Risk Assessment
Fine-grained locking may result in deadlocks if some thread does not always request locks in the same order as others.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
CON12- J | low | likely | high | P3 | L3 |
Automated Detection
TODO
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
References
Wiki Markup |
---|
\[[JLS 05|AA. Java References#JLS 05]\] [Chapter 17, Threads and Locks|http://java.sun.com/docs/books/jls/third_edition/html/memory.html]
\[[SDN 08|AA. Java References#SDN 08]\] [Sun Developer Network Tech tips|http://java.sun.com/developer/TechTips/2000/tt0328.html]
\[[MITRE 09|AA. Java References#MITRE 09]\] [CWE ID 412|http://cwe.mitre.org/data/definitions/412.html] "Unrestricted Lock on Critical Resource" |
...
b.depositAllAmount(a);
}
});
t1.start();
t2.start();
}
}
{code}
Objects of class {{BankAccount}} represent bank accounts. The {{balanceAmount}} field represents the total balance amount available for a particular object (bank account). A user is allowed to initiate an operation _deposit all amount_ that transfers the balance amount from one account to another. This is akin to closing a bank account and transferring all the amount to a different (existing or new) account.
An attacker may cause the program to construct two threads so that they initiate balance amount transfers from one object to the other and from the other object to the first object, respectively. {{BankAccount}} object's instances {{a}} and {{b}} are constructed such that the first thread atomically transfers the amount from {{a}} to {{b}} by depositing the balance amount from {{a}} to {{b}} and withdrawing the whole amount from {{a}}. The second thread performs the reverse operation, that is, it transfers the amount from {{b}} to {{a}} and withdraws the whole amount from {{b}}. These operations are safe thus far. However, the {{depositAllAmount()}} method (first thread) invokes the {{synchronized}} {{displayAllAmount()}} method on the instance of object {{b}}. The {{displayAllAmount()}} may request the monitor that is already secured by the second thread that is performing the reverse transfer. The second thread may itself be blocked waiting to enter the monitor secured by the {{displayAllAmount()}} method of the first thread that is attempting to display the balance amount of instance {{a}}. This constitutes a deadlock.
In this program the threads request monitors in different orders depending on the interleaving of method calls. If {{Thread T1}} finishes executing before {{Thread T2}}, there are no issues because in this case, locks are acquired ad released in the proper order. Sequences where the threads alternate, such as, T1, T2, T1, T2 can result in a deadlock (In Tx, 'x' denotes Thread number).
h2. Compliant Solution (avoid excessive synchronization)
The deadlock can be avoided by declaring the {{balanceAmount}} field as {{volatile}} and removing the {{synchronized}} keyword from the {{displayAllAmount()}} method.
{code:bgColor=#ccccff}
private volatile int balanceAmount;
//...
private void displayAllAmount() {
System.out.println(balanceAmount);
}
{code}
The {{displayAllAmount()}} method does not require additional synchronization because it atomically displays the latest value for the balance amount.
To be compliant, do not request a lock when it is already held by another object where that object is waiting for the requesting object to release its own lock. When this is not possible, avoid synchronizing unnecessary accesses.
h2. Noncompliant Code Example
This noncompliant code example consists of three integer arrays: {{distances}}, {{speeds}} and {{times}}. The distances are fixed and cannot be changed by the client. The client can pass a {{time}} array as argument to method {{getAverageSpeed()}} to find the average speed. It can also pass a {{speed}} array as argument to method {{getAverageTime()}} to find the average time taken. This allows the client to calculate the third parameter from two given parameters. For example, speed is calculated as distance/time.
The example also uses an array of internal lock objects instead of intrinsic synchronization (or method synchronization) so that multiple threads do not interfere with the array elements when the arrays are being traversed. Because it is not possible to lock on primitive types, a direct lock on the array elements cannot be obtained. Instead, an array of raw {{Objects}} ({{locks}}) is used.
{code:bgColor=#FFcccc}
public class RecursiveTravel {
final static int MAX = 20;
static int[] distances = new int[MAX];
static int[] times = new int[MAX];
static int[] speeds = new int[MAX];
static Object[] locks = new Object[MAX];
static {
for (int i = 0; i < MAX; i++) {
distances[i] = 10; // Assuming all distances are 10 for illustration
locks[i] = new Object(); // Create lock objects
}
}
double getAverageSpeed(int[] time) {
times = time.clone();
return getSpeed(0, 0) / MAX;
}
double getAverageTime(int[] speed) {
speeds = speed.clone();
return getTime(MAX - 1, 0) / MAX;
}
int getSpeed(int i, int speed) { // Acquire locks in nondecreasing order
if(i > MAX - 1) {
return speed;
}
synchronized(locks[i]) {
speed += distances[i]/times[i];
return getSpeed(++i, speed);
}
}
int getTime(int i, int time) { // Acquire locks in nonincreasing order
if(i <= -1) {
return time;
}
synchronized(locks[i]) {
time += distances[i]/speeds[i];
return getTime(--i, time);
}
}
}
{code}
The {{getSpeed()}} and {{getTime()}} methods recursively calculate the sum of the speeds and times, respectively, for each distance value in the array. The implementation is deadlock prone because the recursive calls occur within the synchronized regions of these methods and acquire locks in opposite numerical orders. That is, {{getSpeed()}} requests locks from index 0 to {{MAX}} - 1 (19) whereas {{getTime()}} requests them from index {{MAX}} - 1 (19) to 0. Because of recursion, no previously acquired locks are released by either method. A deadlock occurs when two threads call these methods out of order in that, one thread calls {{getSpeed()}} while the other calls {{getTime()}} before either method has finished executing.
One such execution order that causes a deadlock is shown below:
{code}
Thread T1 (in getTime()) acquires lock:
i = 19
...
i = 0
Thread T1 (in getSpeed()) acquires lock:
i = 0
...
i = 18
Thread T2 (in getTime()) acquires lock:
i = 19
Thread T1 next wants, i = 19 which T2 holds
Thread T2 next wants, i = 18 which T1 holds
{code}
{mc}
// Class to make the above code run in a multi-threaded environment
public class RecursiveControl implements Runnable {
static RecursiveTravel t = new RecursiveTravel();
static int[] speed = new int[MAX];
static int[] time = new int[MAX];
static {
for (int i = 0; i < MAX; i++) {
speed[i] = 2;
time[i] = 2;
}
}
public void run() {
t.getTime(speed);
t.getSpeed(time);
}
public static void main(String[] args) throws InterruptedException {
Runnable r1 = new RecursiveControl();
Runnable r2 = new RecursiveControl();
Thread c1 = new Thread(r1);
Thread c2 = new Thread(r2);
c1.start();
c2.start();
}
}
{mc}
h2. Compliant Solution
This compliant solution moves the recursive calls from the {{getSpeed()}} and {{getTime()}} methods to outside the {{synchronized}} block. Consequently, locks are released as soon as they are no longer needed. Also, the locks are acquired in the same order (nondecreasing) form both these methods. This eliminates potential deadlock conditions.
{code:bgColor=#ccccff}
public class RecursiveTravel {
// ...
double getAverageSpeed(int[] time) {
times = time.clone();
return getSpeed(19, 0) / MAX;
}
double getAverageTime(int[] speed) {
speeds = speed.clone();
return getTime(19, 0) / MAX;
}
int getSpeed(int i, int speed) { // Acquire locks in nondecreasing order
if(i > 19) {
return speed;
}
synchronized(locks[i]) {
speed += distances[i]/times[i];
}
return getSpeed(++i, speed); // Moved outside of synchronized region
}
int getTime(int i, int time) { // Acquire locks in nondecreasing order
if(i > 19) {
return time;
}
synchronized(locks[i]) {
time += distances[i]/speeds[i];
}
return getTime(++i, time); // Moved outside of synchronized region
}
}
{code}
h2. Risk Assessment
Acquiring and releasing locks in the wrong order may result in deadlocks.
|| Rule || Severity || Likelihood || Remediation Cost || Priority || Level ||
| CON12- J | low | likely | high | {color:green}{*}P3{*}{color} | {color:green}{*}L3{*}{color} |
h3. Automated Detection
TODO
h3. Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the [CERT website|https://www.kb.cert.org/vulnotes/bymetric?searchview&query=FIELD+KEYWORDS+contains+CON34-J].
h2. References
\[[JLS 05|AA. Java References#JLS 05]\] [Chapter 17, Threads and Locks|http://java.sun.com/docs/books/jls/third_edition/html/memory.html]
\[[SDN 08|AA. Java References#SDN 08]\] [Sun Developer Network Tech tips|http://java.sun.com/developer/TechTips/2000/tt0328.html]
\[[MITRE 09|AA. Java References#MITRE 09]\] [CWE ID 412|http://cwe.mitre.org/data/definitions/412.html] "Unrestricted Lock on Critical Resource"
----
[!The CERT Sun Microsystems Secure Coding Standard for Java^button_arrow_left.png!|CON11-J. Do not assume that declaring an object volatile guarantees visibility of its members] [!The CERT Sun Microsystems Secure Coding Standard for Java^button_arrow_up.png!|11. Concurrency (CON)] [!The CERT Sun Microsystems Secure Coding Standard for Java^button_arrow_right.png!|CON13-J. Do not try to force thread shutdown]
|