Merge branch 'main' into henrymercer/check-query-ids

This commit is contained in:
Henry Mercer
2022-12-08 13:05:46 +00:00
committed by GitHub
682 changed files with 14110 additions and 4191 deletions

View File

@@ -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];

View File

@@ -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)
}
}
}

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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? {

View File

@@ -0,0 +1,6 @@
@AllDefaultsAnnotation
public class User {
public static void test() { new AllDefaultsConstructor(); new AllDefaultsExplicitNoargConstructor(); }
}

View File

@@ -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) { }
}

View File

@@ -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")

View File

@@ -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> |

View File

@@ -1,3 +1,7 @@
## 0.4.5
No user-facing changes.
## 0.4.4
### New Features

View File

@@ -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.

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* The library `PathSanitizer.qll` has been improved to detect more path validation patterns in Kotlin.

View File

@@ -0,0 +1,3 @@
## 0.4.5
No user-facing changes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.4.4
lastReleaseVersion: 0.4.5

View File

@@ -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

View File

@@ -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.
*

View File

@@ -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") }

View File

@@ -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
}
/**

View File

@@ -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)

View File

@@ -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))

View File

@@ -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() {

View File

@@ -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."
}
}

View File

@@ -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. */

View File

@@ -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

View File

@@ -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() {

View File

@@ -3,7 +3,6 @@
*/
import java
private import semmle.code.java.dataflow.ExternalFlow
/** The class `flexjson.JSONDeserializer`. */
class FlexjsonDeserializer extends RefType {

View File

@@ -3,7 +3,6 @@
*/
import java
private import semmle.code.java.dataflow.ExternalFlow
/** The interface `org.hibernate.query.QueryProducer`. */
class HibernateQueryProducer extends RefType {

View File

@@ -4,7 +4,6 @@
*/
import java
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.security.XSS
/**

View File

@@ -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`. */

View File

@@ -3,7 +3,6 @@
*/
import java
private import semmle.code.java.dataflow.ExternalFlow
/** The class `jodd.json.Parser`. */
class JoddJsonParser extends RefType {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -3,7 +3,6 @@
*/
import java
private import semmle.code.java.dataflow.ExternalFlow
/** The class `org.springframework.jdbc.core.JdbcTemplate`. */
class JdbcTemplate extends RefType {

View File

@@ -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 {

View File

@@ -3,7 +3,6 @@
*/
import java
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.xml.AndroidManifest
/**

View File

@@ -3,7 +3,6 @@
*/
import java
private import semmle.code.java.dataflow.ExternalFlow
/** The class `android.content.ContentValues`. */
class ContentValues extends Class {

View File

@@ -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

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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`

View File

@@ -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`.

View File

@@ -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" }

View File

@@ -3,7 +3,6 @@
*/
import java
private import semmle.code.java.dataflow.ExternalFlow
/**
* Methods annotated with this allow for generation of "plain SQL"

View File

@@ -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.

View File

@@ -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.

View 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") }
}

View 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()
}
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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

View File

@@ -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
}

View File

@@ -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. */

View File

@@ -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

View File

@@ -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 }

View File

@@ -1,3 +1,7 @@
## 0.4.5
No user-facing changes.
## 0.4.4
### New Queries

View File

@@ -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>

View File

@@ -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."

View File

@@ -0,0 +1,2 @@
WebSettings settings = webview.getSettings();
settings.setJavaScriptEnabled(false);

View File

@@ -0,0 +1,2 @@
WebSettings settings = webview.getSettings();
settings.setJavaScriptEnabled(true);

View File

@@ -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."

View File

@@ -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.

View File

@@ -0,0 +1,3 @@
## 0.4.5
No user-facing changes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.4.4
lastReleaseVersion: 0.4.5

View File

@@ -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`. */

View File

@@ -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. */

View File

@@ -1,5 +1,5 @@
name: codeql/java-queries
version: 0.4.5-dev
version: 0.4.6-dev
groups:
- java
- queries

View File

@@ -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

View File

@@ -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()

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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" }

View File

@@ -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" }

View File

@@ -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" }

View File

@@ -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" }

View File

@@ -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" }

View File

@@ -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

View File

@@ -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 |

View File

@@ -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> |

View File

@@ -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);
}
*/
}
}

View File

@@ -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

View File

@@ -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 |

View File

@@ -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 |

View File

@@ -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

View File

@@ -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

View File

@@ -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>;

View File

@@ -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

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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();
}

View 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
}
}
}
}

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -0,0 +1 @@
| SetJavascriptEnabled.java:9:9:9:43 | setJavaScriptEnabled(...) | JavaScript execution enabled in WebView. |

View File

@@ -0,0 +1 @@
Security/CWE/CWE-079/AndroidWebViewSettingsEnabledJavaScript.ql

View File

@@ -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

View File

@@ -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;
}
}