mirror of
https://github.com/github/codeql.git
synced 2026-04-29 18:55:14 +02:00
Merge branch 'main' into henrymercer/check-query-ids
This commit is contained in:
@@ -19,9 +19,10 @@ import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import com.github.codeql.Logger;
|
||||
import static com.github.codeql.ClassNamesKt.getIrDeclBinaryName;
|
||||
import static com.github.codeql.ClassNamesKt.getIrElementBinaryName;
|
||||
import static com.github.codeql.ClassNamesKt.getIrClassVirtualFile;
|
||||
|
||||
import org.jetbrains.kotlin.ir.IrElement;
|
||||
import org.jetbrains.kotlin.ir.declarations.IrClass;
|
||||
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
@@ -212,20 +213,19 @@ public class OdasaOutput {
|
||||
PathTransformer.std().fileAsDatabaseString(file) + ".trap.gz");
|
||||
}
|
||||
|
||||
private File getTrapFileForDecl(IrDeclaration sym, String signature) {
|
||||
private File getTrapFileForDecl(IrElement sym, String signature) {
|
||||
if (currentSpecFileEntry == null)
|
||||
return null;
|
||||
return trapFileForDecl(sym, signature);
|
||||
}
|
||||
|
||||
private File trapFileForDecl(IrDeclaration sym, String signature) {
|
||||
private File trapFileForDecl(IrElement sym, String signature) {
|
||||
return FileUtil.fileRelativeTo(currentSpecFileEntry.getTrapFolder(),
|
||||
trapFilePathForDecl(sym, signature));
|
||||
}
|
||||
|
||||
private String trapFilePathForDecl(IrDeclaration sym, String signature) {
|
||||
String binaryName = getIrDeclBinaryName(sym);
|
||||
String binaryNameWithSignature = binaryName + signature;
|
||||
private String trapFilePathForDecl(IrElement sym, String signature) {
|
||||
String binaryName = getIrElementBinaryName(sym);
|
||||
// TODO: Reinstate this?
|
||||
//if (getTrackClassOrigins())
|
||||
// classId += "-" + StringDigestor.digest(sym.getSourceFileId());
|
||||
@@ -241,7 +241,7 @@ public class OdasaOutput {
|
||||
* Deletion of existing trap files.
|
||||
*/
|
||||
|
||||
private void deleteTrapFileAndDependencies(IrDeclaration sym, String signature) {
|
||||
private void deleteTrapFileAndDependencies(IrElement sym, String signature) {
|
||||
File trap = trapFileForDecl(sym, signature);
|
||||
if (trap.exists()) {
|
||||
trap.delete();
|
||||
@@ -269,7 +269,7 @@ public class OdasaOutput {
|
||||
* Any unique suffix needed to distinguish `sym` from other declarations with the same name.
|
||||
* For functions for example, this means its parameter signature.
|
||||
*/
|
||||
private TrapFileManager getMembersWriterForDecl(File trap, File trapFileBase, TrapClassVersion trapFileVersion, IrDeclaration sym, String signature) {
|
||||
private TrapFileManager getMembersWriterForDecl(File trap, File trapFileBase, TrapClassVersion trapFileVersion, IrElement sym, String signature) {
|
||||
if (use_trap_locking) {
|
||||
TrapClassVersion currVersion = TrapClassVersion.fromSymbol(sym, log);
|
||||
String shortName = sym instanceof IrDeclarationWithName ? ((IrDeclarationWithName)sym).getName().asString() : "(name unknown)";
|
||||
@@ -326,7 +326,7 @@ public class OdasaOutput {
|
||||
return trapWriter(trap, sym, signature);
|
||||
}
|
||||
|
||||
private TrapFileManager trapWriter(File trapFile, IrDeclaration sym, String signature) {
|
||||
private TrapFileManager trapWriter(File trapFile, IrElement sym, String signature) {
|
||||
if (!trapFile.getName().endsWith(".trap.gz"))
|
||||
throw new CatastrophicError("OdasaOutput only supports writing to compressed trap files");
|
||||
String relative = FileUtil.relativePath(trapFile, currentSpecFileEntry.getTrapFolder());
|
||||
@@ -335,7 +335,7 @@ public class OdasaOutput {
|
||||
return concurrentWriter(trapFile, relative, log, sym, signature);
|
||||
}
|
||||
|
||||
private TrapFileManager concurrentWriter(File trapFile, String relative, Logger log, IrDeclaration sym, String signature) {
|
||||
private TrapFileManager concurrentWriter(File trapFile, String relative, Logger log, IrElement sym, String signature) {
|
||||
if (trapFile.exists())
|
||||
return null;
|
||||
return new TrapFileManager(trapFile, relative, true, log, sym, signature);
|
||||
@@ -345,11 +345,11 @@ public class OdasaOutput {
|
||||
|
||||
private TrapDependencies trapDependenciesForClass;
|
||||
private File trapFile;
|
||||
private IrDeclaration sym;
|
||||
private IrElement sym;
|
||||
private String signature;
|
||||
private boolean hasError = false;
|
||||
|
||||
private TrapFileManager(File trapFile, String relative, boolean concurrentCreation, Logger log, IrDeclaration sym, String signature) {
|
||||
private TrapFileManager(File trapFile, String relative, boolean concurrentCreation, Logger log, IrElement sym, String signature) {
|
||||
trapDependenciesForClass = new TrapDependencies(relative);
|
||||
this.trapFile = trapFile;
|
||||
this.sym = sym;
|
||||
@@ -360,7 +360,7 @@ public class OdasaOutput {
|
||||
return trapFile;
|
||||
}
|
||||
|
||||
public void addDependency(IrDeclaration dep, String signature) {
|
||||
public void addDependency(IrElement dep, String signature) {
|
||||
trapDependenciesForClass.addDependency(trapFilePathForDecl(dep, signature));
|
||||
}
|
||||
|
||||
@@ -422,7 +422,7 @@ public class OdasaOutput {
|
||||
* previously set by a call to {@link OdasaOutput#setCurrentSourceFile(File)}.
|
||||
*/
|
||||
public TrapLocker getTrapLockerForCurrentSourceFile() {
|
||||
return new TrapLocker((IrClass)null, null);
|
||||
return new TrapLocker((IrClass)null, null, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -460,19 +460,19 @@ public class OdasaOutput {
|
||||
*
|
||||
* @return a {@link TrapLocker} for the trap file corresponding to the given class symbol.
|
||||
*/
|
||||
public TrapLocker getTrapLockerForDecl(IrDeclaration sym, String signature) {
|
||||
return new TrapLocker(sym, signature);
|
||||
public TrapLocker getTrapLockerForDecl(IrElement sym, String signature, boolean fromSource) {
|
||||
return new TrapLocker(sym, signature, fromSource);
|
||||
}
|
||||
|
||||
public class TrapLocker implements AutoCloseable {
|
||||
private final IrDeclaration sym;
|
||||
private final IrElement sym;
|
||||
private final File trapFile;
|
||||
// trapFileBase is used when doing lockless TRAP file writing.
|
||||
// It is trapFile without the #metadata.trap.gz suffix.
|
||||
private File trapFileBase = null;
|
||||
private TrapClassVersion trapFileVersion = null;
|
||||
private final String signature;
|
||||
private TrapLocker(IrDeclaration decl, String signature) {
|
||||
private TrapLocker(IrElement decl, String signature, boolean fromSource) {
|
||||
this.sym = decl;
|
||||
this.signature = signature;
|
||||
if (sym==null) {
|
||||
@@ -485,7 +485,10 @@ public class OdasaOutput {
|
||||
} else {
|
||||
// We encode the metadata into the filename, so that the
|
||||
// TRAP filenames for different metadatas don't overlap.
|
||||
trapFileVersion = TrapClassVersion.fromSymbol(sym, log);
|
||||
if (fromSource)
|
||||
trapFileVersion = new TrapClassVersion(0, 0, 0, "kotlin");
|
||||
else
|
||||
trapFileVersion = TrapClassVersion.fromSymbol(sym, log);
|
||||
String baseName = normalTrapFile.getName().replace(".trap.gz", "");
|
||||
// If a class has lots of inner classes, then we get lots of files
|
||||
// in a single directory. This makes our directory listings later slow.
|
||||
@@ -717,11 +720,18 @@ public class OdasaOutput {
|
||||
return vf.getTimeStamp();
|
||||
}
|
||||
|
||||
private static TrapClassVersion fromSymbol(IrDeclaration sym, Logger log) {
|
||||
VirtualFile vf = sym instanceof IrClass ? getIrClassVirtualFile((IrClass)sym) :
|
||||
sym.getParent() instanceof IrClass ? getIrClassVirtualFile((IrClass)sym.getParent()) :
|
||||
null;
|
||||
if(vf == null)
|
||||
private static VirtualFile getVirtualFileIfClass(IrElement e) {
|
||||
if (e instanceof IrClass)
|
||||
return getIrClassVirtualFile((IrClass)e);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
private static TrapClassVersion fromSymbol(IrElement sym, Logger log) {
|
||||
VirtualFile vf = getVirtualFileIfClass(sym);
|
||||
if (vf == null && sym instanceof IrDeclaration)
|
||||
vf = getVirtualFileIfClass(((IrDeclaration)sym).getParent());
|
||||
if (vf == null)
|
||||
return new TrapClassVersion(-1, 0, 0, null);
|
||||
|
||||
final int[] versionStore = new int[1];
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
package com.github.codeql
|
||||
|
||||
import com.github.codeql.utils.isExternalDeclaration
|
||||
import com.github.codeql.utils.isExternalFileClassMember
|
||||
import com.semmle.extractor.java.OdasaOutput
|
||||
import com.semmle.util.data.StringDigestor
|
||||
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
|
||||
import org.jetbrains.kotlin.ir.util.isFileClass
|
||||
import org.jetbrains.kotlin.ir.util.packageFqName
|
||||
import org.jetbrains.kotlin.ir.util.parentClassOrNull
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import java.io.BufferedWriter
|
||||
import java.io.File
|
||||
import java.util.ArrayList
|
||||
import java.util.HashSet
|
||||
@@ -25,87 +23,103 @@ class ExternalDeclExtractor(val logger: FileLogger, val invocationTrapFile: Stri
|
||||
val propertySignature = ";property"
|
||||
val fieldSignature = ";field"
|
||||
|
||||
val output = OdasaOutput(false, logger).also {
|
||||
it.setCurrentSourceFile(File(sourceFilePath))
|
||||
}
|
||||
|
||||
fun extractLater(d: IrDeclarationWithName, signature: String): Boolean {
|
||||
if (d !is IrClass && !isExternalFileClassMember(d)) {
|
||||
logger.errorElement("External declaration is neither a class, nor a top-level declaration", d)
|
||||
return false
|
||||
}
|
||||
val declBinaryName = declBinaryNames.getOrPut(d) { getIrDeclBinaryName(d) }
|
||||
val declBinaryName = declBinaryNames.getOrPut(d) { getIrElementBinaryName(d) }
|
||||
val ret = externalDeclsDone.add(Pair(declBinaryName, signature))
|
||||
if (ret) externalDeclWorkList.add(Pair(d, signature))
|
||||
return ret
|
||||
}
|
||||
fun extractLater(c: IrClass) = extractLater(c, "")
|
||||
|
||||
fun writeStubTrapFile(e: IrElement, signature: String = "") {
|
||||
extractElement(e, signature, true) { trapFileBW, _, _ ->
|
||||
trapFileBW.write("// Trap file stubbed because this declaration was extracted from source in $sourceFilePath\n")
|
||||
trapFileBW.write("// Part of invocation $invocationTrapFile\n")
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractElement(element: IrElement, possiblyLongSignature: String, fromSource: Boolean, extractorFn: (BufferedWriter, String, OdasaOutput.TrapFileManager) -> Unit) {
|
||||
// In order to avoid excessively long signatures which can lead to trap file names longer than the filesystem
|
||||
// limit, we truncate and add a hash to preserve uniqueness if necessary.
|
||||
val signature = if (possiblyLongSignature.length > 100) {
|
||||
possiblyLongSignature.substring(0, 92) + "#" + StringDigestor.digest(possiblyLongSignature).substring(0, 8)
|
||||
} else { possiblyLongSignature }
|
||||
output.getTrapLockerForDecl(element, signature, fromSource).useAC { locker ->
|
||||
locker.trapFileManager.useAC { manager ->
|
||||
val shortName = when(element) {
|
||||
is IrDeclarationWithName -> element.name.asString()
|
||||
is IrFile -> element.name
|
||||
else -> "(unknown name)"
|
||||
}
|
||||
if (manager == null) {
|
||||
logger.info("Skipping extracting external decl $shortName")
|
||||
} else {
|
||||
val trapFile = manager.file
|
||||
val trapTmpFile = File.createTempFile("${trapFile.nameWithoutExtension}.", ".${trapFile.extension}.tmp", trapFile.parentFile)
|
||||
try {
|
||||
GZIPOutputStream(trapTmpFile.outputStream()).bufferedWriter().use {
|
||||
extractorFn(it, signature, manager)
|
||||
}
|
||||
|
||||
if (!trapTmpFile.renameTo(trapFile)) {
|
||||
logger.error("Failed to rename $trapTmpFile to $trapFile")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
manager.setHasError()
|
||||
logger.error("Failed to extract '$shortName'. Partial TRAP file location is $trapTmpFile", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun extractExternalClasses() {
|
||||
val output = OdasaOutput(false, logger)
|
||||
output.setCurrentSourceFile(File(sourceFilePath))
|
||||
do {
|
||||
val nextBatch = ArrayList(externalDeclWorkList)
|
||||
externalDeclWorkList.clear()
|
||||
nextBatch.forEach { workPair ->
|
||||
val (irDecl, possiblyLongSignature) = workPair
|
||||
// In order to avoid excessively long signatures which can lead to trap file names longer than the filesystem
|
||||
// limit, we truncate and add a hash to preserve uniqueness if necessary.
|
||||
val signature = if (possiblyLongSignature.length > 100) {
|
||||
possiblyLongSignature.substring(0, 92) + "#" + StringDigestor.digest(possiblyLongSignature).substring(0, 8)
|
||||
} else { possiblyLongSignature }
|
||||
output.getTrapLockerForDecl(irDecl, signature).useAC { locker ->
|
||||
locker.trapFileManager.useAC { manager ->
|
||||
val shortName = when(irDecl) {
|
||||
is IrDeclarationWithName -> irDecl.name.asString()
|
||||
else -> "(unknown name)"
|
||||
extractElement(irDecl, possiblyLongSignature, false) { trapFileBW, signature, manager ->
|
||||
val containingClass = getContainingClassOrSelf(irDecl)
|
||||
if (containingClass == null) {
|
||||
logger.errorElement("Unable to get containing class", irDecl)
|
||||
} else {
|
||||
val binaryPath = getIrClassBinaryPath(containingClass)
|
||||
|
||||
// We want our comments to be the first thing in the file,
|
||||
// so start off with a mere TrapWriter
|
||||
val tw = TrapWriter(logger.loggerBase, TrapLabelManager(), trapFileBW, diagnosticTrapWriter)
|
||||
tw.writeComment("Generated by the CodeQL Kotlin extractor for external dependencies")
|
||||
tw.writeComment("Part of invocation $invocationTrapFile")
|
||||
if (signature != possiblyLongSignature) {
|
||||
tw.writeComment("Function signature abbreviated; full signature is: $possiblyLongSignature")
|
||||
}
|
||||
if(manager == null) {
|
||||
logger.info("Skipping extracting external decl $shortName")
|
||||
// Now elevate to a SourceFileTrapWriter, and populate the
|
||||
// file information if needed:
|
||||
val ftw = tw.makeFileTrapWriter(binaryPath, true)
|
||||
|
||||
val fileExtractor = KotlinFileExtractor(logger, ftw, null, binaryPath, manager, this, primitiveTypeMapping, pluginContext, KotlinFileExtractor.DeclarationStack(), globalExtensionState)
|
||||
|
||||
if (irDecl is IrClass) {
|
||||
// Populate a location and compilation-unit package for the file. This is similar to
|
||||
// the beginning of `KotlinFileExtractor.extractFileContents` but without an `IrFile`
|
||||
// to start from.
|
||||
val pkg = irDecl.packageFqName?.asString() ?: ""
|
||||
val pkgId = fileExtractor.extractPackage(pkg)
|
||||
ftw.writeHasLocation(ftw.fileId, ftw.getWholeFileLocation())
|
||||
ftw.writeCupackage(ftw.fileId, pkgId)
|
||||
|
||||
fileExtractor.extractClassSource(irDecl, extractDeclarations = !irDecl.isFileClass, extractStaticInitializer = false, extractPrivateMembers = false, extractFunctionBodies = false)
|
||||
} else {
|
||||
val trapFile = manager.file
|
||||
val trapTmpFile = File.createTempFile("${trapFile.nameWithoutExtension}.", ".${trapFile.extension}.tmp", trapFile.parentFile)
|
||||
|
||||
val containingClass = getContainingClassOrSelf(irDecl)
|
||||
if (containingClass == null) {
|
||||
logger.errorElement("Unable to get containing class", irDecl)
|
||||
return
|
||||
}
|
||||
val binaryPath = getIrClassBinaryPath(containingClass)
|
||||
try {
|
||||
GZIPOutputStream(trapTmpFile.outputStream()).bufferedWriter().use { trapFileBW ->
|
||||
// We want our comments to be the first thing in the file,
|
||||
// so start off with a mere TrapWriter
|
||||
val tw = TrapWriter(logger.loggerBase, TrapLabelManager(), trapFileBW, diagnosticTrapWriter)
|
||||
tw.writeComment("Generated by the CodeQL Kotlin extractor for external dependencies")
|
||||
tw.writeComment("Part of invocation $invocationTrapFile")
|
||||
if (signature != possiblyLongSignature) {
|
||||
tw.writeComment("Function signature abbreviated; full signature is: $possiblyLongSignature")
|
||||
}
|
||||
// Now elevate to a SourceFileTrapWriter, and populate the
|
||||
// file information if needed:
|
||||
val ftw = tw.makeFileTrapWriter(binaryPath, true)
|
||||
|
||||
val fileExtractor = KotlinFileExtractor(logger, ftw, null, binaryPath, manager, this, primitiveTypeMapping, pluginContext, KotlinFileExtractor.DeclarationStack(), globalExtensionState)
|
||||
|
||||
if (irDecl is IrClass) {
|
||||
// Populate a location and compilation-unit package for the file. This is similar to
|
||||
// the beginning of `KotlinFileExtractor.extractFileContents` but without an `IrFile`
|
||||
// to start from.
|
||||
val pkg = irDecl.packageFqName?.asString() ?: ""
|
||||
val pkgId = fileExtractor.extractPackage(pkg)
|
||||
ftw.writeHasLocation(ftw.fileId, ftw.getWholeFileLocation())
|
||||
ftw.writeCupackage(ftw.fileId, pkgId)
|
||||
|
||||
fileExtractor.extractClassSource(irDecl, extractDeclarations = !irDecl.isFileClass, extractStaticInitializer = false, extractPrivateMembers = false, extractFunctionBodies = false)
|
||||
} else {
|
||||
fileExtractor.extractDeclaration(irDecl, extractPrivateMembers = false, extractFunctionBodies = false)
|
||||
}
|
||||
}
|
||||
|
||||
if (!trapTmpFile.renameTo(trapFile)) {
|
||||
logger.error("Failed to rename $trapTmpFile to $trapFile")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
manager.setHasError()
|
||||
logger.error("Failed to extract '$shortName'. Partial TRAP file location is $trapTmpFile", e)
|
||||
}
|
||||
fileExtractor.extractDeclaration(irDecl, extractPrivateMembers = false, extractFunctionBodies = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,12 @@ open class KotlinFileExtractor(
|
||||
}
|
||||
}
|
||||
|
||||
file.declarations.forEach { extractDeclaration(it, extractPrivateMembers = true, extractFunctionBodies = true) }
|
||||
file.declarations.forEach {
|
||||
extractDeclaration(it, extractPrivateMembers = true, extractFunctionBodies = true)
|
||||
if (it is IrProperty || it is IrField || it is IrFunction) {
|
||||
externalClassExtractor.writeStubTrapFile(it, getTrapFileSignature(it))
|
||||
}
|
||||
}
|
||||
extractStaticInitializer(file, { extractFileClass(file) })
|
||||
CommentExtractor(this, file, tw.fileId).extract()
|
||||
|
||||
@@ -99,6 +104,8 @@ open class KotlinFileExtractor(
|
||||
}
|
||||
|
||||
linesOfCode?.linesOfCodeInFile(id)
|
||||
|
||||
externalClassExtractor.writeStubTrapFile(file)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,6 +514,9 @@ open class KotlinFileExtractor(
|
||||
addModifiers(instance.id, "public", "static", "final")
|
||||
tw.writeClass_object(id.cast<DbClass>(), instance.id)
|
||||
}
|
||||
if (c.isObject) {
|
||||
addModifiers(id, "static")
|
||||
}
|
||||
if (extractFunctionBodies && needsObinitFunction(c)) {
|
||||
extractObinitFunction(c, id)
|
||||
}
|
||||
@@ -516,6 +526,9 @@ open class KotlinFileExtractor(
|
||||
|
||||
linesOfCode?.linesOfCodeInDeclaration(c, id)
|
||||
|
||||
if (extractFunctionBodies && !c.isAnonymousObject && !c.isLocal)
|
||||
externalClassExtractor.writeStubTrapFile(c)
|
||||
|
||||
return id
|
||||
}
|
||||
}
|
||||
@@ -1049,8 +1062,6 @@ open class KotlinFileExtractor(
|
||||
private val jvmOverloadsFqName = FqName("kotlin.jvm.JvmOverloads")
|
||||
|
||||
private fun extractGeneratedOverloads(f: IrFunction, parentId: Label<out DbReftype>, maybeSourceParentId: Label<out DbReftype>?, extractBody: Boolean, extractMethodAndParameterTypeAccesses: Boolean, typeSubstitution: TypeSubstitution?, classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?) {
|
||||
if (!f.hasAnnotation(jvmOverloadsFqName))
|
||||
return
|
||||
|
||||
fun extractGeneratedOverload(paramList: List<IrValueParameter?>) {
|
||||
val overloadParameters = paramList.filterNotNull()
|
||||
@@ -1096,6 +1107,22 @@ open class KotlinFileExtractor(
|
||||
}
|
||||
}
|
||||
|
||||
if (!f.hasAnnotation(jvmOverloadsFqName)) {
|
||||
if (f is IrConstructor &&
|
||||
f.valueParameters.isNotEmpty() &&
|
||||
f.valueParameters.all { it.defaultValue != null } &&
|
||||
f.parentClassOrNull?.let {
|
||||
// Don't create a default constructor for an annotation class, or a class that explicitly declares a no-arg constructor.
|
||||
!it.isAnnotationClass &&
|
||||
it.declarations.none { d -> d is IrConstructor && d.valueParameters.isEmpty() }
|
||||
} == true) {
|
||||
// Per https://kotlinlang.org/docs/classes.html#creating-instances-of-classes, a single default overload gets created specifically
|
||||
// when we have all default parameters, regardless of `@JvmOverloads`.
|
||||
extractGeneratedOverload(f.valueParameters.map { _ -> null })
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
val paramList: MutableList<IrValueParameter?> = f.valueParameters.toMutableList()
|
||||
for (n in (f.valueParameters.size - 1) downTo 0) {
|
||||
if (f.valueParameters[n].defaultValue != null) {
|
||||
@@ -1235,13 +1262,13 @@ open class KotlinFileExtractor(
|
||||
DeclarationStackAdjuster(f).use {
|
||||
val fNameSuffix = getExtensionReceiverType(f)?.let { it.classFqName?.asString()?.replace(".", "$$") } ?: ""
|
||||
val extractType = if (isAnnotationClassField(f)) kClassToJavaClass(f.type) else f.type
|
||||
return extractField(useField(f), "${f.name.asString()}$fNameSuffix", extractType, parentId, tw.getLocation(f), f.visibility, f, isExternalDeclaration(f), f.isFinal)
|
||||
return extractField(useField(f), "${f.name.asString()}$fNameSuffix", extractType, parentId, tw.getLocation(f), f.visibility, f, isExternalDeclaration(f), f.isFinal, isDirectlyExposedCompanionObjectField(f))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun extractField(id: Label<out DbField>, name: String, type: IrType, parentId: Label<out DbReftype>, locId: Label<DbLocation>, visibility: DescriptorVisibility, errorElement: IrElement, isExternalDeclaration: Boolean, isFinal: Boolean): Label<out DbField> {
|
||||
private fun extractField(id: Label<out DbField>, name: String, type: IrType, parentId: Label<out DbReftype>, locId: Label<DbLocation>, visibility: DescriptorVisibility, errorElement: IrElement, isExternalDeclaration: Boolean, isFinal: Boolean, isStatic: Boolean): Label<out DbField> {
|
||||
val t = useType(type)
|
||||
tw.writeFields(id, name, t.javaResult.id, parentId, id)
|
||||
tw.writeFieldsKotlinType(id, t.kotlinResult.id)
|
||||
@@ -1251,6 +1278,9 @@ open class KotlinFileExtractor(
|
||||
if (isFinal) {
|
||||
addModifiers(id, "final")
|
||||
}
|
||||
if (isStatic) {
|
||||
addModifiers(id, "static")
|
||||
}
|
||||
|
||||
if (!isExternalDeclaration) {
|
||||
val fieldDeclarationId = tw.getFreshIdLabel<DbFielddecl>()
|
||||
@@ -4182,12 +4212,12 @@ open class KotlinFileExtractor(
|
||||
val firstAssignmentStmtIdx = 1
|
||||
|
||||
if (dispatchReceiverInfo != null) {
|
||||
extractField(dispatchReceiverInfo.field, "<dispatchReceiver>", dispatchReceiverInfo.type, classId, locId, DescriptorVisibilities.PRIVATE, callableReferenceExpr, isExternalDeclaration = false, isFinal = true)
|
||||
extractField(dispatchReceiverInfo.field, "<dispatchReceiver>", dispatchReceiverInfo.type, classId, locId, DescriptorVisibilities.PRIVATE, callableReferenceExpr, isExternalDeclaration = false, isFinal = true, isStatic = false)
|
||||
extractParameterToFieldAssignmentInConstructor("<dispatchReceiver>", dispatchReceiverInfo.type, dispatchReceiverInfo.field, 0 + dispatchReceiverInfo.indexOffset, firstAssignmentStmtIdx + dispatchReceiverInfo.indexOffset)
|
||||
}
|
||||
|
||||
if (extensionReceiverInfo != null) {
|
||||
extractField(extensionReceiverInfo.field, "<extensionReceiver>", extensionReceiverInfo.type, classId, locId, DescriptorVisibilities.PRIVATE, callableReferenceExpr, isExternalDeclaration = false, isFinal = true)
|
||||
extractField(extensionReceiverInfo.field, "<extensionReceiver>", extensionReceiverInfo.type, classId, locId, DescriptorVisibilities.PRIVATE, callableReferenceExpr, isExternalDeclaration = false, isFinal = true, isStatic = false)
|
||||
extractParameterToFieldAssignmentInConstructor( "<extensionReceiver>", extensionReceiverInfo.type, extensionReceiverInfo.field, 0 + extensionReceiverInfo.indexOffset, firstAssignmentStmtIdx + extensionReceiverInfo.indexOffset)
|
||||
}
|
||||
}
|
||||
@@ -5255,7 +5285,7 @@ open class KotlinFileExtractor(
|
||||
|
||||
// add field
|
||||
val fieldId = tw.getFreshIdLabel<DbField>()
|
||||
extractField(fieldId, "<fn>", functionType, classId, locId, DescriptorVisibilities.PRIVATE, e, isExternalDeclaration = false, isFinal = true)
|
||||
extractField(fieldId, "<fn>", functionType, classId, locId, DescriptorVisibilities.PRIVATE, e, isExternalDeclaration = false, isFinal = true, isStatic = false)
|
||||
|
||||
// adjust constructor
|
||||
helper.extractParameterToFieldAssignmentInConstructor("<fn>", functionType, fieldId, 0, 1)
|
||||
|
||||
@@ -67,15 +67,12 @@ open class KotlinUsesExtractor(
|
||||
TypeResult(fakeKotlinType(), "", "")
|
||||
)
|
||||
|
||||
@OptIn(kotlin.ExperimentalStdlibApi::class) // Annotation required by kotlin versions < 1.5
|
||||
fun extractFileClass(f: IrFile): Label<out DbClass> {
|
||||
val fileName = f.fileEntry.name
|
||||
val pkg = f.fqName.asString()
|
||||
val defaultName = fileName.replaceFirst(Regex(""".*[/\\]"""), "").replaceFirst(Regex("""\.kt$"""), "").replaceFirstChar({ it.uppercase() }) + "Kt"
|
||||
var jvmName = getJvmName(f) ?: defaultName
|
||||
val jvmName = getFileClassName(f)
|
||||
val qualClassName = if (pkg.isEmpty()) jvmName else "$pkg.$jvmName"
|
||||
val label = "@\"class;$qualClassName\""
|
||||
val id: Label<DbClass> = tw.getLabelFor(label, {
|
||||
val id: Label<DbClass> = tw.getLabelFor(label) {
|
||||
val fileId = tw.mkFileId(f.path, false)
|
||||
val locId = tw.getWholeFileLocation(fileId)
|
||||
val pkgId = extractPackage(pkg)
|
||||
@@ -84,7 +81,7 @@ open class KotlinUsesExtractor(
|
||||
tw.writeHasLocation(it, locId)
|
||||
|
||||
addModifiers(it, "public", "final")
|
||||
})
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
@@ -258,10 +255,26 @@ open class KotlinUsesExtractor(
|
||||
private fun propertySignature(p: IrProperty) =
|
||||
((p.getter ?: p.setter)?.extensionReceiverParameter?.let { useType(erase(it.type)).javaResult.signature } ?: "")
|
||||
|
||||
fun getTrapFileSignature(d: IrDeclaration) =
|
||||
when(d) {
|
||||
is IrFunction ->
|
||||
// Note we erase the parameter types before calling useType even though the signature should be the same
|
||||
// in order to prevent an infinite loop through useTypeParameter -> useDeclarationParent -> useFunction
|
||||
// -> extractFunctionLaterIfExternalFileMember, which would result for `fun <T> f(t: T) { ... }` for example.
|
||||
(listOfNotNull(d.extensionReceiverParameter) + d.valueParameters)
|
||||
.map { useType(erase(it.type)).javaResult.signature }
|
||||
.joinToString(separator = ",", prefix = "(", postfix = ")")
|
||||
is IrProperty -> propertySignature(d) + externalClassExtractor.propertySignature
|
||||
is IrField -> (d.correspondingPropertySymbol?.let { propertySignature(it.owner) } ?: "") + externalClassExtractor.fieldSignature
|
||||
else -> "unknown signature".also {
|
||||
logger.warn("Trap file signature requested for unexpected element $d")
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractPropertyLaterIfExternalFileMember(p: IrProperty) {
|
||||
if (isExternalFileClassMember(p)) {
|
||||
extractExternalClassLater(p.parentAsClass)
|
||||
val signature = propertySignature(p) + externalClassExtractor.propertySignature
|
||||
val signature = getTrapFileSignature(p)
|
||||
dependencyCollector?.addDependency(p, signature)
|
||||
externalClassExtractor.extractLater(p, signature)
|
||||
}
|
||||
@@ -270,7 +283,7 @@ open class KotlinUsesExtractor(
|
||||
private fun extractFieldLaterIfExternalFileMember(f: IrField) {
|
||||
if (isExternalFileClassMember(f)) {
|
||||
extractExternalClassLater(f.parentAsClass)
|
||||
val signature = (f.correspondingPropertySymbol?.let { propertySignature(it.owner) } ?: "") + externalClassExtractor.fieldSignature
|
||||
val signature = getTrapFileSignature(f)
|
||||
dependencyCollector?.addDependency(f, signature)
|
||||
externalClassExtractor.extractLater(f, signature)
|
||||
}
|
||||
@@ -285,18 +298,7 @@ open class KotlinUsesExtractor(
|
||||
// getters and setters are extracted alongside it
|
||||
return
|
||||
}
|
||||
// Note we erase the parameter types before calling useType even though the signature should be the same
|
||||
// in order to prevent an infinite loop through useTypeParameter -> useDeclarationParent -> useFunction
|
||||
// -> extractFunctionLaterIfExternalFileMember, which would result for `fun <T> f(t: T) { ... }` for example.
|
||||
val ext = f.extensionReceiverParameter
|
||||
val parameters = if (ext != null) {
|
||||
listOf(ext) + f.valueParameters
|
||||
} else {
|
||||
f.valueParameters
|
||||
}
|
||||
|
||||
val paramSigs = parameters.map { useType(erase(it.type)).javaResult.signature }
|
||||
val signature = paramSigs.joinToString(separator = ",", prefix = "(", postfix = ")")
|
||||
val signature = getTrapFileSignature(f)
|
||||
dependencyCollector?.addDependency(f, signature)
|
||||
externalClassExtractor.extractLater(f, signature)
|
||||
}
|
||||
@@ -1647,7 +1649,7 @@ open class KotlinUsesExtractor(
|
||||
fun useValueParameter(vp: IrValueParameter, parent: Label<out DbCallable>?): Label<out DbParam> =
|
||||
tw.getLabelFor(getValueParameterLabel(vp, parent))
|
||||
|
||||
private fun isDirectlyExposedCompanionObjectField(f: IrField) =
|
||||
private fun isDirectlyExposableCompanionObjectField(f: IrField) =
|
||||
f.hasAnnotation(FqName("kotlin.jvm.JvmField")) ||
|
||||
f.correspondingPropertySymbol?.owner?.let {
|
||||
it.isConst || it.isLateinit
|
||||
@@ -1655,12 +1657,14 @@ open class KotlinUsesExtractor(
|
||||
|
||||
fun getFieldParent(f: IrField) =
|
||||
f.parentClassOrNull?.let {
|
||||
if (it.isCompanion && isDirectlyExposedCompanionObjectField(f))
|
||||
if (it.isCompanion && isDirectlyExposableCompanionObjectField(f))
|
||||
it.parent
|
||||
else
|
||||
null
|
||||
} ?: f.parent
|
||||
|
||||
fun isDirectlyExposedCompanionObjectField(f: IrField) = getFieldParent(f) != f.parent
|
||||
|
||||
// Gets a field's corresponding property's extension receiver type, if any
|
||||
fun getExtensionReceiverType(f: IrField) =
|
||||
f.correspondingPropertySymbol?.owner?.let {
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinarySourceElement
|
||||
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
|
||||
import org.jetbrains.kotlin.ir.util.parentClassOrNull
|
||||
@@ -21,20 +22,46 @@ import org.jetbrains.kotlin.load.kotlin.JvmPackagePartSource
|
||||
// for `that`.
|
||||
private fun getName(d: IrDeclarationWithName) = (d as? IrAnnotationContainer)?.let { getJvmName(it) } ?: d.name.asString()
|
||||
|
||||
fun getIrDeclBinaryName(that: IrDeclaration): String {
|
||||
val shortName = when(that) {
|
||||
is IrDeclarationWithName -> getName(that)
|
||||
else -> "(unknown-name)"
|
||||
}
|
||||
val internalName = StringBuilder(shortName);
|
||||
generateSequence(that.parent) { (it as? IrDeclaration)?.parent }
|
||||
.forEach {
|
||||
when (it) {
|
||||
is IrClass -> internalName.insert(0, getName(it) + "$")
|
||||
is IrPackageFragment -> it.fqName.asString().takeIf { fqName -> fqName.isNotEmpty() }?.let { fqName -> internalName.insert(0, "$fqName.") }
|
||||
}
|
||||
}
|
||||
return internalName.toString()
|
||||
@OptIn(ExperimentalStdlibApi::class) // Annotation required by kotlin versions < 1.5
|
||||
fun getFileClassName(f: IrFile) =
|
||||
getJvmName(f) ?:
|
||||
((f.fileEntry.name.replaceFirst(Regex(""".*[/\\]"""), "")
|
||||
.replaceFirst(Regex("""\.kt$"""), "")
|
||||
.replaceFirstChar { it.uppercase() }) + "Kt")
|
||||
|
||||
fun getIrElementBinaryName(that: IrElement): String {
|
||||
if (that is IrFile) {
|
||||
val shortName = getFileClassName(that)
|
||||
val pkg = that.fqName.asString()
|
||||
return if (pkg.isEmpty()) shortName else "$pkg.$shortName"
|
||||
}
|
||||
|
||||
if (that !is IrDeclaration) {
|
||||
return "(unknown-name)"
|
||||
}
|
||||
|
||||
val shortName = when(that) {
|
||||
is IrDeclarationWithName -> getName(that)
|
||||
else -> "(unknown-name)"
|
||||
}
|
||||
|
||||
val internalName = StringBuilder(shortName)
|
||||
if (that !is IrClass) {
|
||||
val parent = that.parent
|
||||
if (parent is IrFile) {
|
||||
// Note we'll fall through and do the IrPackageFragment case as well, since IrFile <: IrPackageFragment
|
||||
internalName.insert(0, getFileClassName(parent) + "$")
|
||||
}
|
||||
}
|
||||
|
||||
generateSequence(that.parent) { (it as? IrDeclaration)?.parent }
|
||||
.forEach {
|
||||
when (it) {
|
||||
is IrClass -> internalName.insert(0, getName(it) + "$")
|
||||
is IrPackageFragment -> it.fqName.asString().takeIf { fqName -> fqName.isNotEmpty() }?.let { fqName -> internalName.insert(0, "$fqName.") }
|
||||
}
|
||||
}
|
||||
return internalName.toString()
|
||||
}
|
||||
|
||||
fun getIrClassVirtualFile(irClass: IrClass): VirtualFile? {
|
||||
@@ -81,7 +108,7 @@ private fun getRawIrClassBinaryPath(irClass: IrClass) =
|
||||
fun getIrClassBinaryPath(irClass: IrClass): String {
|
||||
return getRawIrClassBinaryPath(irClass)
|
||||
// Otherwise, make up a fake location:
|
||||
?: "/!unknown-binary-location/${getIrDeclBinaryName(irClass).replace(".", "/")}.class"
|
||||
?: "/!unknown-binary-location/${getIrElementBinaryName(irClass).replace(".", "/")}.class"
|
||||
}
|
||||
|
||||
fun getContainingClassOrSelf(decl: IrDeclaration): IrClass? {
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
@AllDefaultsAnnotation
|
||||
public class User {
|
||||
|
||||
public static void test() { new AllDefaultsConstructor(); new AllDefaultsExplicitNoargConstructor(); }
|
||||
|
||||
}
|
||||
@@ -3,3 +3,13 @@ public class Test {
|
||||
@JvmOverloads fun f(x: Int = 0, y: Int) { }
|
||||
|
||||
}
|
||||
|
||||
public class AllDefaultsConstructor(val x: Int = 1, val y: Int = 2) { }
|
||||
|
||||
public annotation class AllDefaultsAnnotation(val x: Int = 1, val y: Int = 2) { }
|
||||
|
||||
public class AllDefaultsExplicitNoargConstructor(val x: Int = 1, val y: Int = 2) {
|
||||
|
||||
constructor() : this(3, 4) { }
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from create_database_utils import *
|
||||
|
||||
os.mkdir('bin')
|
||||
run_codeql_database_create(["kotlinc test.kt -d bin", "kotlinc user.kt -cp bin"], lang="java")
|
||||
run_codeql_database_create(["kotlinc test.kt -d bin", "kotlinc user.kt -cp bin", "javac User.java -cp bin"], lang="java")
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
| J.java:1:14:1:14 | J |
|
||||
| build/J.class:0:0:0:0 | J<String> |
|
||||
| file:///!unknown-binary-location/J.class:0:0:0:0 | J<> |
|
||||
| file:///!unknown-binary-location/J.class:0:0:0:0 | J<Integer> |
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
## 0.4.5
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.4.4
|
||||
|
||||
### New Features
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The query `java/insecure-cookie` now uses global dataflow to track secure cookies being set to the HTTP response object.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The library `PathSanitizer.qll` has been improved to detect more path validation patterns in Kotlin.
|
||||
3
java/ql/lib/change-notes/released/0.4.5.md
Normal file
3
java/ql/lib/change-notes/released/0.4.5.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.4.5
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.4.4
|
||||
lastReleaseVersion: 0.4.5
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/java-all
|
||||
version: 0.4.5-dev
|
||||
version: 0.4.6-dev
|
||||
groups: java
|
||||
dbscheme: config/semmlecode.dbscheme
|
||||
extractor: java
|
||||
|
||||
@@ -80,53 +80,6 @@ private import internal.FlowSummaryImplSpecific as FlowSummaryImplSpecific
|
||||
private import internal.AccessPathSyntax
|
||||
private import FlowSummary
|
||||
|
||||
/**
|
||||
* A module importing the frameworks that provide external flow data,
|
||||
* ensuring that they are visible to the taint tracking / data flow library.
|
||||
*/
|
||||
private module Frameworks {
|
||||
private import internal.ContainerFlow
|
||||
private import semmle.code.java.frameworks.android.Android
|
||||
private import semmle.code.java.frameworks.android.ContentProviders
|
||||
private import semmle.code.java.frameworks.android.ExternalStorage
|
||||
private import semmle.code.java.frameworks.android.Intent
|
||||
private import semmle.code.java.frameworks.android.SharedPreferences
|
||||
private import semmle.code.java.frameworks.android.Slice
|
||||
private import semmle.code.java.frameworks.android.SQLite
|
||||
private import semmle.code.java.frameworks.android.Widget
|
||||
private import semmle.code.java.frameworks.ApacheHttp
|
||||
private import semmle.code.java.frameworks.apache.Collections
|
||||
private import semmle.code.java.frameworks.apache.Lang
|
||||
private import semmle.code.java.frameworks.Flexjson
|
||||
private import semmle.code.java.frameworks.guava.Guava
|
||||
private import semmle.code.java.frameworks.jackson.JacksonSerializability
|
||||
private import semmle.code.java.frameworks.javaee.jsf.JSFRenderer
|
||||
private import semmle.code.java.frameworks.JaxWS
|
||||
private import semmle.code.java.frameworks.JoddJson
|
||||
private import semmle.code.java.frameworks.Stream
|
||||
private import semmle.code.java.frameworks.ratpack.RatpackExec
|
||||
private import semmle.code.java.frameworks.spring.SpringHttp
|
||||
private import semmle.code.java.frameworks.spring.SpringWebClient
|
||||
private import semmle.code.java.security.AndroidIntentRedirection
|
||||
private import semmle.code.java.security.ResponseSplitting
|
||||
private import semmle.code.java.security.InformationLeak
|
||||
private import semmle.code.java.security.FragmentInjection
|
||||
private import semmle.code.java.security.GroovyInjection
|
||||
private import semmle.code.java.security.ImplicitPendingIntents
|
||||
private import semmle.code.java.security.JndiInjection
|
||||
private import semmle.code.java.security.LdapInjection
|
||||
private import semmle.code.java.security.MvelInjection
|
||||
private import semmle.code.java.security.OgnlInjection
|
||||
private import semmle.code.java.security.TemplateInjection
|
||||
private import semmle.code.java.security.XPath
|
||||
private import semmle.code.java.security.XsltInjection
|
||||
private import semmle.code.java.frameworks.Jdbc
|
||||
private import semmle.code.java.frameworks.SpringJdbc
|
||||
private import semmle.code.java.frameworks.MyBatis
|
||||
private import semmle.code.java.frameworks.Hibernate
|
||||
private import semmle.code.java.frameworks.jOOQ
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Define source models as data extensions instead.
|
||||
*
|
||||
|
||||
@@ -36,6 +36,13 @@ abstract class RemoteFlowSource extends DataFlow::Node {
|
||||
abstract string getSourceType();
|
||||
}
|
||||
|
||||
/**
|
||||
* A module for importing frameworks that define remote flow sources.
|
||||
*/
|
||||
private module RemoteFlowSources {
|
||||
private import semmle.code.java.frameworks.android.Widget
|
||||
}
|
||||
|
||||
private class ExternalRemoteFlowSource extends RemoteFlowSource {
|
||||
ExternalRemoteFlowSource() { sourceNode(this, "remote") }
|
||||
|
||||
|
||||
@@ -10,17 +10,19 @@ private import semmle.code.java.dataflow.DataFlow
|
||||
* ensuring that they are visible to the taint tracking library.
|
||||
*/
|
||||
private module Frameworks {
|
||||
private import semmle.code.java.JDK
|
||||
private import semmle.code.java.frameworks.jackson.JacksonSerializability
|
||||
private import semmle.code.java.frameworks.android.AsyncTask
|
||||
private import semmle.code.java.frameworks.android.Intent
|
||||
private import semmle.code.java.frameworks.android.Slice
|
||||
private import semmle.code.java.frameworks.android.SQLite
|
||||
private import semmle.code.java.frameworks.Guice
|
||||
private import semmle.code.java.frameworks.Properties
|
||||
private import semmle.code.java.frameworks.Protobuf
|
||||
private import semmle.code.java.frameworks.guava.Guava
|
||||
private import semmle.code.java.frameworks.apache.Lang
|
||||
private import semmle.code.java.frameworks.ApacheHttp
|
||||
private import semmle.code.java.frameworks.guava.Guava
|
||||
private import semmle.code.java.frameworks.Guice
|
||||
private import semmle.code.java.frameworks.jackson.JacksonSerializability
|
||||
private import semmle.code.java.frameworks.Properties
|
||||
private import semmle.code.java.frameworks.Protobuf
|
||||
private import semmle.code.java.frameworks.ratpack.RatpackExec
|
||||
private import semmle.code.java.JDK
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,11 +6,6 @@ import java
|
||||
private import internal.FlowSummaryImpl as Impl
|
||||
private import internal.DataFlowUtil
|
||||
|
||||
// import all instances of SummarizedCallable below
|
||||
private module Summaries {
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
}
|
||||
|
||||
class SummaryComponent = Impl::Public::SummaryComponent;
|
||||
|
||||
/** Provides predicates for constructing summary components. */
|
||||
@@ -102,6 +97,14 @@ abstract class SyntheticCallable extends string {
|
||||
Type getReturnType() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A module for importing frameworks that define synthetic callables.
|
||||
*/
|
||||
private module SyntheticCallables {
|
||||
private import semmle.code.java.frameworks.android.Intent
|
||||
private import semmle.code.java.frameworks.Stream
|
||||
}
|
||||
|
||||
private newtype TSummarizedCallableBase =
|
||||
TSimpleCallable(Callable c) { c.isSourceDeclaration() } or
|
||||
TSyntheticCallable(SyntheticCallable c)
|
||||
|
||||
@@ -53,16 +53,6 @@ private class TypeFlowNode extends TTypeFlowNode {
|
||||
}
|
||||
}
|
||||
|
||||
private int getNodeKind(TypeFlowNode n) {
|
||||
result = 1 and n instanceof TField
|
||||
or
|
||||
result = 2 and n instanceof TSsa
|
||||
or
|
||||
result = 3 and n instanceof TExpr
|
||||
or
|
||||
result = 4 and n instanceof TMethod
|
||||
}
|
||||
|
||||
/** Gets `t` if it is a `RefType` or the boxed type if `t` is a primitive type. */
|
||||
private RefType boxIfNeeded(Type t) {
|
||||
t.(PrimitiveType).getBoxedType() = result or
|
||||
@@ -158,107 +148,45 @@ private predicate joinStep(TypeFlowNode n1, TypeFlowNode n2) {
|
||||
|
||||
private predicate anyStep(TypeFlowNode n1, TypeFlowNode n2) { joinStep(n1, n2) or step(n1, n2) }
|
||||
|
||||
private import SccReduction
|
||||
private predicate sccEdge(TypeFlowNode n1, TypeFlowNode n2) { anyStep(n1, n2) and anyStep+(n2, n1) }
|
||||
|
||||
/**
|
||||
* SCC reduction.
|
||||
*
|
||||
* This ought to be as easy as `equivalenceRelation(sccEdge/2)(n, scc)`, but
|
||||
* this HOP is not currently supported for newtypes.
|
||||
*
|
||||
* A straightforward implementation would be:
|
||||
* ```ql
|
||||
* predicate sccRepr(TypeFlowNode n, TypeFlowNode scc) {
|
||||
* scc =
|
||||
* max(TypeFlowNode n2 |
|
||||
* sccEdge+(n, n2)
|
||||
* |
|
||||
* n2
|
||||
* order by
|
||||
* n2.getLocation().getStartLine(), n2.getLocation().getStartColumn(), getNodeKind(n2)
|
||||
* )
|
||||
* }
|
||||
*
|
||||
* ```
|
||||
* but this is quadratic in the size of the SCCs.
|
||||
*
|
||||
* Instead we find local maxima by following SCC edges and determine the SCC
|
||||
* representatives from those.
|
||||
* (This is still worst-case quadratic in the size of the SCCs, but generally
|
||||
* performs better.)
|
||||
*/
|
||||
private module SccReduction {
|
||||
private predicate sccEdge(TypeFlowNode n1, TypeFlowNode n2) {
|
||||
anyStep(n1, n2) and anyStep+(n2, n1)
|
||||
}
|
||||
private module Scc = QlBuiltins::EquivalenceRelation<TypeFlowNode, sccEdge/2>;
|
||||
|
||||
private predicate sccEdgeWithMax(TypeFlowNode n1, TypeFlowNode n2, TypeFlowNode m) {
|
||||
sccEdge(n1, n2) and
|
||||
m =
|
||||
max(TypeFlowNode n |
|
||||
n = [n1, n2]
|
||||
|
|
||||
n order by n.getLocation().getStartLine(), n.getLocation().getStartColumn(), getNodeKind(n)
|
||||
)
|
||||
}
|
||||
private class TypeFlowScc = Scc::EquivalenceClass;
|
||||
|
||||
private predicate hasLargerNeighbor(TypeFlowNode n) {
|
||||
exists(TypeFlowNode n2 |
|
||||
sccEdgeWithMax(n, n2, n2) and
|
||||
not sccEdgeWithMax(n, n2, n)
|
||||
or
|
||||
sccEdgeWithMax(n2, n, n2) and
|
||||
not sccEdgeWithMax(n2, n, n)
|
||||
)
|
||||
}
|
||||
/** Holds if `n` is part of an SCC of size 2 or more represented by `scc`. */
|
||||
private predicate sccRepr(TypeFlowNode n, TypeFlowScc scc) { scc = Scc::getEquivalenceClass(n) }
|
||||
|
||||
private predicate localMax(TypeFlowNode m) {
|
||||
sccEdgeWithMax(_, _, m) and
|
||||
not hasLargerNeighbor(m)
|
||||
}
|
||||
|
||||
private predicate sccReprFromLocalMax(TypeFlowNode scc) {
|
||||
exists(TypeFlowNode m |
|
||||
localMax(m) and
|
||||
scc =
|
||||
max(TypeFlowNode n2 |
|
||||
sccEdge+(m, n2) and localMax(n2)
|
||||
|
|
||||
n2
|
||||
order by
|
||||
n2.getLocation().getStartLine(), n2.getLocation().getStartColumn(), getNodeKind(n2)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `n` is part of an SCC of size 2 or more represented by `scc`. */
|
||||
predicate sccRepr(TypeFlowNode n, TypeFlowNode scc) {
|
||||
sccEdge+(n, scc) and sccReprFromLocalMax(scc)
|
||||
}
|
||||
|
||||
predicate sccJoinStep(TypeFlowNode n, TypeFlowNode scc) {
|
||||
exists(TypeFlowNode mid |
|
||||
joinStep(n, mid) and
|
||||
sccRepr(mid, scc) and
|
||||
not sccRepr(n, scc)
|
||||
)
|
||||
}
|
||||
private predicate sccJoinStep(TypeFlowNode n, TypeFlowScc scc) {
|
||||
exists(TypeFlowNode mid |
|
||||
joinStep(n, mid) and
|
||||
sccRepr(mid, scc) and
|
||||
not sccRepr(n, scc)
|
||||
)
|
||||
}
|
||||
|
||||
private signature predicate edgeSig(TypeFlowNode n1, TypeFlowNode n2);
|
||||
private signature class NodeSig;
|
||||
|
||||
private signature module RankedEdge {
|
||||
predicate edgeRank(int r, TypeFlowNode n1, TypeFlowNode n2);
|
||||
private signature module Edge {
|
||||
class Node;
|
||||
|
||||
int lastRank(TypeFlowNode n);
|
||||
predicate edge(TypeFlowNode n1, Node n2);
|
||||
}
|
||||
|
||||
private module RankEdge<edgeSig/2 edge> implements RankedEdge {
|
||||
private signature module RankedEdge<NodeSig Node> {
|
||||
predicate edgeRank(int r, TypeFlowNode n1, Node n2);
|
||||
|
||||
int lastRank(Node n);
|
||||
}
|
||||
|
||||
private module RankEdge<Edge E> implements RankedEdge<E::Node> {
|
||||
private import E
|
||||
|
||||
/**
|
||||
* Holds if `r` is a ranking of the incoming edges `(n1,n2)` to `n2`. The used
|
||||
* ordering is not necessarily total, so the ranking may have gaps.
|
||||
*/
|
||||
private predicate edgeRank1(int r, TypeFlowNode n1, TypeFlowNode n2) {
|
||||
private predicate edgeRank1(int r, TypeFlowNode n1, Node n2) {
|
||||
n1 =
|
||||
rank[r](TypeFlowNode n |
|
||||
edge(n, n2)
|
||||
@@ -271,19 +199,19 @@ private module RankEdge<edgeSig/2 edge> implements RankedEdge {
|
||||
* Holds if `r2` is a ranking of the ranks from `edgeRank1`. This removes the
|
||||
* gaps from the ranking.
|
||||
*/
|
||||
private predicate edgeRank2(int r2, int r1, TypeFlowNode n) {
|
||||
private predicate edgeRank2(int r2, int r1, Node n) {
|
||||
r1 = rank[r2](int r | edgeRank1(r, _, n) | r)
|
||||
}
|
||||
|
||||
/** Holds if `r` is a ranking of the incoming edges `(n1,n2)` to `n2`. */
|
||||
predicate edgeRank(int r, TypeFlowNode n1, TypeFlowNode n2) {
|
||||
predicate edgeRank(int r, TypeFlowNode n1, Node n2) {
|
||||
exists(int r1 |
|
||||
edgeRank1(r1, n1, n2) and
|
||||
edgeRank2(r, r1, n2)
|
||||
)
|
||||
}
|
||||
|
||||
int lastRank(TypeFlowNode n) { result = max(int r | edgeRank(r, _, n)) }
|
||||
int lastRank(Node n) { result = max(int r | edgeRank(r, _, n)) }
|
||||
}
|
||||
|
||||
private signature module TypePropagation {
|
||||
@@ -296,16 +224,16 @@ private signature module TypePropagation {
|
||||
}
|
||||
|
||||
/** Implements recursion through `forall` by way of edge ranking. */
|
||||
private module ForAll<RankedEdge Edge, TypePropagation T> {
|
||||
private module ForAll<NodeSig Node, RankedEdge<Node> E, TypePropagation T> {
|
||||
/**
|
||||
* Holds if `t` is a bound that holds on one of the incoming edges to `n` and
|
||||
* thus is a candidate bound for `n`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate candJoinType(TypeFlowNode n, T::Typ t) {
|
||||
private predicate candJoinType(Node n, T::Typ t) {
|
||||
exists(TypeFlowNode mid |
|
||||
T::candType(mid, t) and
|
||||
Edge::edgeRank(_, mid, n)
|
||||
E::edgeRank(_, mid, n)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -314,13 +242,13 @@ private module ForAll<RankedEdge Edge, TypePropagation T> {
|
||||
* through the edges into `n` ranked from `1` to `r`.
|
||||
*/
|
||||
pragma[assume_small_delta]
|
||||
private predicate flowJoin(int r, TypeFlowNode n, T::Typ t) {
|
||||
private predicate flowJoin(int r, Node n, T::Typ t) {
|
||||
(
|
||||
r = 1 and candJoinType(n, t)
|
||||
or
|
||||
flowJoin(r - 1, n, t) and Edge::edgeRank(r, _, n)
|
||||
flowJoin(r - 1, n, t) and E::edgeRank(r, _, n)
|
||||
) and
|
||||
forall(TypeFlowNode mid | Edge::edgeRank(r, mid, n) | T::supportsType(mid, t))
|
||||
forall(TypeFlowNode mid | E::edgeRank(r, mid, n) | T::supportsType(mid, t))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -328,12 +256,24 @@ private module ForAll<RankedEdge Edge, TypePropagation T> {
|
||||
* coming through all the incoming edges, and therefore is a valid bound for
|
||||
* `n`.
|
||||
*/
|
||||
predicate flowJoin(TypeFlowNode n, T::Typ t) { flowJoin(Edge::lastRank(n), n, t) }
|
||||
predicate flowJoin(Node n, T::Typ t) { flowJoin(E::lastRank(n), n, t) }
|
||||
}
|
||||
|
||||
module RankedJoinStep = RankEdge<joinStep/2>;
|
||||
private module JoinStep implements Edge {
|
||||
class Node = TypeFlowNode;
|
||||
|
||||
module RankedSccJoinStep = RankEdge<sccJoinStep/2>;
|
||||
predicate edge = joinStep/2;
|
||||
}
|
||||
|
||||
private module SccJoinStep implements Edge {
|
||||
class Node = TypeFlowScc;
|
||||
|
||||
predicate edge = sccJoinStep/2;
|
||||
}
|
||||
|
||||
private module RankedJoinStep = RankEdge<JoinStep>;
|
||||
|
||||
private module RankedSccJoinStep = RankEdge<SccJoinStep>;
|
||||
|
||||
private predicate exactTypeBase(TypeFlowNode n, RefType t) {
|
||||
exists(ClassInstanceExpr e |
|
||||
@@ -363,13 +303,13 @@ private predicate exactType(TypeFlowNode n, RefType t) {
|
||||
or
|
||||
// The following is an optimized version of
|
||||
// `forex(TypeFlowNode mid | joinStep(mid, n) | exactType(mid, t))`
|
||||
ForAll<RankedJoinStep, ExactTypePropagation>::flowJoin(n, t)
|
||||
ForAll<TypeFlowNode, RankedJoinStep, ExactTypePropagation>::flowJoin(n, t)
|
||||
or
|
||||
exists(TypeFlowNode scc |
|
||||
exists(TypeFlowScc scc |
|
||||
sccRepr(n, scc) and
|
||||
// Optimized version of
|
||||
// `forex(TypeFlowNode mid | sccJoinStep(mid, scc) | exactType(mid, t))`
|
||||
ForAll<RankedSccJoinStep, ExactTypePropagation>::flowJoin(scc, t)
|
||||
ForAll<TypeFlowScc, RankedSccJoinStep, ExactTypePropagation>::flowJoin(scc, t)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -563,11 +503,11 @@ private predicate typeFlow(TypeFlowNode n, RefType t) {
|
||||
or
|
||||
exists(TypeFlowNode mid | typeFlow(mid, t) and step(mid, n))
|
||||
or
|
||||
ForAll<RankedJoinStep, TypeFlowPropagation>::flowJoin(n, t)
|
||||
ForAll<TypeFlowNode, RankedJoinStep, TypeFlowPropagation>::flowJoin(n, t)
|
||||
or
|
||||
exists(TypeFlowNode scc |
|
||||
exists(TypeFlowScc scc |
|
||||
sccRepr(n, scc) and
|
||||
ForAll<RankedSccJoinStep, TypeFlowPropagation>::flowJoin(scc, t)
|
||||
ForAll<TypeFlowScc, RankedSccJoinStep, TypeFlowPropagation>::flowJoin(scc, t)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -703,13 +643,13 @@ private predicate hasUnionTypeFlow(TypeFlowNode n) {
|
||||
(
|
||||
// Optimized version of
|
||||
// `forex(TypeFlowNode mid | joinStep(mid, n) | unionTypeFlowBaseCand(mid, _, _) or hasUnionTypeFlow(mid))`
|
||||
ForAll<RankedJoinStep, HasUnionTypePropagation>::flowJoin(n, _)
|
||||
ForAll<TypeFlowNode, RankedJoinStep, HasUnionTypePropagation>::flowJoin(n, _)
|
||||
or
|
||||
exists(TypeFlowNode scc |
|
||||
exists(TypeFlowScc scc |
|
||||
sccRepr(n, scc) and
|
||||
// Optimized version of
|
||||
// `forex(TypeFlowNode mid | sccJoinStep(mid, scc) | unionTypeFlowBaseCand(mid, _, _) or hasUnionTypeFlow(mid))`
|
||||
ForAll<RankedSccJoinStep, HasUnionTypePropagation>::flowJoin(scc, _)
|
||||
ForAll<TypeFlowScc, RankedSccJoinStep, HasUnionTypePropagation>::flowJoin(scc, _)
|
||||
)
|
||||
or
|
||||
exists(TypeFlowNode mid | step(mid, n) and hasUnionTypeFlow(mid))
|
||||
|
||||
@@ -3,7 +3,6 @@ import semmle.code.java.Collections
|
||||
import semmle.code.java.Maps
|
||||
private import semmle.code.java.dataflow.SSA
|
||||
private import DataFlowUtil
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
|
||||
private class EntryType extends RefType {
|
||||
EntryType() {
|
||||
|
||||
@@ -244,4 +244,20 @@ module Consistency {
|
||||
not callable = viableCallable(call) and
|
||||
not any(ConsistencyConfiguration c).viableImplInCallContextTooLargeExclude(call, ctx, callable)
|
||||
}
|
||||
|
||||
query predicate uniqueParameterNodeAtPosition(
|
||||
DataFlowCallable c, ParameterPosition pos, Node p, string msg
|
||||
) {
|
||||
isParameterNode(p, c, pos) and
|
||||
not exists(unique(Node p0 | isParameterNode(p0, c, pos))) and
|
||||
msg = "Parameters with overlapping positions."
|
||||
}
|
||||
|
||||
query predicate uniqueParameterNodePosition(
|
||||
DataFlowCallable c, ParameterPosition pos, Node p, string msg
|
||||
) {
|
||||
isParameterNode(p, c, pos) and
|
||||
not exists(unique(ParameterPosition pos0 | isParameterNode(p, c, pos0))) and
|
||||
msg = "Parameter node with multiple positions."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,13 @@ private import semmle.code.java.dataflow.internal.AccessPathSyntax as AccessPath
|
||||
|
||||
class SummarizedCallableBase = FlowSummary::SummarizedCallableBase;
|
||||
|
||||
/**
|
||||
* A module for importing frameworks that define synthetic globals.
|
||||
*/
|
||||
private module SyntheticGlobals {
|
||||
private import semmle.code.java.frameworks.android.Intent
|
||||
}
|
||||
|
||||
DataFlowCallable inject(SummarizedCallable c) { result.asSummarizedCallable() = c }
|
||||
|
||||
/** Gets the parameter position of the instance parameter. */
|
||||
|
||||
@@ -10,7 +10,6 @@ private import semmle.code.java.dataflow.internal.ContainerFlow
|
||||
private import semmle.code.java.frameworks.spring.SpringController
|
||||
private import semmle.code.java.frameworks.spring.SpringHttp
|
||||
private import semmle.code.java.frameworks.Networking
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
private import semmle.code.java.dataflow.FlowSources
|
||||
private import semmle.code.java.dataflow.internal.DataFlowPrivate
|
||||
import semmle.code.java.dataflow.FlowSteps
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
import java
|
||||
private import semmle.code.java.dataflow.FlowSteps
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
|
||||
class ApacheHttpGetParams extends Method {
|
||||
ApacheHttpGetParams() {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
*/
|
||||
|
||||
import java
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
|
||||
/** The class `flexjson.JSONDeserializer`. */
|
||||
class FlexjsonDeserializer extends RefType {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
*/
|
||||
|
||||
import java
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
|
||||
/** The interface `org.hibernate.query.QueryProducer`. */
|
||||
class HibernateQueryProducer extends RefType {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
import java
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
private import semmle.code.java.security.XSS
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Provides classes and predicates for working with the Java JDBC API.
|
||||
*/
|
||||
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
import java
|
||||
|
||||
/*--- Types ---*/
|
||||
/** The interface `java.sql.Connection`. */
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
*/
|
||||
|
||||
import java
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
|
||||
/** The class `jodd.json.Parser`. */
|
||||
class JoddJsonParser extends RefType {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
import java
|
||||
private import semmle.code.java.dataflow.DataFlow
|
||||
private import semmle.code.java.dataflow.TaintTracking
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
|
||||
/** The class `org.apache.ibatis.jdbc.SqlRunner`. */
|
||||
class MyBatisSqlRunner extends RefType {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** Definitions related to `java.util.regex`. */
|
||||
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
import java
|
||||
|
||||
/** The class `java.util.regex.Pattern`. */
|
||||
class TypeRegexPattern extends Class {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
*/
|
||||
|
||||
import java
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
|
||||
/** The class `org.springframework.jdbc.core.JdbcTemplate`. */
|
||||
class JdbcTemplate extends RefType {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/** Definitions related to `java.util.stream`. */
|
||||
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
private import semmle.code.java.dataflow.FlowSummary
|
||||
|
||||
private class CollectCall extends MethodAccess {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
*/
|
||||
|
||||
import java
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
private import semmle.code.xml.AndroidManifest
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
*/
|
||||
|
||||
import java
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
|
||||
/** The class `android.content.ContentValues`. */
|
||||
class ContentValues extends Class {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/** Provides classes and predicates for working with SQLite databases. */
|
||||
|
||||
import java
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
private import semmle.code.java.dataflow.FlowSteps
|
||||
private import semmle.code.java.frameworks.android.Android
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/** Provides classes related to `android.content.SharedPreferences`. */
|
||||
|
||||
import java
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
|
||||
/** The interface `android.content.SharedPreferences`. */
|
||||
class SharedPreferences extends Interface {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import java
|
||||
private import semmle.code.java.dataflow.DataFlow
|
||||
private import semmle.code.java.dataflow.FlowSteps
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
|
||||
/** The class `androidx.slice.SliceProvider`. */
|
||||
class SliceProvider extends Class {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import java
|
||||
private import semmle.code.java.dataflow.FlowSteps
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
|
||||
/**
|
||||
* The method `isEmpty` in either `org.apache.commons.collections.CollectionUtils`
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import java
|
||||
private import semmle.code.java.dataflow.FlowSteps
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
|
||||
/**
|
||||
* The class `org.apache.commons.lang.RandomStringUtils` or `org.apache.commons.lang3.RandomStringUtils`.
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import java
|
||||
private import semmle.code.java.dataflow.DataFlow
|
||||
private import semmle.code.java.dataflow.FlowSteps
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
private import semmle.code.java.Collections
|
||||
|
||||
private string guavaCollectPackage() { result = "com.google.common.collect" }
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
*/
|
||||
|
||||
import java
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
|
||||
/**
|
||||
* Methods annotated with this allow for generation of "plain SQL"
|
||||
|
||||
@@ -9,7 +9,6 @@ import semmle.code.java.Reflection
|
||||
import semmle.code.java.dataflow.DataFlow
|
||||
private import semmle.code.java.dataflow.internal.DataFlowForSerializability
|
||||
import semmle.code.java.dataflow.FlowSteps
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
|
||||
/**
|
||||
* A `@com.fasterxml.jackson.annotation.JsonIgnore` annoation.
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/** Provides classes and predicates for working with JavaServer Faces renderer. */
|
||||
|
||||
import java
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
|
||||
/**
|
||||
* The JSF class `FacesContext` for processing HTTP requests.
|
||||
|
||||
8
java/ql/lib/semmle/code/java/frameworks/kotlin/IO.qll
Normal file
8
java/ql/lib/semmle/code/java/frameworks/kotlin/IO.qll
Normal file
@@ -0,0 +1,8 @@
|
||||
/** Provides classes and predicates related to `kotlin.io`. */
|
||||
|
||||
import java
|
||||
|
||||
/** The type `kotlin.io.FilesKt`, where `File` extension methods are declared. */
|
||||
class FilesKt extends RefType {
|
||||
FilesKt() { this.hasQualifiedName("kotlin.io", "FilesKt") }
|
||||
}
|
||||
21
java/ql/lib/semmle/code/java/frameworks/kotlin/Text.qll
Normal file
21
java/ql/lib/semmle/code/java/frameworks/kotlin/Text.qll
Normal file
@@ -0,0 +1,21 @@
|
||||
/** Provides classes and predicates related to `kotlin.text`. */
|
||||
|
||||
import java
|
||||
|
||||
/** The type `kotlin.text.StringsKt`, where `String` extension methods are declared. */
|
||||
class StringsKt extends RefType {
|
||||
StringsKt() { this.hasQualifiedName("kotlin.text", "StringsKt") }
|
||||
}
|
||||
|
||||
/** A call to the extension method `String.toRegex` from `kotlin.text`. */
|
||||
class KtToRegex extends MethodAccess {
|
||||
KtToRegex() {
|
||||
this.getMethod().getDeclaringType() instanceof StringsKt and
|
||||
this.getMethod().hasName("toRegex")
|
||||
}
|
||||
|
||||
/** Gets the constant string value being converted to a regex by this call. */
|
||||
string getExpressionString() {
|
||||
result = this.getArgument(0).(CompileTimeConstantExpr).getStringValue()
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@
|
||||
import java
|
||||
private import semmle.code.java.dataflow.DataFlow
|
||||
private import semmle.code.java.dataflow.FlowSteps
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
|
||||
/** A reference type that extends a parameterization the Promise type. */
|
||||
private class RatpackPromise extends RefType {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
import java
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
private import semmle.code.java.dataflow.DataFlow
|
||||
private import semmle.code.java.frameworks.spring.SpringController
|
||||
private import semmle.code.java.security.XSS as XSS
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
import java
|
||||
import SpringHttp
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
|
||||
/** The class `org.springframework.web.client.RestTemplate`. */
|
||||
class SpringRestTemplate extends Class {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
private import semmle.code.java.security.Encryption
|
||||
private import semmle.code.java.dataflow.DataFlow
|
||||
private import semmle.code.java.security.internal.EncryptionKeySizes
|
||||
|
||||
/** A source for an insufficient key size. */
|
||||
abstract class InsufficientKeySizeSource extends DataFlow::Node {
|
||||
@@ -21,39 +22,67 @@ private module Asymmetric {
|
||||
private module NonEllipticCurve {
|
||||
/** A source for an insufficient key size used in RSA, DSA, and DH algorithms. */
|
||||
private class Source extends InsufficientKeySizeSource {
|
||||
Source() { this.asExpr().(IntegerLiteral).getIntValue() < getMinKeySize() }
|
||||
string algoName;
|
||||
|
||||
override predicate hasState(DataFlow::FlowState state) { state = getMinKeySize().toString() }
|
||||
Source() { this.asExpr().(IntegerLiteral).getIntValue() < getMinKeySize(algoName) }
|
||||
|
||||
override predicate hasState(DataFlow::FlowState state) {
|
||||
state = getMinKeySize(algoName).toString()
|
||||
}
|
||||
}
|
||||
|
||||
/** A sink for an insufficient key size used in RSA, DSA, and DH algorithms. */
|
||||
private class Sink extends InsufficientKeySizeSink {
|
||||
string algoName;
|
||||
|
||||
Sink() {
|
||||
exists(KeyPairGenInit kpgInit, KeyPairGen kpg |
|
||||
kpg.getAlgoName().matches(["RSA", "DSA", "DH"]) and
|
||||
algoName in ["RSA", "DSA", "DH"] and
|
||||
kpg.getAlgoName() = algoName and
|
||||
DataFlow::localExprFlow(kpg, kpgInit.getQualifier()) and
|
||||
this.asExpr() = kpgInit.getKeySizeArg()
|
||||
)
|
||||
or
|
||||
exists(Spec spec | this.asExpr() = spec.getKeySizeArg())
|
||||
exists(Spec spec | this.asExpr() = spec.getKeySizeArg() and algoName = spec.getAlgoName())
|
||||
}
|
||||
|
||||
override predicate hasState(DataFlow::FlowState state) { state = getMinKeySize().toString() }
|
||||
override predicate hasState(DataFlow::FlowState state) {
|
||||
state = getMinKeySize(algoName).toString()
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the minimum recommended key size for RSA, DSA, and DH algorithms. */
|
||||
private int getMinKeySize() { result = 2048 }
|
||||
private int getMinKeySize(string algoName) {
|
||||
algoName = "RSA" and
|
||||
result = minSecureKeySizeRsa()
|
||||
or
|
||||
algoName = "DSA" and
|
||||
result = minSecureKeySizeDsa()
|
||||
or
|
||||
algoName = "DH" and
|
||||
result = minSecureKeySizeDh()
|
||||
}
|
||||
|
||||
/** An instance of an RSA, DSA, or DH algorithm specification. */
|
||||
private class Spec extends ClassInstanceExpr {
|
||||
string algoName;
|
||||
|
||||
Spec() {
|
||||
this.getConstructedType() instanceof RsaKeyGenParameterSpec or
|
||||
this.getConstructedType() instanceof DsaGenParameterSpec or
|
||||
this.getConstructedType() instanceof DhGenParameterSpec
|
||||
this.getConstructedType() instanceof RsaKeyGenParameterSpec and
|
||||
algoName = "RSA"
|
||||
or
|
||||
this.getConstructedType() instanceof DsaGenParameterSpec and
|
||||
algoName = "DSA"
|
||||
or
|
||||
this.getConstructedType() instanceof DhGenParameterSpec and
|
||||
algoName = "DH"
|
||||
}
|
||||
|
||||
/** Gets the `keysize` argument of this instance. */
|
||||
Argument getKeySizeArg() { result = this.getArgument(0) }
|
||||
|
||||
/** Gets the algorithm name of this spec. */
|
||||
string getAlgoName() { result = algoName }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +116,7 @@ private module Asymmetric {
|
||||
}
|
||||
|
||||
/** Returns the minimum recommended key size for elliptic curve (EC) algorithms. */
|
||||
private int getMinKeySize() { result = 256 }
|
||||
private int getMinKeySize() { result = minSecureKeySizeEcc() }
|
||||
|
||||
/** Returns the key size from an EC algorithm's curve name string */
|
||||
bindingset[algorithm]
|
||||
@@ -168,7 +197,7 @@ private module Symmetric {
|
||||
}
|
||||
|
||||
/** Returns the minimum recommended key size for AES algorithms. */
|
||||
private int getMinKeySize() { result = 128 }
|
||||
private int getMinKeySize() { result = minSecureKeySizeAes() }
|
||||
|
||||
/** A call to the `init` method declared in `javax.crypto.KeyGenerator`. */
|
||||
private class KeyGenInit extends MethodAccess {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import java
|
||||
import semmle.code.java.security.PartialPathTraversal
|
||||
import semmle.code.java.dataflow.DataFlow
|
||||
import semmle.code.java.dataflow.ExternalFlow
|
||||
import semmle.code.java.dataflow.TaintTracking
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
import java
|
||||
private import semmle.code.java.controlflow.Guards
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
private import semmle.code.java.dataflow.FlowSources
|
||||
private import semmle.code.java.dataflow.SSA
|
||||
private import semmle.code.java.frameworks.kotlin.IO
|
||||
private import semmle.code.java.frameworks.kotlin.Text
|
||||
|
||||
/** A sanitizer that protects against path injection vulnerabilities. */
|
||||
abstract class PathInjectionSanitizer extends DataFlow::Node { }
|
||||
@@ -51,12 +52,14 @@ private predicate exactPathMatchGuard(Guard g, Expr e, boolean branch) {
|
||||
t instanceof TypeUri or
|
||||
t instanceof TypePath or
|
||||
t instanceof TypeFile or
|
||||
t.hasQualifiedName("android.net", "Uri")
|
||||
t.hasQualifiedName("android.net", "Uri") or
|
||||
t instanceof StringsKt or
|
||||
t instanceof FilesKt
|
||||
|
|
||||
e = getVisualQualifier(ma).getUnderlyingExpr() and
|
||||
ma.getMethod().getDeclaringType() = t and
|
||||
ma = g and
|
||||
ma.getMethod().getName() = ["equals", "equalsIgnoreCase"] and
|
||||
e = ma.getQualifier() and
|
||||
getSourceMethod(ma.getMethod()).hasName(["equals", "equalsIgnoreCase"]) and
|
||||
branch = true
|
||||
)
|
||||
}
|
||||
@@ -87,7 +90,7 @@ private class AllowedPrefixGuard extends PathGuard instanceof MethodAccess {
|
||||
not isDisallowedWord(super.getAnArgument())
|
||||
}
|
||||
|
||||
override Expr getCheckedExpr() { result = super.getQualifier() }
|
||||
override Expr getCheckedExpr() { result = getVisualQualifier(this).getUnderlyingExpr() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,7 +157,7 @@ private class BlockListGuard extends PathGuard instanceof MethodAccess {
|
||||
isDisallowedWord(super.getAnArgument())
|
||||
}
|
||||
|
||||
override Expr getCheckedExpr() { result = super.getQualifier() }
|
||||
override Expr getCheckedExpr() { result = getVisualQualifier(this).getUnderlyingExpr() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,15 +191,31 @@ private class BlockListSanitizer extends PathInjectionSanitizer {
|
||||
}
|
||||
}
|
||||
|
||||
private class ConstantOrRegex extends Expr {
|
||||
ConstantOrRegex() {
|
||||
this instanceof CompileTimeConstantExpr or
|
||||
this instanceof KtToRegex
|
||||
}
|
||||
|
||||
string getStringValue() {
|
||||
result = this.(CompileTimeConstantExpr).getStringValue() or
|
||||
result = this.(KtToRegex).getExpressionString()
|
||||
}
|
||||
}
|
||||
|
||||
private predicate isStringPrefixMatch(MethodAccess ma) {
|
||||
exists(Method m | m = ma.getMethod() and m.getDeclaringType() instanceof TypeString |
|
||||
m.hasName("startsWith")
|
||||
exists(Method m, RefType t |
|
||||
m.getDeclaringType() = t and
|
||||
(t instanceof TypeString or t instanceof StringsKt) and
|
||||
m = ma.getMethod()
|
||||
|
|
||||
getSourceMethod(m).hasName("startsWith")
|
||||
or
|
||||
m.hasName("regionMatches") and
|
||||
ma.getArgument(0).(CompileTimeConstantExpr).getIntValue() = 0
|
||||
getSourceMethod(m).hasName("regionMatches") and
|
||||
getVisualArgument(ma, 0).(CompileTimeConstantExpr).getIntValue() = 0
|
||||
or
|
||||
m.hasName("matches") and
|
||||
not ma.getArgument(0).(CompileTimeConstantExpr).getStringValue().matches(".*%")
|
||||
not getVisualArgument(ma, 0).(ConstantOrRegex).getStringValue().matches(".*%")
|
||||
)
|
||||
}
|
||||
|
||||
@@ -206,52 +225,52 @@ private predicate isStringPrefixMatch(MethodAccess ma) {
|
||||
private predicate isStringPartialMatch(MethodAccess ma) {
|
||||
isStringPrefixMatch(ma)
|
||||
or
|
||||
ma.getMethod().getDeclaringType() instanceof TypeString and
|
||||
ma.getMethod().hasName(["contains", "matches", "regionMatches", "indexOf", "lastIndexOf"])
|
||||
exists(RefType t | t = ma.getMethod().getDeclaringType() |
|
||||
t instanceof TypeString or t instanceof StringsKt
|
||||
) and
|
||||
getSourceMethod(ma.getMethod())
|
||||
.hasName(["contains", "matches", "regionMatches", "indexOf", "lastIndexOf"])
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `ma` is a call to a method that checks whether a path starts with a prefix.
|
||||
*/
|
||||
private predicate isPathPrefixMatch(MethodAccess ma) {
|
||||
exists(RefType t |
|
||||
t instanceof TypePath
|
||||
or
|
||||
t.hasQualifiedName("kotlin.io", "FilesKt")
|
||||
|
|
||||
t = ma.getMethod().getDeclaringType() and
|
||||
ma.getMethod().hasName("startsWith")
|
||||
)
|
||||
exists(RefType t | t = ma.getMethod().getDeclaringType() |
|
||||
t instanceof TypePath or t instanceof FilesKt
|
||||
) and
|
||||
getSourceMethod(ma.getMethod()).hasName("startsWith")
|
||||
}
|
||||
|
||||
private predicate isDisallowedWord(CompileTimeConstantExpr word) {
|
||||
private predicate isDisallowedWord(ConstantOrRegex word) {
|
||||
word.getStringValue().matches(["/", "\\", "%WEB-INF%", "%/data%"])
|
||||
}
|
||||
|
||||
/** A complementary guard that protects against path traversal, by looking for the literal `..`. */
|
||||
private class PathTraversalGuard extends PathGuard {
|
||||
Expr checkedExpr;
|
||||
|
||||
PathTraversalGuard() {
|
||||
exists(MethodAccess ma |
|
||||
ma.getMethod().getDeclaringType() instanceof TypeString and
|
||||
exists(MethodAccess ma, Method m, RefType t |
|
||||
m = ma.getMethod() and
|
||||
t = m.getDeclaringType() and
|
||||
(t instanceof TypeString or t instanceof StringsKt) and
|
||||
checkedExpr = getVisualQualifier(ma).getUnderlyingExpr() and
|
||||
ma.getAnArgument().(CompileTimeConstantExpr).getStringValue() = ".."
|
||||
|
|
||||
this = ma and
|
||||
ma.getMethod().hasName("contains")
|
||||
getSourceMethod(m).hasName("contains")
|
||||
or
|
||||
exists(EqualityTest eq |
|
||||
this = eq and
|
||||
ma.getMethod().hasName(["indexOf", "lastIndexOf"]) and
|
||||
getSourceMethod(m).hasName(["indexOf", "lastIndexOf"]) and
|
||||
eq.getAnOperand() = ma and
|
||||
eq.getAnOperand().(CompileTimeConstantExpr).getIntValue() = -1
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override Expr getCheckedExpr() {
|
||||
exists(MethodAccess ma | ma = this.(EqualityTest).getAnOperand() or ma = this |
|
||||
result = ma.getQualifier()
|
||||
)
|
||||
}
|
||||
override Expr getCheckedExpr() { result = checkedExpr }
|
||||
|
||||
boolean getBranch() {
|
||||
this instanceof MethodAccess and result = false
|
||||
@@ -263,15 +282,46 @@ private class PathTraversalGuard extends PathGuard {
|
||||
/** A complementary sanitizer that protects against path traversal using path normalization. */
|
||||
private class PathNormalizeSanitizer extends MethodAccess {
|
||||
PathNormalizeSanitizer() {
|
||||
exists(RefType t |
|
||||
t instanceof TypePath or
|
||||
t.hasQualifiedName("kotlin.io", "FilesKt")
|
||||
|
|
||||
this.getMethod().getDeclaringType() = t and
|
||||
exists(RefType t | this.getMethod().getDeclaringType() = t |
|
||||
(t instanceof TypePath or t instanceof FilesKt) and
|
||||
this.getMethod().hasName("normalize")
|
||||
or
|
||||
t instanceof TypeFile and
|
||||
this.getMethod().hasName(["getCanonicalPath", "getCanonicalFile"])
|
||||
)
|
||||
or
|
||||
this.getMethod().getDeclaringType() instanceof TypeFile and
|
||||
this.getMethod().hasName(["getCanonicalPath", "getCanonicalFile"])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the qualifier of `ma` as seen in the source code.
|
||||
* This is a helper predicate to solve discrepancies between
|
||||
* what `getQualifier` actually gets in Java and Kotlin.
|
||||
*/
|
||||
private Expr getVisualQualifier(MethodAccess ma) {
|
||||
if getSourceMethod(ma.getMethod()) instanceof ExtensionMethod
|
||||
then result = ma.getArgument(0)
|
||||
else result = ma.getQualifier()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the argument of `ma` at position `argPos` as seen in the source code.
|
||||
* This is a helper predicate to solve discrepancies between
|
||||
* what `getArgument` actually gets in Java and Kotlin.
|
||||
*/
|
||||
bindingset[argPos]
|
||||
private Argument getVisualArgument(MethodAccess ma, int argPos) {
|
||||
if getSourceMethod(ma.getMethod()) instanceof ExtensionMethod
|
||||
then result = ma.getArgument(argPos + 1)
|
||||
else result = ma.getArgument(argPos)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the proxied method if `m` is a Kotlin proxy that supplies default parameter values.
|
||||
* Otherwise, just gets `m`.
|
||||
*/
|
||||
private Method getSourceMethod(Method m) {
|
||||
m = result.getKotlinParameterDefaultsProxy()
|
||||
or
|
||||
not exists(Method src | m = src.getKotlinParameterDefaultsProxy()) and
|
||||
result = m
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import java
|
||||
private import semmle.code.java.dataflow.DataFlow
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
private import semmle.code.java.frameworks.spring.SpringExpression
|
||||
|
||||
/** A data flow sink for unvalidated user input that is used to construct SpEL expressions. */
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/** Provides taint tracking configurations to be used in unsafe content URI resolution queries. */
|
||||
|
||||
import java
|
||||
import semmle.code.java.dataflow.ExternalFlow
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
import semmle.code.java.dataflow.TaintTracking
|
||||
import semmle.code.java.security.UnsafeContentUriResolution
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*
|
||||
* Provides predicates for recommended encryption key sizes.
|
||||
* Such that we can share this logic across our CodeQL analysis of different languages.
|
||||
*/
|
||||
|
||||
/** Returns the minimum recommended key size for RSA. */
|
||||
int minSecureKeySizeRsa() { result = 2048 }
|
||||
|
||||
/** Returns the minimum recommended key size for DSA. */
|
||||
int minSecureKeySizeDsa() { result = 2048 }
|
||||
|
||||
/** Returns the minimum recommended key size for DH. */
|
||||
int minSecureKeySizeDh() { result = 2048 }
|
||||
|
||||
/** Returns the minimum recommended key size for elliptic curve cryptography. */
|
||||
int minSecureKeySizeEcc() { result = 256 }
|
||||
|
||||
/** Returns the minimum recommended key size for AES. */
|
||||
int minSecureKeySizeAes() { result = 128 }
|
||||
@@ -1,3 +1,7 @@
|
||||
## 0.4.5
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.4.4
|
||||
|
||||
### New Queries
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Enabling JavaScript in an Android WebView allows the execution of
|
||||
JavaScript code in the context of the running application. This creates a
|
||||
cross-site scripting vulnerability.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
For example, if your application's WebView allows for visiting web pages
|
||||
that you do not trust, it is possible for an attacker to lead the user to
|
||||
a page which loads malicious JavaScript.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You can enable or disable Javascript execution using
|
||||
the <code>setJavaScriptEnabled</code> method of the settings of a WebView.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>JavaScript execution is disabled by default. You can explicitly disable
|
||||
it by calling <code>setJavaScriptEnabled(false)</code> on the settings of
|
||||
the WebView.</p>
|
||||
|
||||
<p>If JavaScript is necessary, only load content from trusted servers using encrypted channels, such as HTTPS with certificate verification.</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>In the following (bad) example, a WebView has JavaScript enabled in its settings:</p>
|
||||
|
||||
<sample src="WebSettingsEnableJavascript.java"/>
|
||||
|
||||
<p>In the following (good) example, a WebView explicitly disallows JavaScript execution:</p>
|
||||
|
||||
<sample src="WebSettingsDisableJavascript.java"/>
|
||||
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
Android documentation: <a href="https://developer.android.com/reference/android/webkit/WebSettings#setJavaScriptEnabled(boolean)">setJavaScriptEnabled</a>
|
||||
</li>
|
||||
</references>
|
||||
|
||||
</qhelp>
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* @name Android WebView JavaScript settings
|
||||
* @description Enabling JavaScript execution in a WebView can result in cross-site scripting attacks.
|
||||
* @kind problem
|
||||
* @id java/android-websettings-javascript-enabled
|
||||
* @problem.severity warning
|
||||
* @security-severity 6.1
|
||||
* @precision medium
|
||||
* @tags security
|
||||
* external/cwe/cwe-079
|
||||
*/
|
||||
|
||||
import java
|
||||
import semmle.code.java.frameworks.android.WebView
|
||||
|
||||
from MethodAccess ma
|
||||
where
|
||||
ma.getMethod() instanceof AllowJavaScriptMethod and
|
||||
ma.getArgument(0).(CompileTimeConstantExpr).getBooleanValue() = true
|
||||
select ma, "JavaScript execution enabled in WebView."
|
||||
@@ -0,0 +1,2 @@
|
||||
WebSettings settings = webview.getSettings();
|
||||
settings.setJavaScriptEnabled(false);
|
||||
@@ -0,0 +1,2 @@
|
||||
WebSettings settings = webview.getSettings();
|
||||
settings.setJavaScriptEnabled(true);
|
||||
@@ -26,18 +26,31 @@ predicate isSafeSecureCookieSetting(Expr e) {
|
||||
)
|
||||
}
|
||||
|
||||
class SecureCookieConfiguration extends DataFlow::Configuration {
|
||||
SecureCookieConfiguration() { this = "SecureCookieConfiguration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
exists(MethodAccess ma, Method m | ma.getMethod() = m |
|
||||
m.getDeclaringType() instanceof TypeCookie and
|
||||
m.getName() = "setSecure" and
|
||||
source.asExpr() = ma.getQualifier() and
|
||||
forex(DataFlow::Node argSource |
|
||||
DataFlow::localFlow(argSource, DataFlow::exprNode(ma.getArgument(0))) and
|
||||
not DataFlow::localFlowStep(_, argSource)
|
||||
|
|
||||
isSafeSecureCookieSetting(argSource.asExpr())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
sink.asExpr() =
|
||||
any(MethodAccess add | add.getMethod() instanceof ResponseAddCookieMethod).getArgument(0)
|
||||
}
|
||||
}
|
||||
|
||||
from MethodAccess add
|
||||
where
|
||||
add.getMethod() instanceof ResponseAddCookieMethod and
|
||||
not exists(Variable cookie, MethodAccess m |
|
||||
add.getArgument(0) = cookie.getAnAccess() and
|
||||
m.getMethod().getName() = "setSecure" and
|
||||
forex(DataFlow::Node argSource |
|
||||
DataFlow::localFlow(argSource, DataFlow::exprNode(m.getArgument(0))) and
|
||||
not DataFlow::localFlowStep(_, argSource)
|
||||
|
|
||||
isSafeSecureCookieSetting(argSource.asExpr())
|
||||
) and
|
||||
m.getQualifier() = cookie.getAnAccess()
|
||||
)
|
||||
not any(SecureCookieConfiguration df).hasFlowToExpr(add.getArgument(0))
|
||||
select add, "Cookie is added to response without the 'secure' flag being set."
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: newQuery
|
||||
---
|
||||
* Added a new query, `java/android-websettings-javascript-enabled`, to detect if JavaScript execution is enabled in an Android WebView.
|
||||
3
java/ql/src/change-notes/released/0.4.5.md
Normal file
3
java/ql/src/change-notes/released/0.4.5.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.4.5
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.4.4
|
||||
lastReleaseVersion: 0.4.5
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import java
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
private import semmle.code.java.dataflow.FlowSources
|
||||
|
||||
/** The class `com.jfinal.core.Controller`. */
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
*/
|
||||
|
||||
import java
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
private import semmle.code.java.dataflow.FlowSources
|
||||
|
||||
/** A utility class for resolving resource locations to files in the file system in the Spring framework. */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/java-queries
|
||||
version: 0.4.5-dev
|
||||
version: 0.4.6-dev
|
||||
groups:
|
||||
- java
|
||||
- queries
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
import java
|
||||
private import semmle.code.java.dataflow.internal.DataFlowUtil
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
private import semmle.code.java.dataflow.FlowSummary
|
||||
private import semmle.code.java.dataflow.internal.FlowSummaryImpl
|
||||
private import FlowTestCaseUtils
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import sys
|
||||
import os.path
|
||||
import subprocess
|
||||
|
||||
# Add Model as Data script directory to sys.path.
|
||||
gitroot = subprocess.check_output(["git", "rev-parse", "--show-toplevel"]).decode("utf-8").strip()
|
||||
madpath = os.path.join(gitroot, "misc/scripts/models-as-data/")
|
||||
sys.path.append(madpath)
|
||||
|
||||
import generate_flow_model_extensions as model
|
||||
|
||||
language = "java"
|
||||
model.Generator.make(language).run()
|
||||
@@ -5,19 +5,17 @@
|
||||
from pathlib import Path
|
||||
import json
|
||||
import os
|
||||
import requests
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import sys
|
||||
|
||||
|
||||
defaultModelPath = "java/ql/lib/semmle/code/java/frameworks"
|
||||
lgtmSlugToModelFile = {
|
||||
# "apache/commons-beanutils": "apache/BeanUtilsGenerated.qll",
|
||||
# "apache/commons-codec": "apache/CodecGenerated.qll",
|
||||
# "apache/commons-lang": "apache/Lang3Generated.qll",
|
||||
"apache/commons-io": "apache/IOGenerated.qll",
|
||||
"apache/commons-io": "org.apache.commons.io",
|
||||
}
|
||||
|
||||
|
||||
@@ -36,13 +34,12 @@ def regenerateModel(lgtmSlug, extractedDb):
|
||||
print("ERROR: slug " + lgtmSlug +
|
||||
" is not mapped to a model file in script " + sys.argv[0])
|
||||
sys.exit(1)
|
||||
modelFile = defaultModelPath + "/" + lgtmSlugToModelFile[lgtmSlug]
|
||||
modelFile = lgtmSlugToModelFile[lgtmSlug]
|
||||
codeQlRoot = findGitRoot()
|
||||
targetModel = codeQlRoot + "/" + modelFile
|
||||
subprocess.check_call([codeQlRoot + "/java/ql/src/utils/model-generator/GenerateFlowModel.py",
|
||||
"--with-summaries", "--with-sinks",
|
||||
extractedDb, targetModel])
|
||||
print("Regenerated " + targetModel)
|
||||
"--with-summaries", "--with-sinks", "--with-negative-summaries",
|
||||
extractedDb, modelFile])
|
||||
print("Regenerated " + modelFile)
|
||||
shutil.rmtree(tmpDir)
|
||||
|
||||
|
||||
|
||||
@@ -16,9 +16,3 @@ class X {
|
||||
annotation class Ann(
|
||||
val p: Int,
|
||||
@get:JvmName("w") val q: Int)
|
||||
|
||||
// Diagnostic Matches: Incomplete annotation: @kotlin.jvm.JvmName(name="changeY")
|
||||
// Diagnostic Matches: Incomplete annotation: @kotlin.jvm.JvmName(name="getX_prop")
|
||||
// Diagnostic Matches: Incomplete annotation: @kotlin.jvm.JvmName(name="method")
|
||||
// Diagnostic Matches: Incomplete annotation: @kotlin.jvm.JvmName(name="y")
|
||||
// Diagnostic Matches: Unknown location for kotlin.jvm.JvmName
|
||||
|
||||
@@ -110,6 +110,3 @@ public class TakesArrayList {
|
||||
fun inInArrayComparableAny(c: Comparable<Array<in Array<in Any>>>) { }
|
||||
|
||||
}
|
||||
|
||||
// Diagnostic Matches: Completion failure for type: org.jetbrains.annotations.NotNull
|
||||
// Diagnostic Matches: Unknown location for org.jetbrains.annotations.NotNull
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import java
|
||||
import semmle.code.java.dataflow.TaintTracking
|
||||
import semmle.code.java.dataflow.ExternalFlow
|
||||
|
||||
class Conf extends TaintTracking::Configuration {
|
||||
Conf() { this = "qltest:extension-method" }
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import java
|
||||
import semmle.code.java.dataflow.TaintTracking
|
||||
import semmle.code.java.dataflow.ExternalFlow
|
||||
|
||||
class Conf extends TaintTracking::Configuration {
|
||||
Conf() { this = "qltest:foreach-array-iterator" }
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import java
|
||||
import semmle.code.java.dataflow.TaintTracking
|
||||
import semmle.code.java.dataflow.ExternalFlow
|
||||
|
||||
class Conf extends TaintTracking::Configuration {
|
||||
Conf() { this = "qltest:lambdaFlow" }
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import java
|
||||
import semmle.code.java.dataflow.TaintTracking
|
||||
import semmle.code.java.dataflow.ExternalFlow
|
||||
|
||||
class Conf extends TaintTracking::Configuration {
|
||||
Conf() { this = "qltest:notNullExprFlow" }
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import java
|
||||
import semmle.code.java.dataflow.TaintTracking
|
||||
import semmle.code.java.dataflow.ExternalFlow
|
||||
|
||||
class Conf extends TaintTracking::Configuration {
|
||||
Conf() { this = "qltest:notNullExprFlow" }
|
||||
|
||||
@@ -30,6 +30,3 @@ fun foo() {
|
||||
fun String.baz(p1: String): String { return "Baz" }
|
||||
"someString".baz("bazParam")
|
||||
}
|
||||
|
||||
// Diagnostic Matches: Completion failure for type: org.jetbrains.annotations.NotNull
|
||||
// Diagnostic Matches: Unknown location for org.jetbrains.annotations.NotNull
|
||||
|
||||
@@ -5,7 +5,6 @@ classLocations
|
||||
| main.A<Object> | file:///!unknown-binary-location/main/A.class:0:0:0:0 | file:///!unknown-binary-location/main/A.class:0:0:0:0 |
|
||||
| main.A<String> | A.class:0:0:0:0 | A.class:0:0:0:0 |
|
||||
| main.A<String> | file:///!unknown-binary-location/main/A.class:0:0:0:0 | file:///!unknown-binary-location/main/A.class:0:0:0:0 |
|
||||
| main.B | generics-location.testproj/test.class.files/main/B.class:0:0:0:0 | generics-location.testproj/test.class.files/main/B.class:0:0:0:0 |
|
||||
| main.B | generics.kt:3:1:11:1 | generics.kt:3:1:11:1 |
|
||||
| main.B<Integer> | generics-location.testproj/test.class.files/main/B.class:0:0:0:0 | generics-location.testproj/test.class.files/main/B.class:0:0:0:0 |
|
||||
| main.B<Object> | file:///!unknown-binary-location/main/B.class:0:0:0:0 | file:///!unknown-binary-location/main/B.class:0:0:0:0 |
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
| TestClassA.kt:2:1:3:1 | TestClassA |
|
||||
| TestClassA.kt:2:1:3:1 | TestClassA<> |
|
||||
| TestClassAUser.kt:0:0:0:0 | TestClassAUserKt |
|
||||
| TestClassAUser.kt:15:1:15:24 | TestClassAUser |
|
||||
| file:///!unknown-binary-location/TestClassA.class:0:0:0:0 | TestClassA<TestClassAUser> |
|
||||
|
||||
@@ -11,9 +11,13 @@ public class Java {
|
||||
return super.fn0(x);
|
||||
}
|
||||
|
||||
/*
|
||||
// Java interop disabled as it currently doesn't work (no symbol fn1(int, Completion<...>) gets created)
|
||||
// TODO: re-enable this test once a correct function signature is extracted
|
||||
@Override
|
||||
public Object fn1(int x, Continuation<? super String> $completion) {
|
||||
return super.fn1(x, $completion);
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,3 @@ class Dkotlin : Base() {
|
||||
override fun fn0(x: Int): String = super.fn0(x)
|
||||
override suspend fun fn1(x: Int): String = super.fn1(x)
|
||||
}
|
||||
|
||||
// Diagnostic Matches: Completion failure for type: org.jetbrains.annotations.NotNull
|
||||
// Diagnostic Matches: Completion failure for type: org.jetbrains.annotations.Nullable
|
||||
// Diagnostic Matches: Unknown location for org.jetbrains.annotations.NotNull
|
||||
// Diagnostic Matches: Unknown location for org.jetbrains.annotations.Nullable
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
methods
|
||||
| Java.java:4:7:4:13 | javaFun | javaFun() |
|
||||
| Java.java:10:17:10:19 | fn0 | fn0(int) |
|
||||
| Java.java:15:17:15:19 | fn1 | fn1(int,kotlin.coroutines.Continuation) |
|
||||
| Kotlin.kt:2:2:4:2 | kotlinFun | kotlinFun() |
|
||||
| Kotlin.kt:8:10:8:38 | fn0 | fn0(int) |
|
||||
| Kotlin.kt:9:18:9:46 | fn1 | fn1(int) |
|
||||
@@ -9,15 +8,12 @@ methods
|
||||
| Kotlin.kt:14:22:14:59 | fn1 | fn1(int) |
|
||||
overrides
|
||||
| Java.java:10:17:10:19 | fn0 | Kotlin.kt:8:10:8:38 | fn0 |
|
||||
| Java.java:15:17:15:19 | fn1 | java_and_kotlin.testproj/test.class.files/Base.class:0:0:0:0 | fn1 |
|
||||
| Kotlin.kt:13:14:13:51 | fn0 | Kotlin.kt:8:10:8:38 | fn0 |
|
||||
| Kotlin.kt:14:22:14:59 | fn1 | Kotlin.kt:9:18:9:46 | fn1 |
|
||||
signature_mismatch
|
||||
| Kotlin.kt:9:18:9:46 | fn1 | fn1(int) |
|
||||
| java_and_kotlin.testproj/test.class.files/Base.class:0:0:0:0 | fn1 | fn1(int,kotlin.coroutines.Continuation) |
|
||||
#select
|
||||
| Java.java:5:3:5:26 | kotlinFun(...) | Kotlin.kt:2:2:4:2 | kotlinFun |
|
||||
| Java.java:11:11:11:22 | fn0(...) | Kotlin.kt:8:10:8:38 | fn0 |
|
||||
| Java.java:16:11:16:35 | fn1(...) | java_and_kotlin.testproj/test.class.files/Base.class:0:0:0:0 | fn1 |
|
||||
| Kotlin.kt:13:40:13:51 | fn0(...) | Kotlin.kt:8:10:8:38 | fn0 |
|
||||
| Kotlin.kt:14:48:14:59 | fn1(...) | Kotlin.kt:9:18:9:46 | fn1 |
|
||||
|
||||
@@ -3,14 +3,10 @@ isInternal
|
||||
| Kotlin.kt:2:11:3:2 | kotlinFun$main |
|
||||
| Kotlin.kt:6:10:6:36 | topLevelKotlinFun |
|
||||
modifiers_methods
|
||||
| file://:0:0:0:0 | final | Kotlin.kt:2:11:3:2 | kotlinFun$main |
|
||||
| file://:0:0:0:0 | final | Kotlin.kt:6:10:6:36 | topLevelKotlinFun |
|
||||
| file://:0:0:0:0 | internal | Kotlin.kt:2:11:3:2 | kotlinFun$main |
|
||||
| file://:0:0:0:0 | internal | Kotlin.kt:6:10:6:36 | topLevelKotlinFun |
|
||||
| file://:0:0:0:0 | static | Kotlin.kt:6:10:6:36 | topLevelKotlinFun |
|
||||
#select
|
||||
| Kotlin.kt:2:11:3:2 | kotlinFun$main | final |
|
||||
| Kotlin.kt:2:11:3:2 | kotlinFun$main | internal |
|
||||
| Kotlin.kt:6:10:6:36 | topLevelKotlinFun | final |
|
||||
| Kotlin.kt:6:10:6:36 | topLevelKotlinFun | internal |
|
||||
| Kotlin.kt:6:10:6:36 | topLevelKotlinFun | static |
|
||||
|
||||
@@ -85,12 +85,3 @@ public class TestDefaultParameterReference {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Diagnostic Matches: Completion failure for type: org.jetbrains.annotations.NotNull
|
||||
// Diagnostic Matches: Unknown location for org.jetbrains.annotations.NotNull
|
||||
// Diagnostic Matches: Completion failure for type: kotlin.jvm.JvmOverloads
|
||||
// Diagnostic Matches: Completion failure for type: kotlin.jvm.JvmStatic
|
||||
// Diagnostic Matches: Completion failure for type: org.jetbrains.annotations.Nullable
|
||||
// Diagnostic Matches: Unknown location for kotlin.jvm.JvmOverloads
|
||||
// Diagnostic Matches: Unknown location for kotlin.jvm.JvmStatic
|
||||
// Diagnostic Matches: Unknown location for org.jetbrains.annotations.Nullable
|
||||
|
||||
@@ -4,8 +4,3 @@ public class A {
|
||||
fun <T> genericFunctionWithOverloads(x: T? = null, y: List<T>? = null, z: T? = null): T? = z
|
||||
|
||||
}
|
||||
|
||||
// Diagnostic Matches: Completion failure for type: kotlin.jvm.JvmOverloads
|
||||
// Diagnostic Matches: Completion failure for type: org.jetbrains.annotations.Nullable
|
||||
// Diagnostic Matches: Unknown location for kotlin.jvm.JvmOverloads
|
||||
// Diagnostic Matches: Unknown location for org.jetbrains.annotations.Nullable
|
||||
|
||||
@@ -141,11 +141,11 @@ test.kt:
|
||||
# 65| 0: [MethodAccess] getPropWithStaticGetter(...)
|
||||
# 65| -1: [TypeAccess] NonCompanion
|
||||
# 9| 2: [Class] HasCompanion
|
||||
# 9| 2: [Constructor] HasCompanion
|
||||
# 9| 1: [Constructor] HasCompanion
|
||||
# 9| 5: [BlockStmt] { ... }
|
||||
# 9| 0: [SuperConstructorInvocationStmt] super(...)
|
||||
# 9| 1: [BlockStmt] { ... }
|
||||
# 11| 3: [Class] Companion
|
||||
# 11| 2: [Class] Companion
|
||||
# 11| 1: [Constructor] Companion
|
||||
# 11| 5: [BlockStmt] { ... }
|
||||
# 11| 0: [SuperConstructorInvocationStmt] super(...)
|
||||
@@ -157,11 +157,9 @@ test.kt:
|
||||
# 17| 0: [KtInitializerAssignExpr] ...=...
|
||||
# 17| 0: [VarAccess] nonStaticProp
|
||||
# 13| 2: [Method] staticMethod
|
||||
#-----| 1: (Annotations)
|
||||
# 13| 3: [TypeAccess] String
|
||||
#-----| 4: (Parameters)
|
||||
# 13| 0: [Parameter] s
|
||||
#-----| -1: (Annotations)
|
||||
# 13| 0: [TypeAccess] String
|
||||
# 13| 5: [BlockStmt] { ... }
|
||||
# 13| 0: [ReturnStmt] return ...
|
||||
@@ -169,11 +167,9 @@ test.kt:
|
||||
# 13| -1: [ThisAccess] this
|
||||
# 13| 0: [VarAccess] s
|
||||
# 14| 3: [Method] nonStaticMethod
|
||||
#-----| 1: (Annotations)
|
||||
# 14| 3: [TypeAccess] String
|
||||
#-----| 4: (Parameters)
|
||||
# 14| 0: [Parameter] s
|
||||
#-----| -1: (Annotations)
|
||||
# 14| 0: [TypeAccess] String
|
||||
# 14| 5: [BlockStmt] { ... }
|
||||
# 14| 0: [ReturnStmt] return ...
|
||||
@@ -184,7 +180,6 @@ test.kt:
|
||||
# 16| -1: [TypeAccess] String
|
||||
# 16| 0: [StringLiteral] "a"
|
||||
# 16| 5: [Method] getStaticProp
|
||||
#-----| 1: (Annotations)
|
||||
# 16| 3: [TypeAccess] String
|
||||
# 16| 5: [BlockStmt] { ... }
|
||||
# 16| 0: [ReturnStmt] return ...
|
||||
@@ -194,7 +189,6 @@ test.kt:
|
||||
# 16| 3: [TypeAccess] Unit
|
||||
#-----| 4: (Parameters)
|
||||
# 16| 0: [Parameter] <set-?>
|
||||
#-----| -1: (Annotations)
|
||||
# 16| 0: [TypeAccess] String
|
||||
# 16| 5: [BlockStmt] { ... }
|
||||
# 16| 0: [ExprStmt] <Expr>;
|
||||
@@ -206,7 +200,6 @@ test.kt:
|
||||
# 17| -1: [TypeAccess] String
|
||||
# 17| 0: [StringLiteral] "b"
|
||||
# 17| 8: [Method] getNonStaticProp
|
||||
#-----| 1: (Annotations)
|
||||
# 17| 3: [TypeAccess] String
|
||||
# 17| 5: [BlockStmt] { ... }
|
||||
# 17| 0: [ReturnStmt] return ...
|
||||
@@ -216,7 +209,6 @@ test.kt:
|
||||
# 17| 3: [TypeAccess] Unit
|
||||
#-----| 4: (Parameters)
|
||||
# 17| 0: [Parameter] <set-?>
|
||||
#-----| -1: (Annotations)
|
||||
# 17| 0: [TypeAccess] String
|
||||
# 17| 5: [BlockStmt] { ... }
|
||||
# 17| 0: [ExprStmt] <Expr>;
|
||||
@@ -225,7 +217,6 @@ test.kt:
|
||||
# 17| -1: [ThisAccess] this
|
||||
# 17| 1: [VarAccess] <set-?>
|
||||
# 20| 10: [Method] getPropWithStaticGetter
|
||||
#-----| 1: (Annotations)
|
||||
# 20| 3: [TypeAccess] String
|
||||
# 20| 5: [BlockStmt] { ... }
|
||||
# 20| 0: [ReturnStmt] return ...
|
||||
@@ -235,7 +226,6 @@ test.kt:
|
||||
# 21| 3: [TypeAccess] Unit
|
||||
#-----| 4: (Parameters)
|
||||
# 21| 0: [Parameter] s
|
||||
#-----| -1: (Annotations)
|
||||
# 21| 0: [TypeAccess] String
|
||||
# 21| 5: [BlockStmt] { ... }
|
||||
# 21| 0: [ExprStmt] <Expr>;
|
||||
@@ -243,30 +233,25 @@ test.kt:
|
||||
# 21| -1: [ThisAccess] this
|
||||
# 21| 0: [VarAccess] s
|
||||
# 24| 12: [Method] getPropWithStaticSetter
|
||||
#-----| 1: (Annotations)
|
||||
# 24| 3: [TypeAccess] String
|
||||
# 24| 5: [BlockStmt] { ... }
|
||||
# 24| 0: [ReturnStmt] return ...
|
||||
# 24| 0: [MethodAccess] getPropWithStaticGetter(...)
|
||||
# 24| -1: [ThisAccess] this
|
||||
# 25| 13: [Method] setPropWithStaticSetter
|
||||
#-----| 1: (Annotations)
|
||||
# 25| 3: [TypeAccess] Unit
|
||||
#-----| 4: (Parameters)
|
||||
# 25| 0: [Parameter] s
|
||||
#-----| -1: (Annotations)
|
||||
# 25| 0: [TypeAccess] String
|
||||
# 25| 5: [BlockStmt] { ... }
|
||||
# 25| 0: [ExprStmt] <Expr>;
|
||||
# 25| 0: [MethodAccess] setPropWithStaticGetter(...)
|
||||
# 25| -1: [ThisAccess] this
|
||||
# 25| 0: [VarAccess] s
|
||||
# 13| 4: [Method] staticMethod
|
||||
#-----| 1: (Annotations)
|
||||
# 13| 3: [Method] staticMethod
|
||||
# 13| 3: [TypeAccess] String
|
||||
#-----| 4: (Parameters)
|
||||
# 13| 0: [Parameter] s
|
||||
#-----| -1: (Annotations)
|
||||
# 13| 0: [TypeAccess] String
|
||||
# 13| 5: [BlockStmt] { ... }
|
||||
# 13| 0: [ReturnStmt] return ...
|
||||
@@ -274,19 +259,17 @@ test.kt:
|
||||
# 13| -1: [VarAccess] HasCompanion.Companion
|
||||
# 13| -1: [TypeAccess] HasCompanion
|
||||
# 13| 0: [VarAccess] s
|
||||
# 16| 5: [Method] getStaticProp
|
||||
#-----| 1: (Annotations)
|
||||
# 16| 4: [Method] getStaticProp
|
||||
# 16| 3: [TypeAccess] String
|
||||
# 16| 5: [BlockStmt] { ... }
|
||||
# 16| 0: [ReturnStmt] return ...
|
||||
# 16| 0: [MethodAccess] getStaticProp(...)
|
||||
# 16| -1: [VarAccess] HasCompanion.Companion
|
||||
# 16| -1: [TypeAccess] HasCompanion
|
||||
# 16| 6: [Method] setStaticProp
|
||||
# 16| 5: [Method] setStaticProp
|
||||
# 16| 3: [TypeAccess] Unit
|
||||
#-----| 4: (Parameters)
|
||||
# 16| 0: [Parameter] <set-?>
|
||||
#-----| -1: (Annotations)
|
||||
# 16| 0: [TypeAccess] String
|
||||
# 16| 5: [BlockStmt] { ... }
|
||||
# 16| 0: [ReturnStmt] return ...
|
||||
@@ -294,20 +277,17 @@ test.kt:
|
||||
# 16| -1: [VarAccess] HasCompanion.Companion
|
||||
# 16| -1: [TypeAccess] HasCompanion
|
||||
# 16| 0: [VarAccess] <set-?>
|
||||
# 20| 7: [Method] getPropWithStaticGetter
|
||||
#-----| 1: (Annotations)
|
||||
# 20| 6: [Method] getPropWithStaticGetter
|
||||
# 20| 3: [TypeAccess] String
|
||||
# 20| 5: [BlockStmt] { ... }
|
||||
# 20| 0: [ReturnStmt] return ...
|
||||
# 20| 0: [MethodAccess] getPropWithStaticGetter(...)
|
||||
# 20| -1: [VarAccess] HasCompanion.Companion
|
||||
# 20| -1: [TypeAccess] HasCompanion
|
||||
# 25| 8: [Method] setPropWithStaticSetter
|
||||
#-----| 1: (Annotations)
|
||||
# 25| 7: [Method] setPropWithStaticSetter
|
||||
# 25| 3: [TypeAccess] Unit
|
||||
#-----| 4: (Parameters)
|
||||
# 25| 0: [Parameter] s
|
||||
#-----| -1: (Annotations)
|
||||
# 25| 0: [TypeAccess] String
|
||||
# 25| 5: [BlockStmt] { ... }
|
||||
# 25| 0: [ReturnStmt] return ...
|
||||
@@ -316,7 +296,7 @@ test.kt:
|
||||
# 25| -1: [TypeAccess] HasCompanion
|
||||
# 25| 0: [VarAccess] s
|
||||
# 31| 3: [Class] NonCompanion
|
||||
# 31| 2: [Constructor] NonCompanion
|
||||
# 31| 1: [Constructor] NonCompanion
|
||||
# 31| 5: [BlockStmt] { ... }
|
||||
# 31| 0: [SuperConstructorInvocationStmt] super(...)
|
||||
# 31| 1: [BlockStmt] { ... }
|
||||
@@ -326,12 +306,10 @@ test.kt:
|
||||
# 37| 1: [ExprStmt] <Expr>;
|
||||
# 37| 0: [KtInitializerAssignExpr] ...=...
|
||||
# 37| 0: [VarAccess] nonStaticProp
|
||||
# 33| 3: [Method] staticMethod
|
||||
#-----| 1: (Annotations)
|
||||
# 33| 2: [Method] staticMethod
|
||||
# 33| 3: [TypeAccess] String
|
||||
#-----| 4: (Parameters)
|
||||
# 33| 0: [Parameter] s
|
||||
#-----| -1: (Annotations)
|
||||
# 33| 0: [TypeAccess] String
|
||||
# 33| 5: [BlockStmt] { ... }
|
||||
# 33| 0: [ReturnStmt] return ...
|
||||
@@ -339,34 +317,30 @@ test.kt:
|
||||
# 33| -1: [VarAccess] NonCompanion.INSTANCE
|
||||
# 33| -1: [TypeAccess] NonCompanion
|
||||
# 33| 0: [VarAccess] s
|
||||
# 34| 4: [Method] nonStaticMethod
|
||||
#-----| 1: (Annotations)
|
||||
# 34| 3: [Method] nonStaticMethod
|
||||
# 34| 3: [TypeAccess] String
|
||||
#-----| 4: (Parameters)
|
||||
# 34| 0: [Parameter] s
|
||||
#-----| -1: (Annotations)
|
||||
# 34| 0: [TypeAccess] String
|
||||
# 34| 5: [BlockStmt] { ... }
|
||||
# 34| 0: [ReturnStmt] return ...
|
||||
# 34| 0: [MethodAccess] staticMethod(...)
|
||||
# 34| -1: [TypeAccess] NonCompanion
|
||||
# 34| 0: [VarAccess] s
|
||||
# 36| 5: [FieldDeclaration] String staticProp;
|
||||
# 36| 4: [FieldDeclaration] String staticProp;
|
||||
# 36| -1: [TypeAccess] String
|
||||
# 36| 0: [StringLiteral] "a"
|
||||
# 36| 6: [Method] getStaticProp
|
||||
#-----| 1: (Annotations)
|
||||
# 36| 5: [Method] getStaticProp
|
||||
# 36| 3: [TypeAccess] String
|
||||
# 36| 5: [BlockStmt] { ... }
|
||||
# 36| 0: [ReturnStmt] return ...
|
||||
# 36| 0: [VarAccess] NonCompanion.INSTANCE.staticProp
|
||||
# 36| -1: [VarAccess] NonCompanion.INSTANCE
|
||||
# 36| -1: [TypeAccess] NonCompanion
|
||||
# 36| 7: [Method] setStaticProp
|
||||
# 36| 6: [Method] setStaticProp
|
||||
# 36| 3: [TypeAccess] Unit
|
||||
#-----| 4: (Parameters)
|
||||
# 36| 0: [Parameter] <set-?>
|
||||
#-----| -1: (Annotations)
|
||||
# 36| 0: [TypeAccess] String
|
||||
# 36| 5: [BlockStmt] { ... }
|
||||
# 36| 0: [ExprStmt] <Expr>;
|
||||
@@ -375,21 +349,19 @@ test.kt:
|
||||
# 36| -1: [VarAccess] NonCompanion.INSTANCE
|
||||
# 36| -1: [TypeAccess] NonCompanion
|
||||
# 36| 1: [VarAccess] <set-?>
|
||||
# 37| 8: [FieldDeclaration] String nonStaticProp;
|
||||
# 37| 7: [FieldDeclaration] String nonStaticProp;
|
||||
# 37| -1: [TypeAccess] String
|
||||
# 37| 0: [StringLiteral] "b"
|
||||
# 37| 9: [Method] getNonStaticProp
|
||||
#-----| 1: (Annotations)
|
||||
# 37| 8: [Method] getNonStaticProp
|
||||
# 37| 3: [TypeAccess] String
|
||||
# 37| 5: [BlockStmt] { ... }
|
||||
# 37| 0: [ReturnStmt] return ...
|
||||
# 37| 0: [VarAccess] this.nonStaticProp
|
||||
# 37| -1: [ThisAccess] this
|
||||
# 37| 10: [Method] setNonStaticProp
|
||||
# 37| 9: [Method] setNonStaticProp
|
||||
# 37| 3: [TypeAccess] Unit
|
||||
#-----| 4: (Parameters)
|
||||
# 37| 0: [Parameter] <set-?>
|
||||
#-----| -1: (Annotations)
|
||||
# 37| 0: [TypeAccess] String
|
||||
# 37| 5: [BlockStmt] { ... }
|
||||
# 37| 0: [ExprStmt] <Expr>;
|
||||
@@ -397,38 +369,33 @@ test.kt:
|
||||
# 37| 0: [VarAccess] this.nonStaticProp
|
||||
# 37| -1: [ThisAccess] this
|
||||
# 37| 1: [VarAccess] <set-?>
|
||||
# 40| 11: [Method] getPropWithStaticGetter
|
||||
#-----| 1: (Annotations)
|
||||
# 40| 10: [Method] getPropWithStaticGetter
|
||||
# 40| 3: [TypeAccess] String
|
||||
# 40| 5: [BlockStmt] { ... }
|
||||
# 40| 0: [ReturnStmt] return ...
|
||||
# 40| 0: [MethodAccess] getPropWithStaticSetter(...)
|
||||
# 40| -1: [VarAccess] NonCompanion.INSTANCE
|
||||
# 40| -1: [TypeAccess] NonCompanion
|
||||
# 41| 12: [Method] setPropWithStaticGetter
|
||||
# 41| 11: [Method] setPropWithStaticGetter
|
||||
# 41| 3: [TypeAccess] Unit
|
||||
#-----| 4: (Parameters)
|
||||
# 41| 0: [Parameter] s
|
||||
#-----| -1: (Annotations)
|
||||
# 41| 0: [TypeAccess] String
|
||||
# 41| 5: [BlockStmt] { ... }
|
||||
# 41| 0: [ExprStmt] <Expr>;
|
||||
# 41| 0: [MethodAccess] setPropWithStaticSetter(...)
|
||||
# 41| -1: [TypeAccess] NonCompanion
|
||||
# 41| 0: [VarAccess] s
|
||||
# 44| 13: [Method] getPropWithStaticSetter
|
||||
#-----| 1: (Annotations)
|
||||
# 44| 12: [Method] getPropWithStaticSetter
|
||||
# 44| 3: [TypeAccess] String
|
||||
# 44| 5: [BlockStmt] { ... }
|
||||
# 44| 0: [ReturnStmt] return ...
|
||||
# 44| 0: [MethodAccess] getPropWithStaticGetter(...)
|
||||
# 44| -1: [TypeAccess] NonCompanion
|
||||
# 45| 14: [Method] setPropWithStaticSetter
|
||||
#-----| 1: (Annotations)
|
||||
# 45| 13: [Method] setPropWithStaticSetter
|
||||
# 45| 3: [TypeAccess] Unit
|
||||
#-----| 4: (Parameters)
|
||||
# 45| 0: [Parameter] s
|
||||
#-----| -1: (Annotations)
|
||||
# 45| 0: [TypeAccess] String
|
||||
# 45| 5: [BlockStmt] { ... }
|
||||
# 45| 0: [ExprStmt] <Expr>;
|
||||
|
||||
@@ -65,8 +65,3 @@ fun externalUser() {
|
||||
NonCompanion.propWithStaticSetter = NonCompanion.propWithStaticGetter
|
||||
|
||||
}
|
||||
|
||||
// Diagnostic Matches: Completion failure for type: kotlin.jvm.JvmStatic
|
||||
// Diagnostic Matches: Completion failure for type: org.jetbrains.annotations.NotNull
|
||||
// Diagnostic Matches: Unknown location for kotlin.jvm.JvmStatic
|
||||
// Diagnostic Matches: Unknown location for org.jetbrains.annotations.NotNull
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import java
|
||||
import semmle.code.java.dataflow.DataFlow
|
||||
import semmle.code.java.dataflow.ExternalFlow
|
||||
import TestUtilities.InlineExpectationsTest
|
||||
|
||||
class Conf extends DataFlow::Configuration {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import java
|
||||
import semmle.code.java.dataflow.DataFlow
|
||||
import semmle.code.java.dataflow.ExternalFlow
|
||||
import TestUtilities.InlineFlowTest
|
||||
|
||||
class HasFlowTest extends InlineFlowTest {
|
||||
|
||||
@@ -279,7 +279,7 @@ public class Test {
|
||||
}
|
||||
|
||||
private void blockListGuardValidation(String path) throws Exception {
|
||||
if (path.contains("..") || !path.startsWith("/data"))
|
||||
if (path.contains("..") || path.startsWith("/data"))
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
|
||||
499
java/ql/test/library-tests/pathsanitizer/TestKt.kt
Normal file
499
java/ql/test/library-tests/pathsanitizer/TestKt.kt
Normal file
@@ -0,0 +1,499 @@
|
||||
import java.io.File
|
||||
import java.net.URI
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import android.net.Uri
|
||||
|
||||
class TestKt {
|
||||
fun source(): Any? {
|
||||
return null
|
||||
}
|
||||
|
||||
fun sink(o: Any?) {}
|
||||
|
||||
@Throws(Exception::class)
|
||||
private fun exactPathMatchGuardValidation(path: String?) {
|
||||
if (!path.equals("/safe/path")) throw Exception()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun exactPathMatchGuard() {
|
||||
run {
|
||||
val source = source() as String?
|
||||
if (source!!.equals("/safe/path"))
|
||||
sink(source) // Safe
|
||||
else
|
||||
sink(source) // $ hasTaintFlow
|
||||
}
|
||||
run {
|
||||
val source = source() as URI?
|
||||
if (source!!.equals(URI("http://safe/uri")))
|
||||
sink(source) // Safe
|
||||
else
|
||||
sink(source) // $ hasTaintFlow
|
||||
}
|
||||
run {
|
||||
val source = source() as File?
|
||||
if (source!!.equals(File("/safe/file")))
|
||||
sink(source) // Safe
|
||||
else
|
||||
sink(source) // $ hasTaintFlow
|
||||
}
|
||||
run {
|
||||
val source = source() as Uri?
|
||||
if (source!!.equals(Uri.parse("http://safe/uri")))
|
||||
sink(source) // Safe
|
||||
else
|
||||
sink(source) // $ hasTaintFlow
|
||||
}
|
||||
run {
|
||||
val source = source() as String?
|
||||
exactPathMatchGuardValidation(source)
|
||||
sink(source) // Safe
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
private fun allowListGuardValidation(path: String?) {
|
||||
if (path!!.contains("..") || !path.startsWith("/safe")) throw Exception()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun allowListGuard() {
|
||||
// Prefix check by itself is not enough
|
||||
run {
|
||||
val source = source() as String?
|
||||
if (source!!.startsWith("/safe")) {
|
||||
sink(source) // $ hasTaintFlow
|
||||
} else
|
||||
sink(source) // $ hasTaintFlow
|
||||
}
|
||||
// PathTraversalGuard + allowListGuard
|
||||
run {
|
||||
val source = source() as String?
|
||||
if (!source!!.contains("..") && source.startsWith("/safe"))
|
||||
sink(source) // Safe
|
||||
else
|
||||
sink(source) // $ hasTaintFlow
|
||||
}
|
||||
run {
|
||||
val source = source() as String?
|
||||
if (source!!.indexOf("..") == -1 && source.startsWith("/safe"))
|
||||
sink(source) // Safe
|
||||
else
|
||||
sink(source) // $ hasTaintFlow
|
||||
}
|
||||
run {
|
||||
val source = source() as String?
|
||||
if (source!!.lastIndexOf("..") == -1 && source.startsWith("/safe"))
|
||||
sink(source) // Safe
|
||||
else
|
||||
sink(source) // $ hasTaintFlow
|
||||
}
|
||||
// PathTraversalSanitizer + allowListGuard
|
||||
run {
|
||||
val source: File? = source() as File?
|
||||
val normalized: String = source!!.canonicalPath
|
||||
if (normalized.startsWith("/safe")) {
|
||||
sink(source) // Safe
|
||||
sink(normalized) // Safe
|
||||
} else {
|
||||
sink(source) // $ hasTaintFlow
|
||||
sink(normalized) // $ hasTaintFlow
|
||||
}
|
||||
}
|
||||
run {
|
||||
val source: File? = source() as File?
|
||||
val normalized: File = source!!.canonicalFile
|
||||
if (normalized.startsWith("/safe")) {
|
||||
sink(source) // Safe
|
||||
sink(normalized) // Safe
|
||||
} else {
|
||||
sink(source) // $ hasTaintFlow
|
||||
sink(normalized) // $ hasTaintFlow
|
||||
}
|
||||
}
|
||||
run {
|
||||
val source: File? = source() as File?
|
||||
val normalized: String = source!!.canonicalFile.toString()
|
||||
if (normalized.startsWith("/safe")) {
|
||||
sink(source) // Safe
|
||||
sink(normalized) // Safe
|
||||
} else {
|
||||
sink(source) // $ hasTaintFlow
|
||||
sink(normalized) // $ hasTaintFlow
|
||||
}
|
||||
}
|
||||
run {
|
||||
val source = source() as String?
|
||||
val normalized: Path = Paths.get(source).normalize()
|
||||
if (normalized.startsWith("/safe")) {
|
||||
sink(source) // Safe
|
||||
sink(normalized) // Safe
|
||||
} else {
|
||||
sink(source) // $ hasTaintFlow
|
||||
sink(normalized) // $ hasTaintFlow
|
||||
}
|
||||
}
|
||||
run {
|
||||
val source = source() as String?
|
||||
// normalize().toString() gets extracted as Object.toString, stopping taint here
|
||||
val normalized: String = Paths.get(source).normalize().toString()
|
||||
if (normalized.startsWith("/safe")) {
|
||||
sink(source) // $ SPURIOUS: hasTaintFlow
|
||||
sink(normalized) // Safe
|
||||
} else {
|
||||
sink(source) // $ hasTaintFlow
|
||||
sink(normalized) // $ MISSING: hasTaintFlow
|
||||
}
|
||||
}
|
||||
run {
|
||||
val source = source() as String?
|
||||
// normalize().toString() gets extracted as Object.toString, stopping taint here
|
||||
val normalized: String = Paths.get(source).normalize().toString()
|
||||
if (normalized.regionMatches(0, "/safe", 0, 5)) {
|
||||
sink(source) // $ SPURIOUS: hasTaintFlow
|
||||
sink(normalized) // Safe
|
||||
} else {
|
||||
sink(source) // $ hasTaintFlow
|
||||
sink(normalized) // $ MISSING: hasTaintFlow
|
||||
}
|
||||
}
|
||||
run {
|
||||
val source = source() as String?
|
||||
// normalize().toString() gets extracted as Object.toString, stopping taint here
|
||||
val normalized: String = Paths.get(source).normalize().toString()
|
||||
if (normalized.matches("/safe/.*".toRegex())) {
|
||||
sink(source) // $ SPURIOUS: hasTaintFlow
|
||||
sink(normalized) // Safe
|
||||
} else {
|
||||
sink(source) // $ hasTaintFlow
|
||||
sink(normalized) // $ MISSING: hasTaintFlow
|
||||
}
|
||||
}
|
||||
// validation method
|
||||
run {
|
||||
val source = source() as String?
|
||||
allowListGuardValidation(source)
|
||||
sink(source) // Safe
|
||||
}
|
||||
// PathInjectionSanitizer + partial string match is considered unsafe
|
||||
run {
|
||||
val source = source() as String?
|
||||
// normalize().toString() gets extracted as Object.toString, stopping taint here
|
||||
val normalized: String = Paths.get(source).normalize().toString()
|
||||
if (normalized.contains("/safe")) {
|
||||
sink(source) // $ hasTaintFlow
|
||||
sink(normalized) // $ MISSING: hasTaintFlow
|
||||
} else {
|
||||
sink(source) // $ hasTaintFlow
|
||||
sink(normalized) // $ MISSING: hasTaintFlow
|
||||
}
|
||||
}
|
||||
run {
|
||||
val source = source() as String?
|
||||
// normalize().toString() gets extracted as Object.toString, stopping taint here
|
||||
val normalized: String = Paths.get(source).normalize().toString()
|
||||
if (normalized.regionMatches(1, "/safe", 0, 5)) {
|
||||
sink(source) // $ hasTaintFlow
|
||||
sink(normalized) // $ MISSING: hasTaintFlow
|
||||
} else {
|
||||
sink(source) // $ hasTaintFlow
|
||||
sink(normalized) // $ MISSING: hasTaintFlow
|
||||
}
|
||||
}
|
||||
run {
|
||||
val source = source() as String?
|
||||
// normalize().toString() gets extracted as Object.toString, stopping taint here
|
||||
val normalized: String = Paths.get(source).normalize().toString()
|
||||
if (normalized.matches(".*/safe/.*".toRegex())) {
|
||||
sink(source) // $ hasTaintFlow
|
||||
sink(normalized) // $ MISSING: hasTaintFlow
|
||||
} else {
|
||||
sink(source) // $ hasTaintFlow
|
||||
sink(normalized) // $ MISSING: hasTaintFlow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
private fun dotDotCheckGuardValidation(path: String?) {
|
||||
if (!path!!.startsWith("/safe") || path.contains("..")) throw Exception()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun dotDotCheckGuard() {
|
||||
// dot dot check by itself is not enough
|
||||
run {
|
||||
val source = source() as String?
|
||||
if (!source!!.contains("..")) {
|
||||
sink(source) // $ hasTaintFlow
|
||||
} else
|
||||
sink(source) // $ hasTaintFlow
|
||||
}
|
||||
// allowListGuard + dotDotCheckGuard
|
||||
run {
|
||||
val source = source() as String?
|
||||
if (source!!.startsWith("/safe") && !source.contains(".."))
|
||||
sink(source) // Safe
|
||||
else
|
||||
sink(source) // $ hasTaintFlow
|
||||
}
|
||||
run {
|
||||
val source = source() as String?
|
||||
if (source!!.startsWith("/safe") && source.indexOf("..") == -1)
|
||||
sink(source) // Safe
|
||||
else
|
||||
sink(source) // $ hasTaintFlow
|
||||
}
|
||||
run {
|
||||
val source = source() as String?
|
||||
if (!source!!.startsWith("/safe") || source.indexOf("..") != -1)
|
||||
sink(source) // $ hasTaintFlow
|
||||
else
|
||||
sink(source) // Safe
|
||||
}
|
||||
run {
|
||||
val source = source() as String?
|
||||
if (source!!.startsWith("/safe") && source.lastIndexOf("..") == -1)
|
||||
sink(source) // Safe
|
||||
else
|
||||
sink(source) // $ hasTaintFlow
|
||||
}
|
||||
// blockListGuard + dotDotCheckGuard
|
||||
run {
|
||||
val source = source() as String?
|
||||
if (!source!!.startsWith("/data") && !source.contains(".."))
|
||||
sink(source) // Safe
|
||||
else
|
||||
sink(source) // $ hasTaintFlow
|
||||
}
|
||||
run {
|
||||
val source = source() as String?
|
||||
if (!source!!.startsWith("/data") && source.indexOf("..") == -1)
|
||||
sink(source) // Safe
|
||||
else
|
||||
sink(source) // $ hasTaintFlow
|
||||
}
|
||||
run {
|
||||
val source = source() as String?
|
||||
if (source!!.startsWith("/data") || source.indexOf("..") != -1)
|
||||
sink(source) // $ hasTaintFlow
|
||||
else
|
||||
sink(source) // Safe
|
||||
}
|
||||
run {
|
||||
val source = source() as String?
|
||||
if (!source!!.startsWith("/data") && source.lastIndexOf("..") == -1)
|
||||
sink(source) // Safe
|
||||
else
|
||||
sink(source) // $ hasTaintFlow
|
||||
}
|
||||
// validation method
|
||||
run {
|
||||
val source = source() as String?
|
||||
dotDotCheckGuardValidation(source)
|
||||
sink(source) // Safe
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
private fun blockListGuardValidation(path: String?) {
|
||||
if (path!!.contains("..") || path.startsWith("/data")) throw Exception()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun blockListGuard() {
|
||||
// Prefix check by itself is not enough
|
||||
run {
|
||||
val source = source() as String?
|
||||
if (!source!!.startsWith("/data")) {
|
||||
sink(source) // $ hasTaintFlow
|
||||
} else
|
||||
sink(source) // $ hasTaintFlow
|
||||
}
|
||||
// PathTraversalGuard + blockListGuard
|
||||
run {
|
||||
val source = source() as String?
|
||||
if (!source!!.contains("..") && !source.startsWith("/data"))
|
||||
sink(source) // Safe
|
||||
else
|
||||
sink(source) // $ hasTaintFlow
|
||||
}
|
||||
run {
|
||||
val source = source() as String?
|
||||
if (source!!.indexOf("..") == -1 && !source.startsWith("/data"))
|
||||
sink(source) // Safe
|
||||
else
|
||||
sink(source) // $ hasTaintFlow
|
||||
}
|
||||
run {
|
||||
val source = source() as String?
|
||||
if (source!!.lastIndexOf("..") == -1 && !source.startsWith("/data"))
|
||||
sink(source) // Safe
|
||||
else
|
||||
sink(source) // $ hasTaintFlow
|
||||
}
|
||||
// PathTraversalSanitizer + blockListGuard
|
||||
run {
|
||||
val source: File? = source() as File?
|
||||
val normalized: String = source!!.canonicalPath
|
||||
if (!normalized.startsWith("/data")) {
|
||||
sink(source) // Safe
|
||||
sink(normalized) // Safe
|
||||
} else {
|
||||
sink(source) // $ hasTaintFlow
|
||||
sink(normalized) // $ hasTaintFlow
|
||||
}
|
||||
}
|
||||
run {
|
||||
val source: File? = source() as File?
|
||||
val normalized: File = source!!.canonicalFile
|
||||
if (!normalized.startsWith("/data")) {
|
||||
sink(source) // Safe
|
||||
sink(normalized) // Safe
|
||||
} else {
|
||||
sink(source) // $ hasTaintFlow
|
||||
sink(normalized) // $ hasTaintFlow
|
||||
}
|
||||
}
|
||||
run {
|
||||
val source: File? = source() as File?
|
||||
val normalized: String = source!!.canonicalFile.toString()
|
||||
if (!normalized.startsWith("/data")) {
|
||||
sink(source) // Safe
|
||||
sink(normalized) // Safe
|
||||
} else {
|
||||
sink(source) // $ hasTaintFlow
|
||||
sink(normalized) // $ hasTaintFlow
|
||||
}
|
||||
}
|
||||
run {
|
||||
val source = source() as String?
|
||||
val normalized: Path = Paths.get(source).normalize()
|
||||
if (!normalized.startsWith("/data")) {
|
||||
sink(source) // Safe
|
||||
sink(normalized) // Safe
|
||||
} else {
|
||||
sink(source) // $ hasTaintFlow
|
||||
sink(normalized) // $ hasTaintFlow
|
||||
}
|
||||
}
|
||||
run {
|
||||
val source = source() as String?
|
||||
// normalize().toString() gets extracted as Object.toString, stopping taint here
|
||||
val normalized: String = Paths.get(source).normalize().toString()
|
||||
if (!normalized.startsWith("/data")) {
|
||||
sink(source) // $ SPURIOUS: hasTaintFlow
|
||||
sink(normalized) // Safe
|
||||
} else {
|
||||
sink(source) // $ hasTaintFlow
|
||||
sink(normalized) // $ MISSING: hasTaintFlow
|
||||
}
|
||||
}
|
||||
run {
|
||||
val source = source() as String?
|
||||
// normalize().toString() gets extracted as Object.toString, stopping taint here
|
||||
val normalized: String = Paths.get(source).normalize().toString()
|
||||
if (!normalized.regionMatches(0, "/data", 0, 5)) {
|
||||
sink(source) // $ SPURIOUS: hasTaintFlow
|
||||
sink(normalized) // Safe
|
||||
} else {
|
||||
sink(source) // $ hasTaintFlow
|
||||
sink(normalized) // $ MISSING: hasTaintFlow
|
||||
}
|
||||
}
|
||||
run {
|
||||
val source = source() as String?
|
||||
// normalize().toString() gets extracted as Object.toString, stopping taint here
|
||||
val normalized: String = Paths.get(source).normalize().toString()
|
||||
if (!normalized.matches("/data/.*".toRegex())) {
|
||||
sink(source) // $ SPURIOUS: hasTaintFlow
|
||||
sink(normalized) // Safe
|
||||
} else {
|
||||
sink(source) // $ hasTaintFlow
|
||||
sink(normalized) // $ MISSING: hasTaintFlow
|
||||
}
|
||||
}
|
||||
// validation method
|
||||
run {
|
||||
val source = source() as String?
|
||||
blockListGuardValidation(source)
|
||||
sink(source) // Safe
|
||||
}
|
||||
// PathInjectionSanitizer + partial string match with disallowed words
|
||||
run {
|
||||
val source = source() as String?
|
||||
// normalize().toString() gets extracted as Object.toString, stopping taint here
|
||||
val normalized: String = Paths.get(source).normalize().toString()
|
||||
if (!normalized.contains("/")) {
|
||||
sink(source) // $ SPURIOUS: hasTaintFlow
|
||||
sink(normalized) // Safe
|
||||
} else {
|
||||
sink(source) // $ hasTaintFlow
|
||||
sink(normalized) // $ MISSING: hasTaintFlow
|
||||
}
|
||||
}
|
||||
run {
|
||||
val source = source() as String?
|
||||
// normalize().toString() gets extracted as Object.toString, stopping taint here
|
||||
val normalized: String = Paths.get(source).normalize().toString()
|
||||
if (!normalized.regionMatches(1, "/", 0, 5)) {
|
||||
sink(source) // $ SPURIOUS: hasTaintFlow
|
||||
sink(normalized) // Safe
|
||||
} else {
|
||||
sink(source) // $ hasTaintFlow
|
||||
sink(normalized) // $ MISSING: hasTaintFlow
|
||||
}
|
||||
}
|
||||
run {
|
||||
val source = source() as String?
|
||||
// normalize().toString() gets extracted as Object.toString, stopping taint here
|
||||
val normalized: String = Paths.get(source).normalize().toString()
|
||||
if (!normalized.matches("/".toRegex())) {
|
||||
sink(source) // $ SPURIOUS: hasTaintFlow
|
||||
sink(normalized) // Safe
|
||||
} else {
|
||||
sink(source) // $ hasTaintFlow
|
||||
sink(normalized) // $ MISSING: hasTaintFlow
|
||||
}
|
||||
}
|
||||
// PathInjectionSanitizer + partial string match with disallowed prefixes
|
||||
run {
|
||||
val source = source() as String?
|
||||
// normalize().toString() gets extracted as Object.toString, stopping taint here
|
||||
val normalized: String = Paths.get(source).normalize().toString()
|
||||
if (!normalized.contains("/data")) {
|
||||
sink(source) // $ SPURIOUS: hasTaintFlow
|
||||
sink(normalized) // Safe
|
||||
} else {
|
||||
sink(source) // $ hasTaintFlow
|
||||
sink(normalized) // $ MISSING: hasTaintFlow
|
||||
}
|
||||
}
|
||||
run {
|
||||
val source = source() as String?
|
||||
// normalize().toString() gets extracted as Object.toString, stopping taint here
|
||||
val normalized: String = Paths.get(source).normalize().toString()
|
||||
if (!normalized.regionMatches(1, "/data", 0, 5)) {
|
||||
sink(source) // $ SPURIOUS: hasTaintFlow
|
||||
sink(normalized) // Safe
|
||||
} else {
|
||||
sink(source) // $ hasTaintFlow
|
||||
sink(normalized) // $ MISSING: hasTaintFlow
|
||||
}
|
||||
}
|
||||
run {
|
||||
val source = source() as String?
|
||||
// normalize().toString() gets extracted as Object.toString, stopping taint here
|
||||
val normalized: String = Paths.get(source).normalize().toString()
|
||||
if (!normalized.matches(".*/data/.*".toRegex())) {
|
||||
sink(source) // $ SPURIOUS: hasTaintFlow
|
||||
sink(normalized) // Safe
|
||||
} else {
|
||||
sink(source) // $ hasTaintFlow
|
||||
sink(normalized) // $ MISSING: hasTaintFlow
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
//semmle-extractor-options: --javac-args -cp ${testdir}/../../stubs/google-android-9.0.0
|
||||
//codeql-extractor-kotlin-options: ${testdir}/../../stubs/google-android-9.0.0
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.example.test;
|
||||
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebSettings;
|
||||
|
||||
public class SetJavascriptEnabled {
|
||||
public static void configureWebViewUnsafe(WebView view) {
|
||||
WebSettings settings = view.getSettings();
|
||||
settings.setJavaScriptEnabled(true); // $javascriptEnabled
|
||||
}
|
||||
|
||||
public static void configureWebViewSafe(WebView view) {
|
||||
WebSettings settings = view.getSettings();
|
||||
|
||||
// Safe: Javascript disabled
|
||||
settings.setJavaScriptEnabled(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
| SetJavascriptEnabled.java:9:9:9:43 | setJavaScriptEnabled(...) | JavaScript execution enabled in WebView. |
|
||||
@@ -0,0 +1 @@
|
||||
Security/CWE/CWE-079/AndroidWebViewSettingsEnabledJavaScript.ql
|
||||
@@ -1 +1 @@
|
||||
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../stubs/servlet-api-2.4:${testdir}/../../../../../stubs/javax-ws-rs-api-2.1.1/:${testdir}/../../../../../stubs/springframework-5.3.8:${testdir}/../../../../../stubs/javax-faces-2.3/
|
||||
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../stubs/servlet-api-2.4:${testdir}/../../../../../stubs/javax-ws-rs-api-2.1.1/:${testdir}/../../../../../stubs/springframework-5.3.8:${testdir}/../../../../../stubs/javax-faces-2.3/:${testdir}/../../../../../stubs/google-android-9.0.0
|
||||
|
||||
@@ -84,5 +84,15 @@ class Test {
|
||||
response.addCookie(cookie);
|
||||
}
|
||||
|
||||
{
|
||||
// GOOD: set secure flag in call to `createSecureCookie`
|
||||
response.addCookie(createSecureCookie());
|
||||
}
|
||||
}
|
||||
|
||||
private static Cookie createSecureCookie() {
|
||||
Cookie cookie = new Cookie("secret", "fakesecret");
|
||||
cookie.setSecure(constTrue);
|
||||
return cookie;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user