Skip to content

False Negative: Guards in while loop with break fail to 'control' variable usage outside #8490

Closed as not planned
@JLLeitschuh

Description

@JLLeitschuh

Take the following two examples:

static File safe11() throws IOException {
    File temp = null;
    if (temp == null) {
        while (true) {
            temp = File.createTempFile("test", "directory");
            if (temp.delete() && temp.mkdir()) {
                break;
            }
        }
    }
    return temp;
}

File safe12temp;
File safe12() throws IOException {
    if (safe12temp == null) {
        while (true) {
            safe12temp = File.createTempFile("test", "directory");
            if (safe12temp.delete() && safe12temp.mkdir()) {
                break;
            }
        }
    }
    return safe12temp;
}

Let's take File.createTempFile as a source of taint, and _.mkdir() is a guard.

/**
 * All methods on the class `java.io.File` that create directories.
 */
class MethodFileCreatesDirs extends Method {
  MethodFileCreatesDirs() {
    getDeclaringType() instanceof TypeFile and
    hasName(["mkdir", "mkdirs"])
  }
}

/**
 * An expression that will create a directory without throwing an exception if a file/directory already exists.
 */
private predicate isNonThrowingDirectoryCreationExpression(Expr expr, MethodAccess creationCall) {
  creationCall.getMethod() instanceof MethodFileCreatesDirs and creationCall.getQualifier() = expr
}

private class DirectoryCreationBarrierGuard extends DataFlow::BarrierGuard {
  DirectoryCreationBarrierGuard() { isNonThrowingDirectoryCreationExpression(_, this) }

  override predicate checks(Expr e, boolean branch) {
    this.controls(e, branch)
  }
}

Running a query for DirectoryCreationBarrierGuard will find the safe12temp.mkdir() and temp.mkdir().
However, when quick evaluating the DirectoryCreationBarrierGuard::checks method, it will not find that safe12temp.mkdir() is a check for return safe12temp and temp.mkdir() is a check for return safe12temp;.

Interesting note: as soon as the outer null check is removed, codeQL does correctly identify the guard controls the return. For example, this is detected as correctly guarded:

File safe12temp;
File safe12() throws IOException {
	while (true) {
        safe12temp = File.createTempFile("test", "directory");
        if (safe12temp.delete() && safe12temp.mkdir()) {
            break;
        }
    }
    return safe12temp;
}

Practical example:

https://github.com/apache/hive/blob/cba74fa91d4035aee2c39dcf929a0ae480609540/ql/src/java/org/apache/hadoop/hive/ql/exec/spark/HiveKVResultCache.java#L89-L103

The above code is flagged as a false positive result when executing this new query: #4473

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions