Java secure coding guide: the correct use of lock and synchronization

brief introduction

In Java multithreading environment, lock and synchronization are functions that we will use. So what should I pay attention to after writing lock and synchronization related code in Java? Let's have a look.

Use private final object as lock object

Generally speaking, we need to synchronize when we share objects with multiple threads. There are two synchronization methods in Java. The first is method synchronization and the second is synchronization block.

If we use the synchronized keyword in the instance method or synchronized (this) in the synchronization block, the instance of the object will be used as the monitor, which we call the intrinsic lock.

If malicious code maliciously obtains the lock of the object and releases it, our system will not respond to normal services in time and will suffer DoS attacks.

The way to solve this problem is to use private final object as the object of lock. Because it is private, the malicious object cannot obtain the lock of the object, thus avoiding the problem.

If the synchronized keyword is used in the class method (static), the class object will be used as the monitor. In this case, the malicious object can obtain the class directly through the subclass of the class, and then obtain the class object by calling getClass (), so as to lock the normal service.

Therefore, we recommend using private final object as lock object.

Here are some examples:

public class SynObject {

    public synchronized  void doSomething(){
        //do something
    }

    public static void main(String[] args) throws InterruptedException {
        SynObject synObject= new SynObject();
        synchronized (synObject){
            while (true){
                //loop forever
                Thread.sleep(10000);
            }
        }
    }
}

The above code may be the code we use most often. We define a synchronized dosomething method in the object.

If malicious code directly gets the synobject object we want to call and directly synchronizes it, as shown in the above example, the lock of this object will never be released. Eventually, DOS.

Let's look at the second way:

    public Object lock = new Object();

    public void doSomething2(){
        synchronized (lock){
            //do something
        }
    }

In the above example, we synchronized a public object, but because the object is public, malicious programs can access the public field and permanently obtain the monitor of the object to generate DOS.

Take another example:

    private volatile Object lock2 = new Object();

    public void doSomething3() {
        synchronized (lock2) {
            // do something
        }
    }

    public void setLock2(Object lockValue) {
        lock2 = lockValue;
    }

In the above example, we defined a private lock object and used it to lock the dosomething3 method.

Although it is private, we provide a public method to modify the object. So there are security problems.

The correct approach is to use private final object:

    private final Object lock4= new Object();

    public void doSomething4() {
        synchronized (lock4) {
            // do something
        }
    }

Let's consider the case of static methods:

    public static synchronized void doSomething5() {
        // do something
    }

synchronized (SynObject.class) {
  while (true) {
    Thread.sleep(10000); 
  }
}

The above defines a public static method to lock a class object. Malicious code can maliciously occupy the lock of the object, resulting in DOS.

Do not synchronize reusable objects

When we talked about expression rules earlier, we mentioned the construction principle of encapsulated class objects:

For Boolean and byte, if they are built directly from the basic class value, they are the same object.

For character, if the value range is \ u0000 to \ u007f, it belongs to the same object. If it exceeds this range, it belongs to different objects.

For integer and short, if the range of values is - 128 and 127, they belong to the same object. If they exceed this range, they are different objects.

for instance:

        Boolean boolA=true;
        Boolean boolB=true;
        System.out.println(boolA==boolB);

The Boolean object built from the basic type above is actually the same object.

If we use the following Boolean object in our code for synchronization, a security problem may be triggered:

private final Boolean booleanLock = Boolean.FALSE;
 
public void doSomething() {
  synchronized (booleanLock) {
    // ...
  }
}

In the above example, we start from Boolean False constructs a Boolean object. Although the object is private, malicious code can be passed through Boolean False to build the same object, thus invalidating the private rule.

The same problem may also occur in string:

private final String lock = "lock";
 
public void doSomething() {
  synchronized (lock) {
    // ...
  }
}

Because the string object has a string constant pool, the string object created directly from the string is actually the same object. So the above code has a security problem.

The solution is to use new to create a new object.

private final String lock = new String("LOCK");

Do not sync object getClass()

Sometimes we want to synchronize class classes. Object provides a convenient getClass method to return the current class. However, in the case of parent and child classes, the getClass of the child class will return the class class of the child class instead of the class class of the parent class, resulting in inconsistent object synchronization.

Take the following example:

public class SycClass {

    public void doSomething(){
        synchronized (getClass()){
            //do something
        }
    }
}

In sysclass, we define a dosomething method. In this method, we sync the object returned by getClass ().

If sycclass has subclasses:

public class SycClassSub extends SycClass{

    public void doSomethingElse(){
        synchronized (SycClass.class){
           doSomething();
        }
    }
}

The dosomethingelse method actually obtains two locks, one is sysclass and the other is sysclasssub, which creates a security risk.

During synchronization, we need to specify the object to be synchronized. There are two methods to specify the class to be synchronized:

synchronized (SycClass.class)
synchronized (Class.forName("com.flydean.SycClass"))

We can call sysclass directly Class can also use class Forname.

Do not sync advanced concurrency objects

We implemented Java util. concurrent. The objects of the lock and condition interfaces in the locks package are called advanced concurrency objects. For example: reentrantlock.

These advanced concurrent objects also look like locks. Can we sync these advanced concurrent objects directly? Take the following example:

public class SyncLock {

    private final Lock lock = new reentrantlock();

    public void doSomething(){
        synchronized (lock){
            //do something
        }
    }
}

It seems that there is no problem, but we should note that our customized synchronized (lock) is different from the lock implementation in advanced concurrent objects. If we use both synchronized (lock) and lock Lock(), then there may be a security risk.

Therefore, for these advanced concurrent objects, the best way is not to directly sync, but to use their own lock mechanism, as follows:

    public void doSomething2(){
        lock.lock();
        try{
        //do something
        }finally {
            lock.unlock();
        }
    }

Do not use instance lock to protect static data

A class can have static class variables or instance variables. Class variables are related to class, while instance variables are related to the instance object of class.

When protecting class variables, we must note that sync must also be class variables. If sync is an instance variable, the purpose of protection cannot be achieved.

Take the following example:

public class SyncStatic {
    private static volatile int age;

    public synchronized void doSomething(){
        age++;
    }
}

We define a static variable age, and then we want to accumulate it in a method. As mentioned in the previous article, + + is a composite operation, and we need to synchronize its data.

However, in the above example, we used the synchronized keyword. In fact, syncstatic instance objects are synchronized. If multiple threads create multiple instance objects and call dosomething method at the same time, it can be carried out in parallel. This causes problems with + + operations.

Similarly, the following code is the same problem:

    private final Object lock = new Object();
    public  void doSomething2(){
        synchronized (lock) {
            age++;
        }
    }

The solution is to define a class variable:

    private static final Object lock3 = new Object();
    public  void doSomething3(){
        synchronized (lock3) {
            age++;
        }
    }

Do not perform time-consuming operations while holding the lock

If we perform time-consuming operations such as I / O operations during lock holding, the lock holding time will be too long. In the case of high concurrency, threads may starve or DOS may occur.

So we must avoid this situation.

Release the lock correctly

After holding the lock, you must pay attention to the correct release of the lock. Even if an exception is encountered, you should not interrupt the release of the lock.

Generally speaking, it is best to release the lock in finally {}.

    public void doSomething(){
        lock.lock();
        try{
        //do something
        }finally {
            lock.unlock();
        }
    }

Code for this article:

learn-java-base-9-to-20/tree/master/security

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>