Home > Net >  Can this code cause an infinite loop while searching for the lowest level cause of an exception?
Can this code cause an infinite loop while searching for the lowest level cause of an exception?

Time:10-20

    public static Throwable getCause(@Nonnull Throwable t) {
        while ((t instanceof ExecutionException || t instanceof CompletionException) && t.getCause() != null) {
            t = t.getCause();
        }

        return t;
    }

Is this code dangerous in a sense that the while loop may never end? Just wondering if someone can cause this to go on forever.

If so, what might be a better way to handle this? I'm thinking maybe adding an upper bound limit.

CodePudding user response:

The loop is following the exception hierarchy down to the root cause. If that one points back to one of the already visited exceptions there is a bigger fail in the causality. Therefore I'd say it will never go into an infinite loop.

CodePudding user response:

This contrived example demonstrates that it is possible to have an exception chain which cannot be unraveled:

public static void main(String[] args) {
    
    
    Throwable t1 = new Throwable1();
    Throwable t2 = new Throwable2();
    t1.initCause(t2);
    t2.initCause(t1);
    
    try {
        throw new Throwable("bad",t1);
    } catch (Throwable t) {
        while (t.getCause() != null) {
            System.out.println(t.getCause());
            t = t.getCause();
        }
    }

Which produces endless prints:

Main$Throwable1
Main$Throwable2
Main$Throwable1
Main$Throwable2
Main$Throwable1
Main$Throwable2

Now is it possible in your code? Only looking at all the relevant code would one be able to answer that specifically.

CodePudding user response:

Of course it is possible, you can't prevent someone write something like:

public class ExceptionWithCauseAsItself extends ExecutionException {
    @Override
    public Throwable getCause() {
        return this;
    }
}

Following the principle of Defensive Programming, the method should not fall into infinite loop even when someone throw something like ExceptionWithCauseAsItself.

Since your case is not only getting the root cause, probably there is no library to fit what you use. I suggest refer to Apache Common Langs ExceptionUtils.getRootCause to get some idea on how to tackle recursive cause structures.

CodePudding user response:

Is this code dangerous in a sense that the while loop may never end? Just wondering if someone can cause this to go on forever.

In short: Theoretically? Yes. But practically? No. Your code is fine as is.

In long:

Theoretically, yes

Sure, one can create a loop in the causal chain just fine. GetCause() is just a method, and not a final one at that; exceptions are just classes, so one can make their own exception and write public Throwable getCause() { return this; }.

Practically, no

... but just because someone could do that, doesn't mean you should deal with it. Because why do you want to deal with that? Perhaps you're thinking: Well, if some programmer is intentionally trying to blow up the system, I'd want to be robust and not blow up even when they try.

But therein lies a problem: If someone wants to blow up a system, they can. It's nearly trivial to do so. Imagine this:

public class HahaHackingIsSoEasy extends RuntimeException {
  @Override public Throwable getCause() {
    while (true) ;
  }
}

And I throw that. Your code will hang just the same, and if you attempt to detect loops, that's not going to solve the problem. And if you try to stop me from doing this, too, by firing up a separate thread with a timeout and then using Thread.stop() (deprecated, and dangerous) to stop me, I'll just write a loop without a savepoint in which case neither stop() nor using JVMTI to hook in as a debugger and stop that way is going to work.

The conclusion is: There are only 2 reliable ways to stop intentionally malicious code:

  • The best, by far: Don't run the malicious code in the first place.
  • The distant second best option: Run it in a highly controlled sandbox environment.

The JVM is un-sandboxable from inside itself (no, the SecurityManager isn't good enough. It has absolutely no way to stop (savepoint-less) infinite loops, for example), so this at the very least involves firing up an entirely separate JVM just to do the job you want to do, so that you can set timeouts and memory limits on it, and possibly an entire new virtual machine. It'll take thousands of times the resources, and is extremely complicated; I rather doubt that's what you intended to do here.

But what about unintentional loops?

The one question that remains is, given that we already wrote off malicious code (not 'we can deal with it', but rather 'if its intentionally malicious you cannot stop them with a loop detector'), what if it's an accident?

Generally, the best way to deal with accidents is to not deal with them at all, not in code: Let them happen; that's why you have operations teams and server maintainers and the like (you're going to have to have those, no matter what happens. Might as well use them). Once it happens, you figure it out, and you fix it.

That leaves just one final corner case which is: What if loops in causal chains have a plausible, desired usecase?

And that's a fair question. Fortunately, the answer is a simple: No, there is no plausible/desired usecase. Loops in causal chains do not happen unless there is a bug (in which case, the answer is: Find it, fix it), or there is malicious case (in which case, the answer is: Do not run it and call your security team).

  •  Tags:  
  • java
  • Related