Merge branch 'github:main' into crypto-test

This commit is contained in:
Nicolas Will
2025-04-30 16:35:26 +02:00
committed by GitHub
6768 changed files with 395014 additions and 163489 deletions

View File

@@ -0,0 +1,176 @@
ql/python/ql/src/AlertSuppression.ql
ql/python/ql/src/Classes/MaybeUndefinedClassAttribute.ql
ql/python/ql/src/Classes/ShouldBeContextManager.ql
ql/python/ql/src/Classes/UndefinedClassAttribute.ql
ql/python/ql/src/Classes/UselessClass.ql
ql/python/ql/src/Expressions/NonPortableComparisonUsingIs.ql
ql/python/ql/src/Filters/ClassifyFiles.ql
ql/python/ql/src/Functions/ReturnValueIgnored.ql
ql/python/ql/src/Imports/ImportShadowedByLoopVar.ql
ql/python/ql/src/Imports/ImportStarUsed.ql
ql/python/ql/src/Imports/Imports.ql
ql/python/ql/src/Lexical/FCommentedOutCode.ql
ql/python/ql/src/Lexical/ToDoComment.ql
ql/python/ql/src/Metrics/CLinesOfCode.ql
ql/python/ql/src/Metrics/ClassAfferentCoupling.ql
ql/python/ql/src/Metrics/ClassEfferentCoupling.ql
ql/python/ql/src/Metrics/CommentRatio.ql
ql/python/ql/src/Metrics/CyclomaticComplexity.ql
ql/python/ql/src/Metrics/Dependencies/ExternalDependencies.ql
ql/python/ql/src/Metrics/Dependencies/ExternalDependenciesSourceLinks.ql
ql/python/ql/src/Metrics/DirectImports.ql
ql/python/ql/src/Metrics/DocStringRatio.ql
ql/python/ql/src/Metrics/External/CommitDisplayStrings.ql
ql/python/ql/src/Metrics/External/CommitSourceLinks.ql
ql/python/ql/src/Metrics/FClasses.ql
ql/python/ql/src/Metrics/FFunctionsAndMethods.ql
ql/python/ql/src/Metrics/FLines.ql
ql/python/ql/src/Metrics/FLinesOfCode.ql
ql/python/ql/src/Metrics/FLinesOfComments.ql
ql/python/ql/src/Metrics/FLinesOfDuplicatedCode.ql
ql/python/ql/src/Metrics/FLinesOfSimilarCode.ql
ql/python/ql/src/Metrics/FNumberOfTests.ql
ql/python/ql/src/Metrics/FunctionNumberOfCalls.ql
ql/python/ql/src/Metrics/FunctionStatementNestingDepth.ql
ql/python/ql/src/Metrics/History/HChurn.ql
ql/python/ql/src/Metrics/History/HLinesAdded.ql
ql/python/ql/src/Metrics/History/HLinesDeleted.ql
ql/python/ql/src/Metrics/History/HNumberOfAuthors.ql
ql/python/ql/src/Metrics/History/HNumberOfCoCommits.ql
ql/python/ql/src/Metrics/History/HNumberOfCommits.ql
ql/python/ql/src/Metrics/History/HNumberOfReCommits.ql
ql/python/ql/src/Metrics/History/HNumberOfRecentAuthors.ql
ql/python/ql/src/Metrics/History/HNumberOfRecentChangedFiles.ql
ql/python/ql/src/Metrics/History/HNumberOfRecentCommits.ql
ql/python/ql/src/Metrics/Internal/CallableDisplayStrings.ql
ql/python/ql/src/Metrics/Internal/CallableExtents.ql
ql/python/ql/src/Metrics/Internal/CallableSourceLinks.ql
ql/python/ql/src/Metrics/Internal/ClassDisplayStrings.ql
ql/python/ql/src/Metrics/Internal/ClassExtents.ql
ql/python/ql/src/Metrics/Internal/ClassSourceLinks.ql
ql/python/ql/src/Metrics/Internal/TypeAnnotations.ql
ql/python/ql/src/Metrics/LackofCohesionInMethodsCK.ql
ql/python/ql/src/Metrics/LackofCohesionInMethodsHM.ql
ql/python/ql/src/Metrics/ModuleAfferentCoupling.ql
ql/python/ql/src/Metrics/ModuleEfferentCoupling.ql
ql/python/ql/src/Metrics/NumberOfParametersWithoutDefault.ql
ql/python/ql/src/Metrics/NumberOfStatements.ql
ql/python/ql/src/Metrics/TransitiveImports.ql
ql/python/ql/src/Security/CWE-020-ExternalAPIs/ExternalAPIsUsedWithUntrustedData.ql
ql/python/ql/src/Security/CWE-020-ExternalAPIs/UntrustedDataToExternalAPI.ql
ql/python/ql/src/Statements/AssertLiteralConstant.ql
ql/python/ql/src/Statements/C_StyleParentheses.ql
ql/python/ql/src/Statements/DocStrings.ql
ql/python/ql/src/Statements/ExecUsed.ql
ql/python/ql/src/Statements/StringConcatenationInLoop.ql
ql/python/ql/src/Variables/Global.ql
ql/python/ql/src/Variables/ShadowBuiltin.ql
ql/python/ql/src/Variables/ShadowGlobal.ql
ql/python/ql/src/Variables/UndefinedGlobal.ql
ql/python/ql/src/Variables/UnusedParameter.ql
ql/python/ql/src/analysis/CallGraphEfficiency.ql
ql/python/ql/src/analysis/CallGraphMarginalEfficiency.ql
ql/python/ql/src/analysis/Consistency.ql
ql/python/ql/src/analysis/ContextEfficiency.ql
ql/python/ql/src/analysis/ContextMarginalEfficiency.ql
ql/python/ql/src/analysis/Definitions.ql
ql/python/ql/src/analysis/Efficiency.ql
ql/python/ql/src/analysis/FailedInference.ql
ql/python/ql/src/analysis/ImportFailure.ql
ql/python/ql/src/analysis/KeyPointsToFailure.ql
ql/python/ql/src/analysis/PointsToFailure.ql
ql/python/ql/src/analysis/Pruned.ql
ql/python/ql/src/analysis/RatioOfDefinitions.ql
ql/python/ql/src/analysis/Summary.ql
ql/python/ql/src/analysis/TypeHierarchyFailure.ql
ql/python/ql/src/analysis/TypeInferenceFailure.ql
ql/python/ql/src/experimental/Classes/NamingConventionsClasses.ql
ql/python/ql/src/experimental/Functions/NamingConventionsFunctions.ql
ql/python/ql/src/experimental/Security/CWE-022/ZipSlip.ql
ql/python/ql/src/experimental/Security/CWE-022bis/TarSlipImprov.ql
ql/python/ql/src/experimental/Security/CWE-022bis/UnsafeUnpack.ql
ql/python/ql/src/experimental/Security/CWE-074/remoteCommandExecution/RemoteCommandExecution.ql
ql/python/ql/src/experimental/Security/CWE-079/EmailXss.ql
ql/python/ql/src/experimental/Security/CWE-091/XsltInjection.ql
ql/python/ql/src/experimental/Security/CWE-094/Js2Py.ql
ql/python/ql/src/experimental/Security/CWE-1236/CsvInjection.ql
ql/python/ql/src/experimental/Security/CWE-176/UnicodeBypassValidation.ql
ql/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHash/PossibleTimingAttackAgainstHash.ql
ql/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHash/TimingAttackAgainstHash.ql
ql/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstHeaderValue/TimingAttackAgainstHeaderValue.ql
ql/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstSensitiveInfo/PossibleTimingAttackAgainstSensitiveInfo.ql
ql/python/ql/src/experimental/Security/CWE-208/TimingAttackAgainstSensitiveInfo/TimingAttackAgainstSensitiveInfo.ql
ql/python/ql/src/experimental/Security/CWE-287-ConstantSecretKey/WebAppConstantSecretKey.ql
ql/python/ql/src/experimental/Security/CWE-287/ImproperLdapAuth.ql
ql/python/ql/src/experimental/Security/CWE-327/Azure/UnsafeUsageOfClientSideEncryptionVersion.ql
ql/python/ql/src/experimental/Security/CWE-338/InsecureRandomness.ql
ql/python/ql/src/experimental/Security/CWE-340/TokenBuiltFromUUID.ql
ql/python/ql/src/experimental/Security/CWE-346/CorsBypass.ql
ql/python/ql/src/experimental/Security/CWE-347/JWTEmptyKeyOrAlgorithm.ql
ql/python/ql/src/experimental/Security/CWE-347/JWTMissingSecretOrPublicKeyVerification.ql
ql/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.ql
ql/python/ql/src/experimental/Security/CWE-409/DecompressionBombs.ql
ql/python/ql/src/experimental/Security/CWE-522/LdapInsecureAuth.ql
ql/python/ql/src/experimental/Security/CWE-611/SimpleXmlRpcServer.ql
ql/python/ql/src/experimental/Security/CWE-770/UnicodeDoS.ql
ql/python/ql/src/experimental/Security/CWE-942/CorsMisconfigurationMiddleware.ql
ql/python/ql/src/experimental/cryptography/example_alerts/UnknownAsymmetricKeyGen.ql
ql/python/ql/src/experimental/cryptography/example_alerts/WeakAsymmetricKeyGen.ql
ql/python/ql/src/experimental/cryptography/example_alerts/WeakAsymmetricPadding.ql
ql/python/ql/src/experimental/cryptography/example_alerts/WeakBlockMode.ql
ql/python/ql/src/experimental/cryptography/example_alerts/WeakBlockModeIVorNonce.ql
ql/python/ql/src/experimental/cryptography/example_alerts/WeakEllipticCurve.ql
ql/python/ql/src/experimental/cryptography/example_alerts/WeakHashes.ql
ql/python/ql/src/experimental/cryptography/example_alerts/WeakKDFAlgorithm.ql
ql/python/ql/src/experimental/cryptography/example_alerts/WeakKDFIteration.ql
ql/python/ql/src/experimental/cryptography/example_alerts/WeakKDFKeyLength.ql
ql/python/ql/src/experimental/cryptography/example_alerts/WeakKDFMode.ql
ql/python/ql/src/experimental/cryptography/example_alerts/WeakKDFSaltGen.ql
ql/python/ql/src/experimental/cryptography/example_alerts/WeakKDFSaltSize.ql
ql/python/ql/src/experimental/cryptography/example_alerts/WeakSymmetricEncryption.ql
ql/python/ql/src/experimental/cryptography/inventory/new_models/AllAsymmetricAlgorithms.ql
ql/python/ql/src/experimental/cryptography/inventory/new_models/AllCryptoAlgorithms.ql
ql/python/ql/src/experimental/cryptography/inventory/new_models/AsymmetricEncryptionAlgorithms.ql
ql/python/ql/src/experimental/cryptography/inventory/new_models/AsymmetricKeyGenOperation.ql
ql/python/ql/src/experimental/cryptography/inventory/new_models/AsymmetricPaddingAlgorithms.ql
ql/python/ql/src/experimental/cryptography/inventory/new_models/AuthenticatedEncryptionAlgorithms.ql
ql/python/ql/src/experimental/cryptography/inventory/new_models/BlockModeAlgorithms.ql
ql/python/ql/src/experimental/cryptography/inventory/new_models/BlockModeKnownIVsOrNonces.ql
ql/python/ql/src/experimental/cryptography/inventory/new_models/BlockModeUnknownIVsOrNonces.ql
ql/python/ql/src/experimental/cryptography/inventory/new_models/EllipticCurveAlgorithms.ql
ql/python/ql/src/experimental/cryptography/inventory/new_models/HashingAlgorithms.ql
ql/python/ql/src/experimental/cryptography/inventory/new_models/KeyDerivationAlgorithms.ql
ql/python/ql/src/experimental/cryptography/inventory/new_models/KeyExchangeAlgorithms.ql
ql/python/ql/src/experimental/cryptography/inventory/new_models/SigningAlgorithms.ql
ql/python/ql/src/experimental/cryptography/inventory/new_models/SymmetricEncryptionAlgorithms.ql
ql/python/ql/src/experimental/cryptography/inventory/new_models/SymmetricPaddingAlgorithms.ql
ql/python/ql/src/experimental/cryptography/inventory/old_models/AllCryptoAlgorithms.ql
ql/python/ql/src/experimental/cryptography/inventory/old_models/BlockModeAlgorithms.ql
ql/python/ql/src/experimental/cryptography/inventory/old_models/HashingAlgorithms.ql
ql/python/ql/src/external/DuplicateBlock.ql
ql/python/ql/src/external/DuplicateFunction.ql
ql/python/ql/src/external/MostlyDuplicateClass.ql
ql/python/ql/src/external/MostlyDuplicateFile.ql
ql/python/ql/src/external/MostlySimilarFile.ql
ql/python/ql/src/external/SimilarFunction.ql
ql/python/ql/src/meta/ClassHierarchy/Find.ql
ql/python/ql/src/meta/alerts/InterestingTaintSinks.ql
ql/python/ql/src/meta/alerts/RemoteFlowSources.ql
ql/python/ql/src/meta/alerts/RemoteFlowSourcesReach.ql
ql/python/ql/src/meta/alerts/RequestHandlers.ql
ql/python/ql/src/meta/alerts/TaintSinks.ql
ql/python/ql/src/meta/analysis-quality/CallGraph.ql
ql/python/ql/src/meta/analysis-quality/PointsToResolvableCallRatio.ql
ql/python/ql/src/meta/analysis-quality/PointsToResolvableCalls.ql
ql/python/ql/src/meta/analysis-quality/PointsToResolvableCallsRelevantTarget.ql
ql/python/ql/src/meta/analysis-quality/ResolvableCallCandidates.ql
ql/python/ql/src/meta/analysis-quality/SummarizedCallableCallSites.ql
ql/python/ql/src/meta/analysis-quality/TTCallGraph.ql
ql/python/ql/src/meta/analysis-quality/TTCallGraphMissing.ql
ql/python/ql/src/meta/analysis-quality/TTCallGraphNew.ql
ql/python/ql/src/meta/analysis-quality/TTCallGraphNewAmbiguous.ql
ql/python/ql/src/meta/analysis-quality/TTCallGraphOverview.ql
ql/python/ql/src/meta/analysis-quality/TTCallGraphShared.ql
ql/python/ql/src/meta/debug/DebugStats.ql
ql/python/ql/src/meta/debug/SimpleClassDebug.ql
ql/python/ql/src/utils/modeleditor/FrameworkModeEndpoints.ql

View File

@@ -0,0 +1,4 @@
ql/python/ql/src/Functions/NonCls.ql
ql/python/ql/src/Functions/NonSelf.ql
ql/python/ql/src/Functions/SignatureSpecialMethods.ql
ql/python/ql/src/Resources/FileNotAlwaysClosed.ql

View File

@@ -0,0 +1,43 @@
ql/python/ql/src/Diagnostics/ExtractedFiles.ql
ql/python/ql/src/Diagnostics/ExtractionWarnings.ql
ql/python/ql/src/Expressions/UseofInput.ql
ql/python/ql/src/Security/CVE-2018-1281/BindToAllInterfaces.ql
ql/python/ql/src/Security/CWE-020/CookieInjection.ql
ql/python/ql/src/Security/CWE-020/IncompleteHostnameRegExp.ql
ql/python/ql/src/Security/CWE-020/IncompleteUrlSubstringSanitization.ql
ql/python/ql/src/Security/CWE-020/OverlyLargeRange.ql
ql/python/ql/src/Security/CWE-022/PathInjection.ql
ql/python/ql/src/Security/CWE-074/TemplateInjection.ql
ql/python/ql/src/Security/CWE-078/CommandInjection.ql
ql/python/ql/src/Security/CWE-079/ReflectedXss.ql
ql/python/ql/src/Security/CWE-089/SqlInjection.ql
ql/python/ql/src/Security/CWE-090/LdapInjection.ql
ql/python/ql/src/Security/CWE-094/CodeInjection.ql
ql/python/ql/src/Security/CWE-113/HeaderInjection.ql
ql/python/ql/src/Security/CWE-116/BadTagFilter.ql
ql/python/ql/src/Security/CWE-209/StackTraceExposure.ql
ql/python/ql/src/Security/CWE-215/FlaskDebug.ql
ql/python/ql/src/Security/CWE-285/PamAuthorization.ql
ql/python/ql/src/Security/CWE-295/MissingHostKeyValidation.ql
ql/python/ql/src/Security/CWE-312/CleartextLogging.ql
ql/python/ql/src/Security/CWE-312/CleartextStorage.ql
ql/python/ql/src/Security/CWE-326/WeakCryptoKey.ql
ql/python/ql/src/Security/CWE-327/BrokenCryptoAlgorithm.ql
ql/python/ql/src/Security/CWE-327/InsecureDefaultProtocol.ql
ql/python/ql/src/Security/CWE-327/InsecureProtocol.ql
ql/python/ql/src/Security/CWE-327/WeakSensitiveDataHashing.ql
ql/python/ql/src/Security/CWE-352/CSRFProtectionDisabled.ql
ql/python/ql/src/Security/CWE-377/InsecureTemporaryFile.ql
ql/python/ql/src/Security/CWE-502/UnsafeDeserialization.ql
ql/python/ql/src/Security/CWE-601/UrlRedirect.ql
ql/python/ql/src/Security/CWE-611/Xxe.ql
ql/python/ql/src/Security/CWE-614/InsecureCookie.ql
ql/python/ql/src/Security/CWE-643/XpathInjection.ql
ql/python/ql/src/Security/CWE-730/PolynomialReDoS.ql
ql/python/ql/src/Security/CWE-730/ReDoS.ql
ql/python/ql/src/Security/CWE-730/RegexInjection.ql
ql/python/ql/src/Security/CWE-776/XmlBomb.ql
ql/python/ql/src/Security/CWE-918/FullServerSideRequestForgery.ql
ql/python/ql/src/Security/CWE-943/NoSqlInjection.ql
ql/python/ql/src/Summary/LinesOfCode.ql
ql/python/ql/src/Summary/LinesOfUserCode.ql

View File

@@ -0,0 +1,173 @@
ql/python/ql/src/Classes/ConflictingAttributesInBaseClasses.ql
ql/python/ql/src/Classes/DefineEqualsWhenAddingAttributes.ql
ql/python/ql/src/Classes/EqualsOrHash.ql
ql/python/ql/src/Classes/EqualsOrNotEquals.ql
ql/python/ql/src/Classes/IncompleteOrdering.ql
ql/python/ql/src/Classes/InconsistentMRO.ql
ql/python/ql/src/Classes/InitCallsSubclassMethod.ql
ql/python/ql/src/Classes/MissingCallToDel.ql
ql/python/ql/src/Classes/MissingCallToInit.ql
ql/python/ql/src/Classes/MutatingDescriptor.ql
ql/python/ql/src/Classes/OverwritingAttributeInSuperClass.ql
ql/python/ql/src/Classes/PropertyInOldStyleClass.ql
ql/python/ql/src/Classes/SlotsInOldStyleClass.ql
ql/python/ql/src/Classes/SubclassShadowing.ql
ql/python/ql/src/Classes/SuperInOldStyleClass.ql
ql/python/ql/src/Classes/SuperclassDelCalledMultipleTimes.ql
ql/python/ql/src/Classes/SuperclassInitCalledMultipleTimes.ql
ql/python/ql/src/Classes/WrongNameForArgumentInClassInstantiation.ql
ql/python/ql/src/Classes/WrongNumberArgumentsInClassInstantiation.ql
ql/python/ql/src/Diagnostics/ExtractedFiles.ql
ql/python/ql/src/Diagnostics/ExtractionWarnings.ql
ql/python/ql/src/Exceptions/CatchingBaseException.ql
ql/python/ql/src/Exceptions/EmptyExcept.ql
ql/python/ql/src/Exceptions/IllegalExceptionHandlerType.ql
ql/python/ql/src/Exceptions/IllegalRaise.ql
ql/python/ql/src/Exceptions/IncorrectExceptOrder.ql
ql/python/ql/src/Exceptions/NotImplementedIsNotAnException.ql
ql/python/ql/src/Exceptions/RaisingTuple.ql
ql/python/ql/src/Exceptions/UnguardedNextInGenerator.ql
ql/python/ql/src/Expressions/CallToSuperWrongClass.ql
ql/python/ql/src/Expressions/CompareConstants.ql
ql/python/ql/src/Expressions/CompareIdenticalValues.ql
ql/python/ql/src/Expressions/CompareIdenticalValuesMissingSelf.ql
ql/python/ql/src/Expressions/Comparisons/UselessComparisonTest.ql
ql/python/ql/src/Expressions/ContainsNonContainer.ql
ql/python/ql/src/Expressions/DuplicateKeyInDictionaryLiteral.ql
ql/python/ql/src/Expressions/EqualsNone.ql
ql/python/ql/src/Expressions/ExpectedMappingForFormatString.ql
ql/python/ql/src/Expressions/ExplicitCallToDel.ql
ql/python/ql/src/Expressions/Formatting/MixedExplicitImplicitIn3101Format.ql
ql/python/ql/src/Expressions/Formatting/UnusedArgumentIn3101Format.ql
ql/python/ql/src/Expressions/Formatting/UnusedNamedArgumentIn3101Format.ql
ql/python/ql/src/Expressions/Formatting/WrongNameInArgumentsFor3101Format.ql
ql/python/ql/src/Expressions/Formatting/WrongNumberArgumentsFor3101Format.ql
ql/python/ql/src/Expressions/HashedButNoHash.ql
ql/python/ql/src/Expressions/IncorrectComparisonUsingIs.ql
ql/python/ql/src/Expressions/NonCallableCalled.ql
ql/python/ql/src/Expressions/Regex/BackspaceEscape.ql
ql/python/ql/src/Expressions/Regex/DuplicateCharacterInSet.ql
ql/python/ql/src/Expressions/Regex/MissingPartSpecialGroup.ql
ql/python/ql/src/Expressions/Regex/UnmatchableCaret.ql
ql/python/ql/src/Expressions/Regex/UnmatchableDollar.ql
ql/python/ql/src/Expressions/TruncatedDivision.ql
ql/python/ql/src/Expressions/UnintentionalImplicitStringConcatenation.ql
ql/python/ql/src/Expressions/UnnecessaryLambda.ql
ql/python/ql/src/Expressions/UnsupportedFormatCharacter.ql
ql/python/ql/src/Expressions/UseofApply.ql
ql/python/ql/src/Expressions/UseofInput.ql
ql/python/ql/src/Expressions/WrongNameForArgumentInCall.ql
ql/python/ql/src/Expressions/WrongNumberArgumentsForFormat.ql
ql/python/ql/src/Expressions/WrongNumberArgumentsInCall.ql
ql/python/ql/src/Functions/ConsistentReturns.ql
ql/python/ql/src/Functions/DeprecatedSliceMethod.ql
ql/python/ql/src/Functions/ExplicitReturnInInit.ql
ql/python/ql/src/Functions/IncorrectRaiseInSpecialMethod.ql
ql/python/ql/src/Functions/IncorrectlyOverriddenMethod.ql
ql/python/ql/src/Functions/IncorrectlySpecifiedOverriddenMethod.ql
ql/python/ql/src/Functions/InitIsGenerator.ql
ql/python/ql/src/Functions/IterReturnsNonIterator.ql
ql/python/ql/src/Functions/IterReturnsNonSelf.ql
ql/python/ql/src/Functions/ModificationOfParameterWithDefault.ql
ql/python/ql/src/Functions/NonCls.ql
ql/python/ql/src/Functions/NonSelf.ql
ql/python/ql/src/Functions/OverlyComplexDelMethod.ql
ql/python/ql/src/Functions/ReturnConsistentTupleSizes.ql
ql/python/ql/src/Functions/SignatureOverriddenMethod.ql
ql/python/ql/src/Functions/SignatureSpecialMethods.ql
ql/python/ql/src/Functions/UseImplicitNoneReturnValue.ql
ql/python/ql/src/Imports/CyclicImport.ql
ql/python/ql/src/Imports/DeprecatedModule.ql
ql/python/ql/src/Imports/EncodingError.ql
ql/python/ql/src/Imports/FromImportOfMutableAttribute.ql
ql/python/ql/src/Imports/ImportandImportFrom.ql
ql/python/ql/src/Imports/ModuleImportsItself.ql
ql/python/ql/src/Imports/ModuleLevelCyclicImport.ql
ql/python/ql/src/Imports/MultipleImports.ql
ql/python/ql/src/Imports/SyntaxError.ql
ql/python/ql/src/Imports/UnintentionalImport.ql
ql/python/ql/src/Imports/UnusedImport.ql
ql/python/ql/src/Lexical/CommentedOutCode.ql
ql/python/ql/src/Lexical/OldOctalLiteral.ql
ql/python/ql/src/Numerics/Pythagorean.ql
ql/python/ql/src/Resources/FileNotAlwaysClosed.ql
ql/python/ql/src/Security/CVE-2018-1281/BindToAllInterfaces.ql
ql/python/ql/src/Security/CWE-020/CookieInjection.ql
ql/python/ql/src/Security/CWE-020/IncompleteHostnameRegExp.ql
ql/python/ql/src/Security/CWE-020/IncompleteUrlSubstringSanitization.ql
ql/python/ql/src/Security/CWE-020/OverlyLargeRange.ql
ql/python/ql/src/Security/CWE-022/PathInjection.ql
ql/python/ql/src/Security/CWE-022/TarSlip.ql
ql/python/ql/src/Security/CWE-074/TemplateInjection.ql
ql/python/ql/src/Security/CWE-078/CommandInjection.ql
ql/python/ql/src/Security/CWE-078/UnsafeShellCommandConstruction.ql
ql/python/ql/src/Security/CWE-079/Jinja2WithoutEscaping.ql
ql/python/ql/src/Security/CWE-079/ReflectedXss.ql
ql/python/ql/src/Security/CWE-089/SqlInjection.ql
ql/python/ql/src/Security/CWE-090/LdapInjection.ql
ql/python/ql/src/Security/CWE-094/CodeInjection.ql
ql/python/ql/src/Security/CWE-113/HeaderInjection.ql
ql/python/ql/src/Security/CWE-116/BadTagFilter.ql
ql/python/ql/src/Security/CWE-117/LogInjection.ql
ql/python/ql/src/Security/CWE-209/StackTraceExposure.ql
ql/python/ql/src/Security/CWE-215/FlaskDebug.ql
ql/python/ql/src/Security/CWE-285/PamAuthorization.ql
ql/python/ql/src/Security/CWE-295/MissingHostKeyValidation.ql
ql/python/ql/src/Security/CWE-295/RequestWithoutValidation.ql
ql/python/ql/src/Security/CWE-312/CleartextLogging.ql
ql/python/ql/src/Security/CWE-312/CleartextStorage.ql
ql/python/ql/src/Security/CWE-326/WeakCryptoKey.ql
ql/python/ql/src/Security/CWE-327/BrokenCryptoAlgorithm.ql
ql/python/ql/src/Security/CWE-327/InsecureDefaultProtocol.ql
ql/python/ql/src/Security/CWE-327/InsecureProtocol.ql
ql/python/ql/src/Security/CWE-327/WeakSensitiveDataHashing.ql
ql/python/ql/src/Security/CWE-352/CSRFProtectionDisabled.ql
ql/python/ql/src/Security/CWE-377/InsecureTemporaryFile.ql
ql/python/ql/src/Security/CWE-502/UnsafeDeserialization.ql
ql/python/ql/src/Security/CWE-601/UrlRedirect.ql
ql/python/ql/src/Security/CWE-611/Xxe.ql
ql/python/ql/src/Security/CWE-614/InsecureCookie.ql
ql/python/ql/src/Security/CWE-643/XpathInjection.ql
ql/python/ql/src/Security/CWE-730/PolynomialReDoS.ql
ql/python/ql/src/Security/CWE-730/ReDoS.ql
ql/python/ql/src/Security/CWE-730/RegexInjection.ql
ql/python/ql/src/Security/CWE-732/WeakFilePermissions.ql
ql/python/ql/src/Security/CWE-776/XmlBomb.ql
ql/python/ql/src/Security/CWE-798/HardcodedCredentials.ql
ql/python/ql/src/Security/CWE-918/FullServerSideRequestForgery.ql
ql/python/ql/src/Security/CWE-918/PartialServerSideRequestForgery.ql
ql/python/ql/src/Security/CWE-943/NoSqlInjection.ql
ql/python/ql/src/Statements/AssertOnTuple.ql
ql/python/ql/src/Statements/BreakOrReturnInFinally.ql
ql/python/ql/src/Statements/ConstantInConditional.ql
ql/python/ql/src/Statements/IterableStringOrSequence.ql
ql/python/ql/src/Statements/MismatchInMultipleAssignment.ql
ql/python/ql/src/Statements/ModificationOfLocals.ql
ql/python/ql/src/Statements/NestedLoopsSameVariable.ql
ql/python/ql/src/Statements/NestedLoopsSameVariableWithReuse.ql
ql/python/ql/src/Statements/NonIteratorInForLoop.ql
ql/python/ql/src/Statements/RedundantAssignment.ql
ql/python/ql/src/Statements/ReturnOrYieldOutsideFunction.ql
ql/python/ql/src/Statements/ShouldUseWithStatement.ql
ql/python/ql/src/Statements/SideEffectInAssert.ql
ql/python/ql/src/Statements/StatementNoEffect.ql
ql/python/ql/src/Statements/TopLevelPrint.ql
ql/python/ql/src/Statements/UnnecessaryDelete.ql
ql/python/ql/src/Statements/UnnecessaryElseClause.ql
ql/python/ql/src/Statements/UnnecessaryPass.ql
ql/python/ql/src/Statements/UnreachableCode.ql
ql/python/ql/src/Statements/UnusedExceptionObject.ql
ql/python/ql/src/Statements/UseOfExit.ql
ql/python/ql/src/Summary/LinesOfCode.ql
ql/python/ql/src/Summary/LinesOfUserCode.ql
ql/python/ql/src/Testing/ImpreciseAssert.ql
ql/python/ql/src/Variables/GlobalAtModuleLevel.ql
ql/python/ql/src/Variables/LeakingListComprehension.ql
ql/python/ql/src/Variables/LoopVariableCapture/LoopVariableCapture.ql
ql/python/ql/src/Variables/MultiplyDefined.ql
ql/python/ql/src/Variables/SuspiciousUnusedLoopIterationVariable.ql
ql/python/ql/src/Variables/UndefinedExport.ql
ql/python/ql/src/Variables/UndefinedPlaceHolder.ql
ql/python/ql/src/Variables/UninitializedLocal.ql
ql/python/ql/src/Variables/UnusedLocalVariable.ql
ql/python/ql/src/Variables/UnusedModuleVariable.ql

View File

@@ -0,0 +1,51 @@
ql/python/ql/src/Diagnostics/ExtractedFiles.ql
ql/python/ql/src/Diagnostics/ExtractionWarnings.ql
ql/python/ql/src/Expressions/UseofInput.ql
ql/python/ql/src/Security/CVE-2018-1281/BindToAllInterfaces.ql
ql/python/ql/src/Security/CWE-020/CookieInjection.ql
ql/python/ql/src/Security/CWE-020/IncompleteHostnameRegExp.ql
ql/python/ql/src/Security/CWE-020/IncompleteUrlSubstringSanitization.ql
ql/python/ql/src/Security/CWE-020/OverlyLargeRange.ql
ql/python/ql/src/Security/CWE-022/PathInjection.ql
ql/python/ql/src/Security/CWE-022/TarSlip.ql
ql/python/ql/src/Security/CWE-074/TemplateInjection.ql
ql/python/ql/src/Security/CWE-078/CommandInjection.ql
ql/python/ql/src/Security/CWE-078/UnsafeShellCommandConstruction.ql
ql/python/ql/src/Security/CWE-079/Jinja2WithoutEscaping.ql
ql/python/ql/src/Security/CWE-079/ReflectedXss.ql
ql/python/ql/src/Security/CWE-089/SqlInjection.ql
ql/python/ql/src/Security/CWE-090/LdapInjection.ql
ql/python/ql/src/Security/CWE-094/CodeInjection.ql
ql/python/ql/src/Security/CWE-113/HeaderInjection.ql
ql/python/ql/src/Security/CWE-116/BadTagFilter.ql
ql/python/ql/src/Security/CWE-117/LogInjection.ql
ql/python/ql/src/Security/CWE-209/StackTraceExposure.ql
ql/python/ql/src/Security/CWE-215/FlaskDebug.ql
ql/python/ql/src/Security/CWE-285/PamAuthorization.ql
ql/python/ql/src/Security/CWE-295/MissingHostKeyValidation.ql
ql/python/ql/src/Security/CWE-295/RequestWithoutValidation.ql
ql/python/ql/src/Security/CWE-312/CleartextLogging.ql
ql/python/ql/src/Security/CWE-312/CleartextStorage.ql
ql/python/ql/src/Security/CWE-326/WeakCryptoKey.ql
ql/python/ql/src/Security/CWE-327/BrokenCryptoAlgorithm.ql
ql/python/ql/src/Security/CWE-327/InsecureDefaultProtocol.ql
ql/python/ql/src/Security/CWE-327/InsecureProtocol.ql
ql/python/ql/src/Security/CWE-327/WeakSensitiveDataHashing.ql
ql/python/ql/src/Security/CWE-352/CSRFProtectionDisabled.ql
ql/python/ql/src/Security/CWE-377/InsecureTemporaryFile.ql
ql/python/ql/src/Security/CWE-502/UnsafeDeserialization.ql
ql/python/ql/src/Security/CWE-601/UrlRedirect.ql
ql/python/ql/src/Security/CWE-611/Xxe.ql
ql/python/ql/src/Security/CWE-614/InsecureCookie.ql
ql/python/ql/src/Security/CWE-643/XpathInjection.ql
ql/python/ql/src/Security/CWE-730/PolynomialReDoS.ql
ql/python/ql/src/Security/CWE-730/ReDoS.ql
ql/python/ql/src/Security/CWE-730/RegexInjection.ql
ql/python/ql/src/Security/CWE-732/WeakFilePermissions.ql
ql/python/ql/src/Security/CWE-776/XmlBomb.ql
ql/python/ql/src/Security/CWE-798/HardcodedCredentials.ql
ql/python/ql/src/Security/CWE-918/FullServerSideRequestForgery.ql
ql/python/ql/src/Security/CWE-918/PartialServerSideRequestForgery.ql
ql/python/ql/src/Security/CWE-943/NoSqlInjection.ql
ql/python/ql/src/Summary/LinesOfCode.ql
ql/python/ql/src/Summary/LinesOfUserCode.ql

View File

@@ -0,0 +1,14 @@
import runs_on
import pytest
from query_suites import *
well_known_query_suites = ['python-code-quality.qls', 'python-security-and-quality.qls', 'python-security-extended.qls', 'python-code-scanning.qls']
@runs_on.posix
@pytest.mark.parametrize("query_suite", well_known_query_suites)
def test(codeql, python, check_query_suite, query_suite):
check_query_suite(query_suite)
@runs_on.posix
def test_not_included_queries(codeql, python, check_queries_not_included):
check_queries_not_included('python', well_known_query_suites)

View File

@@ -1,3 +1,55 @@
## 4.0.6
No user-facing changes.
## 4.0.5
No user-facing changes.
## 4.0.4
### Minor Analysis Improvements
- Added the methods `getMinArguments` and `getMaxArguments` to the `Function` class. These return the minimum and maximum positional arguments that the given function accepts.
### Bug Fixes
- `MatchLiteralPattern`s such as `case None: ...` are now never pruned from the extracted source code. This fixes some situations where code was wrongly identified as unreachable.
## 4.0.3
No user-facing changes.
## 4.0.2
No user-facing changes.
## 4.0.1
### Bug Fixes
- Fixed a bug in the extractor where a comment inside a subscript could sometimes cause the AST to be missing nodes.
- Using the `break` and `continue` keywords outside of a loop, which is a syntax error but is accepted by our parser, would cause the control-flow construction to fail. This is now no longer the case.
## 4.0.0
### Breaking Changes
* Deleted the old deprecated TypeTracking library.
* Deleted the deprecated `classRef` predicate from the `FieldStorage` module, use `subclassRef` instead.
* Deleted a lot of deprecated modules and predicates from `Stdlib.qll`, use API-graphs directly instead.
### Minor Analysis Improvements
* Additional data flow models for the builtin functions `map`, `filter`, `zip`, and `enumerate` have been added.
## 3.1.1
### Minor Analysis Improvements
* The sensitive data library has been improved so that `snake_case` style variable names are recognized more reliably. This may result in more sensitive data being identified, and more results from queries that use the sensitive data library.
- Additional taint steps through methods of `lxml.etree.Element` and `lxml.etree.ElementTree` objects from the `lxml` PyPI package have been modeled.
## 3.1.0
### New Features

View File

@@ -1,4 +0,0 @@
---
category: minorAnalysis
---
- Additional taint steps through methods of `lxml.etree.Element` and `lxml.etree.ElementTree` objects from the `lxml` PyPI package have been modeled.

View File

@@ -0,0 +1,6 @@
## 3.1.1
### Minor Analysis Improvements
* The sensitive data library has been improved so that `snake_case` style variable names are recognized more reliably. This may result in more sensitive data being identified, and more results from queries that use the sensitive data library.
- Additional taint steps through methods of `lxml.etree.Element` and `lxml.etree.ElementTree` objects from the `lxml` PyPI package have been modeled.

View File

@@ -0,0 +1,11 @@
## 4.0.0
### Breaking Changes
* Deleted the old deprecated TypeTracking library.
* Deleted the deprecated `classRef` predicate from the `FieldStorage` module, use `subclassRef` instead.
* Deleted a lot of deprecated modules and predicates from `Stdlib.qll`, use API-graphs directly instead.
### Minor Analysis Improvements
* Additional data flow models for the builtin functions `map`, `filter`, `zip`, and `enumerate` have been added.

View File

@@ -0,0 +1,6 @@
## 4.0.1
### Bug Fixes
- Fixed a bug in the extractor where a comment inside a subscript could sometimes cause the AST to be missing nodes.
- Using the `break` and `continue` keywords outside of a loop, which is a syntax error but is accepted by our parser, would cause the control-flow construction to fail. This is now no longer the case.

View File

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

View File

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

View File

@@ -0,0 +1,9 @@
## 4.0.4
### Minor Analysis Improvements
- Added the methods `getMinArguments` and `getMaxArguments` to the `Function` class. These return the minimum and maximum positional arguments that the given function accepts.
### Bug Fixes
- `MatchLiteralPattern`s such as `case None: ...` are now never pruned from the extracted source code. This fixes some situations where code was wrongly identified as unreachable.

View File

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

View File

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

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 3.1.0
lastReleaseVersion: 4.0.6

View File

@@ -1,5 +1,5 @@
name: codeql/python-all
version: 3.1.1-dev
version: 4.0.7-dev
groups: python
dbscheme: semmlecode.python.dbscheme
extractor: python

View File

@@ -181,7 +181,7 @@ module Path {
}
}
/** A data-flow node that checks that a path is safe to access. */
/** A data-flow node that checks that a path is safe to access in some way, for example by having a controlled prefix. */
class SafeAccessCheck extends DataFlow::ExprNode {
SafeAccessCheck() { this = DataFlow::BarrierGuard<safeAccessCheck/3>::getABarrierNode() }
}
@@ -192,7 +192,7 @@ module Path {
/** Provides a class for modeling new path safety checks. */
module SafeAccessCheck {
/** A data-flow node that checks that a path is safe to access. */
/** A data-flow node that checks that a path is safe to access in some way, for example by having a controlled prefix. */
abstract class Range extends DataFlow::GuardNode {
/** Holds if this guard validates `node` upon evaluating to `branch`. */
abstract predicate checks(ControlFlowNode node, boolean branch);

View File

@@ -746,6 +746,24 @@ class Guard extends Guard_ {
override Expr getASubExpression() { result = this.getTest() }
}
/** An annotation, such as the `int` part of `x: int` */
class Annotation extends Expr {
Annotation() {
this = any(AnnAssign a).getAnnotation()
or
exists(Arguments args |
this in [
args.getAnAnnotation(),
args.getAKwAnnotation(),
args.getKwargannotation(),
args.getVarargannotation()
]
)
or
this = any(FunctionExpr f).getReturns()
}
}
/* Expression Contexts */
/** A context in which an expression used */
class ExprContext extends ExprContext_ { }

View File

@@ -163,6 +163,24 @@ class Function extends Function_, Scope, AstNode {
ret.getValue() = result.getNode()
)
}
/** Gets the minimum number of positional arguments that can be correctly passed to this function. */
int getMinPositionalArguments() {
result = count(this.getAnArg()) - count(this.getDefinition().getArgs().getADefault())
}
/**
* Gets the maximum number of positional arguments that can be correctly passed to this function.
*
* If the function has a `*vararg` parameter, there is no upper limit on the number of positional
* arguments that can be passed to the function. In this case, this method returns a very large
* number (currently `INT_MAX`, 2147483647, but this may change in the future).
*/
int getMaxPositionalArguments() {
if exists(this.getVararg())
then result = 2147483647 // INT_MAX
else result = count(this.getAnArg())
}
}
/** A def statement. Note that FunctionDef extends Assign as a function definition binds the newly created function */

View File

@@ -1,70 +0,0 @@
/**
* DEPRECATED: Use `semmle.python.dataflow.new.TypeTracking` instead.
*
* This file acts as a wrapper for `internal.TypeTracker`, exposing some of the functionality with
* names that are more appropriate for Python.
*/
private import python
private import internal.TypeTracker as Internal
private import internal.TypeTrackerSpecific as InternalSpecific
/** A string that may appear as the name of an attribute or access path. */
deprecated class AttributeName = InternalSpecific::TypeTrackerContent;
/** An attribute name, or the empty string (representing no attribute). */
deprecated class OptionalAttributeName = InternalSpecific::OptionalTypeTrackerContent;
/**
* DEPRECATED: Use `semmle.python.dataflow.new.TypeTracking` instead.
*
* The summary of the steps needed to track a value to a given dataflow node.
*
* This can be used to track objects that implement a certain API in order to
* recognize calls to that API. Note that type-tracking does not by itself provide a
* source/sink relation, that is, it may determine that a node has a given type,
* but it won't determine where that type came from.
*
* It is recommended that all uses of this type are written in the following form,
* for tracking some type `myType`:
* ```ql
* DataFlow::TypeTrackingNode myType(DataFlow::TypeTracker t) {
* t.start() and
* result = < source of myType >
* or
* exists (DataFlow::TypeTracker t2 |
* result = myType(t2).track(t2, t)
* )
* }
*
* DataFlow::LocalSourceNode myType() { myType(DataFlow::TypeTracker::end()) }
* ```
*
* Instead of `result = myType(t2).track(t2, t)`, you can also use the equivalent
* `t = t2.step(myType(t2), result)`. If you additionally want to track individual
* intra-procedural steps, use `t = t2.smallstep(myCallback(t2), result)`.
*/
deprecated class TypeTracker extends Internal::TypeTracker {
/**
* Holds if this is the starting point of type tracking, and the value starts in the attribute named `attrName`.
* The type tracking only ends after the attribute has been loaded.
*/
predicate startInAttr(string attrName) { this.startInContent(attrName) }
/**
* INTERNAL. DO NOT USE.
*
* Gets the attribute associated with this type tracker.
*/
string getAttr() { result = this.getContent() }
}
deprecated module TypeTracker = Internal::TypeTracker;
deprecated class StepSummary = Internal::StepSummary;
deprecated module StepSummary = Internal::StepSummary;
deprecated class TypeBackTracker = Internal::TypeBackTracker;
deprecated module TypeBackTracker = Internal::TypeBackTracker;

View File

@@ -4,7 +4,8 @@
*/
private import internal.TypeTrackingImpl as Impl
import Impl::Shared::TypeTracking<Impl::TypeTrackingInput>
private import semmle.python.Files
import Impl::Shared::TypeTracking<Location, Impl::TypeTrackingInput>
private import semmle.python.dataflow.new.internal.DataFlowPublic as DataFlowPublic
/**

View File

@@ -1,957 +0,0 @@
/** Step Summaries and Type Tracking */
private import TypeTrackerSpecific
private import semmle.python.dataflow.new.internal.DataFlowPublic as DataFlowPublic
cached
private module Cached {
/**
* A description of a step on an inter-procedural data flow path.
*/
cached
deprecated newtype TStepSummary =
LevelStep() or
CallStep() or
ReturnStep() or
deprecated StoreStep(TypeTrackerContent content) {
exists(DataFlowPublic::AttributeContent dfc | dfc.getAttribute() = content |
basicStoreStep(_, _, dfc)
)
} or
deprecated LoadStep(TypeTrackerContent content) {
exists(DataFlowPublic::AttributeContent dfc | dfc.getAttribute() = content |
basicLoadStep(_, _, dfc)
)
} or
deprecated LoadStoreStep(TypeTrackerContent load, TypeTrackerContent store) {
exists(DataFlowPublic::AttributeContent dfcLoad, DataFlowPublic::AttributeContent dfcStore |
dfcLoad.getAttribute() = load and dfcStore.getAttribute() = store
|
basicLoadStoreStep(_, _, dfcLoad, dfcStore)
)
} or
deprecated WithContent(ContentFilter filter) { basicWithContentStep(_, _, filter) } or
deprecated WithoutContent(ContentFilter filter) { basicWithoutContentStep(_, _, filter) } or
JumpStep()
cached
deprecated newtype TTypeTracker =
deprecated MkTypeTracker(Boolean hasCall, OptionalTypeTrackerContent content) {
content = noContent()
or
// Restrict `content` to those that might eventually match a load.
// We can't rely on `basicStoreStep` since `startInContent` might be used with
// a content that has no corresponding store.
exists(DataFlowPublic::AttributeContent loadContents |
(
basicLoadStep(_, _, loadContents)
or
basicLoadStoreStep(_, _, loadContents, _)
) and
compatibleContents(content, loadContents.getAttribute())
)
}
cached
deprecated newtype TTypeBackTracker =
deprecated MkTypeBackTracker(Boolean hasReturn, OptionalTypeTrackerContent content) {
content = noContent()
or
// As in MkTypeTracker, restrict `content` to those that might eventually match a store.
exists(DataFlowPublic::AttributeContent storeContent |
(
basicStoreStep(_, _, storeContent)
or
basicLoadStoreStep(_, _, _, storeContent)
) and
compatibleContents(storeContent.getAttribute(), content)
)
}
/** Gets a type tracker with no content and the call bit set to the given value. */
cached
deprecated TypeTracker noContentTypeTracker(boolean hasCall) {
result = MkTypeTracker(hasCall, noContent())
}
/** Gets the summary resulting from appending `step` to type-tracking summary `tt`. */
cached
deprecated TypeTracker append(TypeTracker tt, StepSummary step) {
exists(Boolean hasCall, OptionalTypeTrackerContent currentContents |
tt = MkTypeTracker(hasCall, currentContents)
|
step = LevelStep() and result = tt
or
step = CallStep() and result = MkTypeTracker(true, currentContents)
or
step = ReturnStep() and hasCall = false and result = tt
or
step = JumpStep() and
result = MkTypeTracker(false, currentContents)
or
exists(ContentFilter filter | result = tt |
step = WithContent(filter) and
currentContents = filter.getAMatchingContent()
or
step = WithoutContent(filter) and
not currentContents = filter.getAMatchingContent()
)
)
or
exists(TypeTrackerContent storeContents, boolean hasCall |
exists(TypeTrackerContent loadContents |
step = LoadStep(pragma[only_bind_into](loadContents)) and
tt = MkTypeTracker(hasCall, storeContents) and
compatibleContents(storeContents, loadContents) and
result = noContentTypeTracker(hasCall)
)
or
step = StoreStep(pragma[only_bind_into](storeContents)) and
tt = noContentTypeTracker(hasCall) and
result = MkTypeTracker(hasCall, storeContents)
)
or
exists(
TypeTrackerContent currentContent, TypeTrackerContent store, TypeTrackerContent load,
boolean hasCall
|
step = LoadStoreStep(pragma[only_bind_into](load), pragma[only_bind_into](store)) and
compatibleContents(pragma[only_bind_into](currentContent), load) and
tt = MkTypeTracker(pragma[only_bind_into](hasCall), currentContent) and
result = MkTypeTracker(pragma[only_bind_out](hasCall), store)
)
}
pragma[nomagic]
deprecated private TypeBackTracker noContentTypeBackTracker(boolean hasReturn) {
result = MkTypeBackTracker(hasReturn, noContent())
}
/** Gets the summary resulting from prepending `step` to this type-tracking summary. */
cached
deprecated TypeBackTracker prepend(TypeBackTracker tbt, StepSummary step) {
exists(Boolean hasReturn, OptionalTypeTrackerContent content |
tbt = MkTypeBackTracker(hasReturn, content)
|
step = LevelStep() and result = tbt
or
step = CallStep() and hasReturn = false and result = tbt
or
step = ReturnStep() and result = MkTypeBackTracker(true, content)
or
step = JumpStep() and
result = MkTypeBackTracker(false, content)
or
exists(ContentFilter filter | result = tbt |
step = WithContent(filter) and
content = filter.getAMatchingContent()
or
step = WithoutContent(filter) and
not content = filter.getAMatchingContent()
)
)
or
exists(TypeTrackerContent loadContents, boolean hasReturn |
exists(TypeTrackerContent storeContents |
step = StoreStep(pragma[only_bind_into](storeContents)) and
tbt = MkTypeBackTracker(hasReturn, loadContents) and
compatibleContents(storeContents, loadContents) and
result = noContentTypeBackTracker(hasReturn)
)
or
step = LoadStep(pragma[only_bind_into](loadContents)) and
tbt = noContentTypeBackTracker(hasReturn) and
result = MkTypeBackTracker(hasReturn, loadContents)
)
or
exists(
TypeTrackerContent currentContent, TypeTrackerContent store, TypeTrackerContent load,
boolean hasCall
|
step = LoadStoreStep(pragma[only_bind_into](load), pragma[only_bind_into](store)) and
compatibleContents(store, pragma[only_bind_into](currentContent)) and
tbt = MkTypeBackTracker(pragma[only_bind_into](hasCall), currentContent) and
result = MkTypeBackTracker(pragma[only_bind_out](hasCall), load)
)
}
/**
* Gets the summary that corresponds to having taken a forwards
* heap and/or intra-procedural step from `nodeFrom` to `nodeTo`.
*
* Steps contained in this predicate should _not_ depend on the call graph.
*/
cached
deprecated predicate stepNoCall(
TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary
) {
exists(Node mid | nodeFrom.flowsTo(mid) and smallstepNoCall(mid, nodeTo, summary))
}
/**
* Gets the summary that corresponds to having taken a forwards
* inter-procedural step from `nodeFrom` to `nodeTo`.
*/
cached
deprecated predicate stepCall(
TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary
) {
exists(Node mid | nodeFrom.flowsTo(mid) and smallstepCall(mid, nodeTo, summary))
}
cached
deprecated predicate smallstepNoCall(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
jumpStep(nodeFrom, nodeTo) and
summary = JumpStep()
or
levelStepNoCall(nodeFrom, nodeTo) and
summary = LevelStep()
or
exists(TypeTrackerContent content |
flowsToStoreStep(nodeFrom, nodeTo, content) and
summary = StoreStep(content)
or
exists(DataFlowPublic::AttributeContent dfc | dfc.getAttribute() = content |
basicLoadStep(nodeFrom, nodeTo, dfc)
) and
summary = LoadStep(content)
)
or
exists(TypeTrackerContent loadContent, TypeTrackerContent storeContent |
flowsToLoadStoreStep(nodeFrom, nodeTo, loadContent, storeContent) and
summary = LoadStoreStep(loadContent, storeContent)
)
or
exists(ContentFilter filter |
basicWithContentStep(nodeFrom, nodeTo, filter) and
summary = WithContent(filter)
or
basicWithoutContentStep(nodeFrom, nodeTo, filter) and
summary = WithoutContent(filter)
)
}
cached
deprecated predicate smallstepCall(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
callStep(nodeFrom, nodeTo) and summary = CallStep()
or
returnStep(nodeFrom, nodeTo) and
summary = ReturnStep()
or
levelStepCall(nodeFrom, nodeTo) and
summary = LevelStep()
}
}
private import Cached
deprecated private predicate step(
TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary
) {
stepNoCall(nodeFrom, nodeTo, summary)
or
stepCall(nodeFrom, nodeTo, summary)
}
pragma[nomagic]
deprecated private predicate stepProj(TypeTrackingNode nodeFrom, StepSummary summary) {
step(nodeFrom, _, summary)
}
deprecated private predicate smallstep(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
smallstepNoCall(nodeFrom, nodeTo, summary)
or
smallstepCall(nodeFrom, nodeTo, summary)
}
pragma[nomagic]
deprecated private predicate smallstepProj(Node nodeFrom, StepSummary summary) {
smallstep(nodeFrom, _, summary)
}
/**
* Holds if `nodeFrom` is being written to the `content` of the object in `nodeTo`.
*
* Note that `nodeTo` will always be a local source node that flows to the place where the content
* is written in `basicStoreStep`. This may lead to the flow of information going "back in time"
* from the point of view of the execution of the program.
*
* For instance, if we interpret attribute writes in Python as writing to content with the same
* name as the attribute and consider the following snippet
*
* ```python
* def foo(y):
* x = Foo()
* bar(x)
* x.attr = y
* baz(x)
*
* def bar(x):
* z = x.attr
* ```
* for the attribute write `x.attr = y`, we will have `content` being the literal string `"attr"`,
* `nodeFrom` will be `y`, and `nodeTo` will be the object `Foo()` created on the first line of the
* function. This means we will track the fact that `x.attr` can have the type of `y` into the
* assignment to `z` inside `bar`, even though this attribute write happens _after_ `bar` is called.
*/
deprecated private predicate flowsToStoreStep(
Node nodeFrom, TypeTrackingNode nodeTo, TypeTrackerContent content
) {
exists(Node obj |
nodeTo.flowsTo(obj) and
exists(DataFlowPublic::AttributeContent dfc | dfc.getAttribute() = content |
basicStoreStep(nodeFrom, obj, dfc)
)
)
}
/**
* Holds if `loadContent` is loaded from `nodeFrom` and written to `storeContent` of `nodeTo`.
*/
deprecated private predicate flowsToLoadStoreStep(
Node nodeFrom, TypeTrackingNode nodeTo, TypeTrackerContent loadContent,
TypeTrackerContent storeContent
) {
exists(Node obj |
nodeTo.flowsTo(obj) and
exists(DataFlowPublic::AttributeContent loadDfc, DataFlowPublic::AttributeContent storeDfc |
loadDfc.getAttribute() = loadContent and storeDfc.getAttribute() = storeContent
|
basicLoadStoreStep(nodeFrom, obj, loadDfc, storeDfc)
)
)
}
/**
* INTERNAL: Use `TypeTracker` or `TypeBackTracker` instead.
*
* A description of a step on an inter-procedural data flow path.
*/
deprecated class StepSummary extends TStepSummary {
/** Gets a textual representation of this step summary. */
string toString() {
this instanceof LevelStep and result = "level"
or
this instanceof CallStep and result = "call"
or
this instanceof ReturnStep and result = "return"
or
exists(TypeTrackerContent content | this = StoreStep(content) | result = "store " + content)
or
exists(TypeTrackerContent content | this = LoadStep(content) | result = "load " + content)
or
exists(TypeTrackerContent load, TypeTrackerContent store |
this = LoadStoreStep(load, store) and
result = "load-store " + load + " -> " + store
)
or
this instanceof JumpStep and result = "jump"
}
}
/** Provides predicates for updating step summaries (`StepSummary`s). */
deprecated module StepSummary {
predicate append = Cached::append/2;
/**
* Gets the summary that corresponds to having taken a forwards
* inter-procedural step from `nodeFrom` to `nodeTo`.
*
* This predicate should normally not be used; consider using `step`
* instead.
*/
predicate stepCall = Cached::stepCall/3;
/**
* Gets the summary that corresponds to having taken a forwards
* intra-procedural step from `nodeFrom` to `nodeTo`.
*
* This predicate should normally not be used; consider using `step`
* instead.
*/
predicate stepNoCall = Cached::stepNoCall/3;
/**
* Gets the summary that corresponds to having taken a forwards
* heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
*/
predicate step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
stepNoCall(nodeFrom, nodeTo, summary)
or
stepCall(nodeFrom, nodeTo, summary)
}
/**
* Gets the summary that corresponds to having taken a forwards
* inter-procedural step from `nodeFrom` to `nodeTo`.
*
* This predicate should normally not be used; consider using `step`
* instead.
*/
predicate smallstepNoCall = Cached::smallstepNoCall/3;
/**
* Gets the summary that corresponds to having taken a forwards
* intra-procedural step from `nodeFrom` to `nodeTo`.
*
* This predicate should normally not be used; consider using `step`
* instead.
*/
predicate smallstepCall = Cached::smallstepCall/3;
/**
* Gets the summary that corresponds to having taken a forwards
* local, heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
*
* Unlike `StepSummary::step`, this predicate does not compress
* type-preserving steps.
*/
predicate smallstep(Node nodeFrom, TypeTrackingNode nodeTo, StepSummary summary) {
smallstepNoCall(nodeFrom, nodeTo, summary)
or
smallstepCall(nodeFrom, nodeTo, summary)
}
/** Gets the step summary for a level step. */
StepSummary levelStep() { result = LevelStep() }
/** Gets the step summary for a call step. */
StepSummary callStep() { result = CallStep() }
/** Gets the step summary for a return step. */
StepSummary returnStep() { result = ReturnStep() }
/** Gets the step summary for storing into `content`. */
StepSummary storeStep(TypeTrackerContent content) { result = StoreStep(content) }
/** Gets the step summary for loading from `content`. */
StepSummary loadStep(TypeTrackerContent content) { result = LoadStep(content) }
/** Gets the step summary for loading from `load` and then storing into `store`. */
StepSummary loadStoreStep(TypeTrackerContent load, TypeTrackerContent store) {
result = LoadStoreStep(load, store)
}
/** Gets the step summary for a step that only permits contents matched by `filter`. */
StepSummary withContent(ContentFilter filter) { result = WithContent(filter) }
/** Gets the step summary for a step that blocks contents matched by `filter`. */
StepSummary withoutContent(ContentFilter filter) { result = WithoutContent(filter) }
/** Gets the step summary for a jump step. */
StepSummary jumpStep() { result = JumpStep() }
}
/**
* DEPRECATED: Use `semmle.python.dataflow.new.TypeTracking` instead.
*
* A summary of the steps needed to track a value to a given dataflow node.
*
* This can be used to track objects that implement a certain API in order to
* recognize calls to that API. Note that type-tracking does not by itself provide a
* source/sink relation, that is, it may determine that a node has a given type,
* but it won't determine where that type came from.
*
* It is recommended that all uses of this type are written in the following form,
* for tracking some type `myType`:
* ```ql
* DataFlow::TypeTrackingNode myType(DataFlow::TypeTracker t) {
* t.start() and
* result = < source of myType >
* or
* exists (DataFlow::TypeTracker t2 |
* result = myType(t2).track(t2, t)
* )
* }
*
* DataFlow::Node myType() { myType(DataFlow::TypeTracker::end()).flowsTo(result) }
* ```
*
* Instead of `result = myType(t2).track(t2, t)`, you can also use the equivalent
* `t = t2.step(myType(t2), result)`. If you additionally want to track individual
* intra-procedural steps, use `t = t2.smallstep(myCallback(t2), result)`.
*/
deprecated class TypeTracker extends TTypeTracker {
Boolean hasCall;
OptionalTypeTrackerContent content;
TypeTracker() { this = MkTypeTracker(hasCall, content) }
/** Gets the summary resulting from appending `step` to this type-tracking summary. */
TypeTracker append(StepSummary step) { result = append(this, step) }
/** Gets a textual representation of this summary. */
string toString() {
exists(string withCall, string withContent |
(if hasCall = true then withCall = "with" else withCall = "without") and
(
if content != noContent()
then withContent = " with content " + content
else withContent = ""
) and
result = "type tracker " + withCall + " call steps" + withContent
)
}
/**
* Holds if this is the starting point of type tracking.
*/
predicate start() { hasCall = false and content = noContent() }
/**
* Holds if this is the starting point of type tracking, and the value starts in the content named `contentName`.
* The type tracking only ends after the content has been loaded.
*/
predicate startInContent(TypeTrackerContent contentName) {
hasCall = false and content = contentName
}
/**
* Holds if this is the starting point of type tracking
* when tracking a parameter into a call, but not out of it.
*/
predicate call() { hasCall = true and content = noContent() }
/**
* Holds if this is the end point of type tracking.
*/
predicate end() { content = noContent() }
/**
* INTERNAL. DO NOT USE.
*
* Holds if this type has been tracked into a call.
*/
boolean hasCall() { result = hasCall }
/**
* INTERNAL. DO NOT USE.
*
* Gets the content associated with this type tracker.
*/
OptionalTypeTrackerContent getContent() { result = content }
/**
* Gets a type tracker that starts where this one has left off to allow continued
* tracking.
*
* This predicate is only defined if the type is not associated to a piece of content.
*/
TypeTracker continue() { content = noContent() and result = this }
/**
* Gets the summary that corresponds to having taken a forwards
* heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
*/
bindingset[nodeFrom, this]
pragma[inline_late]
pragma[noopt]
TypeTracker step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) {
exists(StepSummary summary |
stepProj(nodeFrom, summary) and
result = this.append(summary) and
step(nodeFrom, nodeTo, summary)
)
}
bindingset[nodeFrom, this]
pragma[inline_late]
pragma[noopt]
private TypeTracker smallstepNoSimpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
exists(StepSummary summary |
smallstepProj(nodeFrom, summary) and
result = this.append(summary) and
smallstep(nodeFrom, nodeTo, summary)
)
}
/**
* Gets the summary that corresponds to having taken a forwards
* local, heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
*
* Unlike `TypeTracker::step`, this predicate exposes all edges
* in the flow graph, and not just the edges between `Node`s.
* It may therefore be less performant.
*
* Type tracking predicates using small steps typically take the following form:
* ```ql
* DataFlow::Node myType(DataFlow::TypeTracker t) {
* t.start() and
* result = < source of myType >
* or
* exists (DataFlow::TypeTracker t2 |
* t = t2.smallstep(myType(t2), result)
* )
* }
*
* DataFlow::Node myType() {
* result = myType(DataFlow::TypeTracker::end())
* }
* ```
*/
pragma[inline]
TypeTracker smallstep(Node nodeFrom, Node nodeTo) {
result = this.smallstepNoSimpleLocalFlowStep(nodeFrom, nodeTo)
or
simpleLocalFlowStep(nodeFrom, nodeTo) and
result = this
}
}
/** Provides predicates for implementing custom `TypeTracker`s. */
deprecated module TypeTracker {
/**
* Gets a valid end point of type tracking.
*/
TypeTracker end() { result.end() }
/**
* INTERNAL USE ONLY.
*
* Gets a valid end point of type tracking with the call bit set to the given value.
*/
predicate end = Cached::noContentTypeTracker/1;
}
pragma[nomagic]
deprecated private predicate backStepProj(TypeTrackingNode nodeTo, StepSummary summary) {
step(_, nodeTo, summary)
}
deprecated private predicate backSmallstepProj(TypeTrackingNode nodeTo, StepSummary summary) {
smallstep(_, nodeTo, summary)
}
/**
* DEPRECATED: Use `semmle.python.dataflow.new.TypeTracking` instead.
*
* A summary of the steps needed to back-track a use of a value to a given dataflow node.
*
* This can for example be used to track callbacks that are passed to a certain API,
* so we can model specific parameters of that callback as having a certain type.
*
* Note that type back-tracking does not provide a source/sink relation, that is,
* it may determine that a node will be used in an API call somewhere, but it won't
* determine exactly where that use was, or the path that led to the use.
*
* It is recommended that all uses of this type are written in the following form,
* for back-tracking some callback type `myCallback`:
*
* ```ql
* DataFlow::TypeTrackingNode myCallback(DataFlow::TypeBackTracker t) {
* t.start() and
* result = (< some API call >).getArgument(< n >).getALocalSource()
* or
* exists (DataFlow::TypeBackTracker t2 |
* result = myCallback(t2).backtrack(t2, t)
* )
* }
*
* DataFlow::TypeTrackingNode myCallback() { result = myCallback(DataFlow::TypeBackTracker::end()) }
* ```
*
* Instead of `result = myCallback(t2).backtrack(t2, t)`, you can also use the equivalent
* `t2 = t.step(result, myCallback(t2))`. If you additionally want to track individual
* intra-procedural steps, use `t2 = t.smallstep(result, myCallback(t2))`.
*/
deprecated class TypeBackTracker extends TTypeBackTracker {
Boolean hasReturn;
OptionalTypeTrackerContent content;
TypeBackTracker() { this = MkTypeBackTracker(hasReturn, content) }
/** Gets the summary resulting from prepending `step` to this type-tracking summary. */
TypeBackTracker prepend(StepSummary step) { result = prepend(this, step) }
/** Gets a textual representation of this summary. */
string toString() {
exists(string withReturn, string withContent |
(if hasReturn = true then withReturn = "with" else withReturn = "without") and
(
if content != noContent()
then withContent = " with content " + content
else withContent = ""
) and
result = "type back-tracker " + withReturn + " return steps" + withContent
)
}
/**
* Holds if this is the starting point of type tracking.
*/
predicate start() { hasReturn = false and content = noContent() }
/**
* Holds if this is the end point of type tracking.
*/
predicate end() { content = noContent() }
/**
* INTERNAL. DO NOT USE.
*
* Holds if this type has been back-tracked into a call through return edge.
*/
boolean hasReturn() { result = hasReturn }
/**
* Gets a type tracker that starts where this one has left off to allow continued
* tracking.
*
* This predicate is only defined if the type has not been tracked into a piece of content.
*/
TypeBackTracker continue() { content = noContent() and result = this }
/**
* Gets the summary that corresponds to having taken a backwards
* heap and/or inter-procedural step from `nodeTo` to `nodeFrom`.
*/
bindingset[nodeTo, result]
pragma[inline_late]
pragma[noopt]
TypeBackTracker step(TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo) {
exists(StepSummary summary |
backStepProj(nodeTo, summary) and
this = result.prepend(summary) and
step(nodeFrom, nodeTo, summary)
)
}
bindingset[nodeTo, result]
pragma[inline_late]
pragma[noopt]
private TypeBackTracker smallstepNoSimpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
exists(StepSummary summary |
backSmallstepProj(nodeTo, summary) and
this = result.prepend(summary) and
smallstep(nodeFrom, nodeTo, summary)
)
}
/**
* Gets the summary that corresponds to having taken a backwards
* local, heap and/or inter-procedural step from `nodeTo` to `nodeFrom`.
*
* Unlike `TypeBackTracker::step`, this predicate exposes all edges
* in the flowgraph, and not just the edges between
* `TypeTrackingNode`s. It may therefore be less performant.
*
* Type tracking predicates using small steps typically take the following form:
* ```ql
* DataFlow::Node myType(DataFlow::TypeBackTracker t) {
* t.start() and
* result = < some API call >.getArgument(< n >)
* or
* exists (DataFlow::TypeBackTracker t2 |
* t = t2.smallstep(result, myType(t2))
* )
* }
*
* DataFlow::Node myType() {
* result = myType(DataFlow::TypeBackTracker::end())
* }
* ```
*/
pragma[inline]
TypeBackTracker smallstep(Node nodeFrom, Node nodeTo) {
this = this.smallstepNoSimpleLocalFlowStep(nodeFrom, nodeTo)
or
simpleLocalFlowStep(nodeFrom, nodeTo) and
this = result
}
/**
* Gets a forwards summary that is compatible with this backwards summary.
* That is, if this summary describes the steps needed to back-track a value
* from `sink` to `mid`, and the result is a valid summary of the steps needed
* to track a value from `source` to `mid`, then the value from `source` may
* also flow to `sink`.
*/
TypeTracker getACompatibleTypeTracker() {
exists(boolean hasCall, OptionalTypeTrackerContent c |
result = MkTypeTracker(hasCall, c) and
(
compatibleContents(c, content)
or
content = noContent() and c = content
)
|
hasCall = false
or
this.hasReturn() = false
)
}
}
/** Provides predicates for implementing custom `TypeBackTracker`s. */
deprecated module TypeBackTracker {
/**
* Gets a valid end point of type back-tracking.
*/
TypeBackTracker end() { result.end() }
}
/**
* INTERNAL: Do not use.
*
* Provides logic for constructing a call graph in mutual recursion with type tracking.
*
* When type tracking is used to construct a call graph, we cannot use the join-order
* from `stepInlineLate`, because `step` becomes a recursive call, which means that we
* will have a conjunct with 3 recursive calls: the call to `step`, the call to `stepProj`,
* and the recursive type tracking call itself. The solution is to split the three-way
* non-linear recursion into two non-linear predicates: one that first joins with the
* projected `stepCall` relation, followed by a predicate that joins with the full
* `stepCall` relation (`stepNoCall` not being recursive, can be join-ordered in the
* same way as in `stepInlineLate`).
*/
deprecated module CallGraphConstruction {
/** The input to call graph construction. */
signature module InputSig {
/** A state to track during type tracking. */
class State;
/** Holds if type tracking should start at `start` in state `state`. */
deprecated predicate start(Node start, State state);
/**
* Holds if type tracking should use the step from `nodeFrom` to `nodeTo`,
* which _does not_ depend on the call graph.
*
* Implementing this predicate using `StepSummary::[small]stepNoCall` yields
* standard type tracking.
*/
deprecated predicate stepNoCall(Node nodeFrom, Node nodeTo, StepSummary summary);
/**
* Holds if type tracking should use the step from `nodeFrom` to `nodeTo`,
* which _does_ depend on the call graph.
*
* Implementing this predicate using `StepSummary::[small]stepCall` yields
* standard type tracking.
*/
deprecated predicate stepCall(Node nodeFrom, Node nodeTo, StepSummary summary);
/** A projection of an element from the state space. */
class StateProj;
/** Gets the projection of `state`. */
StateProj stateProj(State state);
/** Holds if type tracking should stop at `n` when we are tracking projected state `stateProj`. */
deprecated predicate filter(Node n, StateProj stateProj);
}
/** Provides the `track` predicate for use in call graph construction. */
module Make<InputSig Input> {
pragma[nomagic]
deprecated private predicate stepNoCallProj(Node nodeFrom, StepSummary summary) {
Input::stepNoCall(nodeFrom, _, summary)
}
pragma[nomagic]
deprecated private predicate stepCallProj(Node nodeFrom, StepSummary summary) {
Input::stepCall(nodeFrom, _, summary)
}
bindingset[nodeFrom, t]
pragma[inline_late]
pragma[noopt]
deprecated private TypeTracker stepNoCallInlineLate(
TypeTracker t, TypeTrackingNode nodeFrom, TypeTrackingNode nodeTo
) {
exists(StepSummary summary |
stepNoCallProj(nodeFrom, summary) and
result = t.append(summary) and
Input::stepNoCall(nodeFrom, nodeTo, summary)
)
}
bindingset[state]
pragma[inline_late]
private Input::StateProj stateProjInlineLate(Input::State state) {
result = Input::stateProj(state)
}
pragma[nomagic]
deprecated private Node track(Input::State state, TypeTracker t) {
t.start() and Input::start(result, state)
or
exists(Input::StateProj stateProj |
stateProj = stateProjInlineLate(state) and
not Input::filter(result, stateProj)
|
exists(TypeTracker t2 | t = stepNoCallInlineLate(t2, track(state, t2), result))
or
exists(StepSummary summary |
// non-linear recursion
Input::stepCall(trackCall(state, t, summary), result, summary)
)
)
}
bindingset[t, summary]
pragma[inline_late]
deprecated private TypeTracker appendInlineLate(TypeTracker t, StepSummary summary) {
result = t.append(summary)
}
pragma[nomagic]
deprecated private Node trackCall(Input::State state, TypeTracker t, StepSummary summary) {
exists(TypeTracker t2 |
// non-linear recursion
result = track(state, t2) and
stepCallProj(result, summary) and
t = appendInlineLate(t2, summary)
)
}
/** Gets a node that can be reached from _some_ start node in state `state`. */
pragma[nomagic]
deprecated Node track(Input::State state) { result = track(state, TypeTracker::end()) }
}
/** A simple version of `CallGraphConstruction` that uses standard type tracking. */
module Simple {
/** The input to call graph construction. */
signature module InputSig {
/** A state to track during type tracking. */
class State;
/** Holds if type tracking should start at `start` in state `state`. */
deprecated predicate start(Node start, State state);
/** Holds if type tracking should stop at `n`. */
deprecated predicate filter(Node n);
}
/** Provides the `track` predicate for use in call graph construction. */
module Make<InputSig Input> {
deprecated private module I implements CallGraphConstruction::InputSig {
private import codeql.util.Unit
class State = Input::State;
predicate start(Node start, State state) { Input::start(start, state) }
predicate stepNoCall(Node nodeFrom, Node nodeTo, StepSummary summary) {
StepSummary::stepNoCall(nodeFrom, nodeTo, summary)
}
predicate stepCall(Node nodeFrom, Node nodeTo, StepSummary summary) {
StepSummary::stepCall(nodeFrom, nodeTo, summary)
}
class StateProj = Unit;
Unit stateProj(State state) { exists(state) and exists(result) }
predicate filter(Node n, Unit u) {
Input::filter(n) and
exists(u)
}
}
deprecated import CallGraphConstruction::Make<I>
}
}
}

View File

@@ -1,102 +0,0 @@
/**
* Provides Python-specific definitions for use in the type tracker library.
*/
private import python
private import semmle.python.dataflow.new.internal.DataFlowPublic as DataFlowPublic
private import TypeTrackingImpl as TypeTrackingImpl
deprecated class Node = DataFlowPublic::Node;
deprecated class TypeTrackingNode = DataFlowPublic::TypeTrackingNode;
/** A content name for use by type trackers, or the empty string. */
deprecated class OptionalTypeTrackerContent extends string {
OptionalTypeTrackerContent() {
this = ""
or
this = any(DataFlowPublic::AttributeContent dfc).getAttribute()
}
}
/** A content name for use by type trackers. */
deprecated class TypeTrackerContent extends OptionalTypeTrackerContent {
TypeTrackerContent() { this != "" }
}
/** Gets the content string representing no value. */
deprecated OptionalTypeTrackerContent noContent() { result = "" }
/**
* A label to use for `WithContent` and `WithoutContent` steps, restricting
* which `ContentSet` may pass through. Not currently used in Python.
*/
deprecated class ContentFilter extends Unit {
TypeTrackerContent getAMatchingContent() { none() }
}
pragma[inline]
deprecated predicate compatibleContents(
TypeTrackerContent storeContent, TypeTrackerContent loadContent
) {
storeContent = loadContent
}
deprecated predicate simpleLocalFlowStep =
TypeTrackingImpl::TypeTrackingInput::simpleLocalSmallStep/2;
deprecated predicate jumpStep = TypeTrackingImpl::TypeTrackingInput::jumpStep/2;
/** Holds if there is a level step from `nodeFrom` to `nodeTo`, which may depend on the call graph. */
deprecated predicate levelStepCall(Node nodeFrom, Node nodeTo) { none() }
/** Holds if there is a level step from `nodeFrom` to `nodeTo`, which does not depend on the call graph. */
deprecated predicate levelStepNoCall = TypeTrackingImpl::TypeTrackingInput::levelStepNoCall/2;
/**
* Holds if `nodeFrom` steps to `nodeTo` by being passed as a parameter in a call.
*
* Flow into summarized library methods is not included, as that will lead to negative
* recursion (or, at best, terrible performance), since identifying calls to library
* methods is done using API graphs (which uses type tracking).
*/
deprecated predicate callStep = TypeTrackingImpl::TypeTrackingInput::callStep/2;
/** Holds if `nodeFrom` steps to `nodeTo` by being returned from a call. */
deprecated predicate returnStep = TypeTrackingImpl::TypeTrackingInput::returnStep/2;
/**
* Holds if `nodeFrom` is being written to the `content` content of the object in `nodeTo`.
*/
deprecated predicate basicStoreStep = TypeTrackingImpl::TypeTrackingInput::storeStep/3;
/**
* Holds if `nodeTo` is the result of accessing the `content` content of `nodeFrom`.
*/
deprecated predicate basicLoadStep = TypeTrackingImpl::TypeTrackingInput::loadStep/3;
/**
* Holds if the `loadContent` of `nodeFrom` is stored in the `storeContent` of `nodeTo`.
*/
deprecated predicate basicLoadStoreStep = TypeTrackingImpl::TypeTrackingInput::loadStoreStep/4;
/**
* Holds if type-tracking should step from `nodeFrom` to `nodeTo` but block flow of contents matched by `filter` through here.
*/
deprecated predicate basicWithoutContentStep(Node nodeFrom, Node nodeTo, ContentFilter filter) {
none()
}
/**
* Holds if type-tracking should step from `nodeFrom` to `nodeTo` if inside a content matched by `filter`.
*/
deprecated predicate basicWithContentStep(Node nodeFrom, Node nodeTo, ContentFilter filter) {
none()
}
/**
* A utility class that is equivalent to `boolean` but does not require type joining.
*/
deprecated class Boolean extends boolean {
Boolean() { this = true or this = false }
}

View File

@@ -106,7 +106,7 @@ private module SummaryTypeTrackerInput implements SummaryTypeTracker::Input {
private module TypeTrackerSummaryFlow = SummaryTypeTracker::SummaryFlow<SummaryTypeTrackerInput>;
module TypeTrackingInput implements Shared::TypeTrackingInput {
module TypeTrackingInput implements Shared::TypeTrackingInput<Location> {
class Node = DataFlowPublic::Node;
class LocalSourceNode = DataFlowPublic::LocalSourceNode;
@@ -323,4 +323,4 @@ module TypeTrackingInput implements Shared::TypeTrackingInput {
predicate nonStandardFlowsTo(LocalSourceNode localSource, Node dst) { localSource.flowsTo(dst) }
}
import SharedImpl::TypeTracking<TypeTrackingInput>
import SharedImpl::TypeTracking<Location, TypeTrackingInput>

View File

@@ -1781,15 +1781,6 @@ module StdlibPrivate {
* See https://docs.python.org/3/library/cgi.html.
*/
module FieldStorage {
/**
* DEPRECATED: Use `subclassRef` predicate instead.
*
* Gets a reference to the `cgi.FieldStorage` class.
*/
deprecated API::Node classRef() {
result = API::moduleImport("cgi").getMember("FieldStorage")
}
/** Gets a reference to the `cgi.FieldStorage` class or any subclass. */
API::Node subclassRef() {
result = API::moduleImport("cgi").getMember("FieldStorage").getASubclass*()
@@ -1900,168 +1891,15 @@ module StdlibPrivate {
// ---------------------------------------------------------------------------
// BaseHTTPServer (Python 2 only)
// ---------------------------------------------------------------------------
/**
* DEPRECATED: Use API-graphs directly instead.
*
* Gets a reference to the `BaseHttpServer` module.
*/
deprecated API::Node baseHttpServer() { result = API::moduleImport("BaseHTTPServer") }
/**
* DEPRECATED: Use API-graphs directly instead.
*
* Provides models for the `BaseHttpServer` module.
*/
deprecated module BaseHttpServer {
/**
* DEPRECATED: Use API-graphs directly instead.
*
* Provides models for the `BaseHTTPServer.BaseHTTPRequestHandler` class (Python 2 only).
*/
deprecated module BaseHttpRequestHandler {
/**
* DEPRECATED: Use API-graphs directly instead.
*
* Gets a reference to the `BaseHttpServer.BaseHttpRequestHandler` class.
*/
deprecated API::Node classRef() {
result = baseHttpServer().getMember("BaseHTTPRequestHandler")
}
}
}
// ---------------------------------------------------------------------------
// SimpleHTTPServer (Python 2 only)
// ---------------------------------------------------------------------------
/**
* DEPRECATED: Use API-graphs directly instead.
*
* Gets a reference to the `SimpleHttpServer` module.
*/
deprecated API::Node simpleHttpServer() { result = API::moduleImport("SimpleHTTPServer") }
/**
* DEPRECATED: Use API-graphs directly instead.
*
* Provides models for the `SimpleHttpServer` module.
*/
deprecated module SimpleHttpServer {
/**
* DEPRECATED: Use API-graphs directly instead.
*
* Provides models for the `SimpleHTTPServer.SimpleHTTPRequestHandler` class (Python 2 only).
*/
deprecated module SimpleHttpRequestHandler {
/**
* DEPRECATED: Use API-graphs directly instead.
*
* Gets a reference to the `SimpleHttpServer.SimpleHttpRequestHandler` class.
*/
deprecated API::Node classRef() {
result = simpleHttpServer().getMember("SimpleHTTPRequestHandler")
}
}
}
// ---------------------------------------------------------------------------
// CGIHTTPServer (Python 2 only)
// ---------------------------------------------------------------------------
/**
* DEPRECATED: Use API-graphs directly instead.
*
* Gets a reference to the `CGIHTTPServer` module.
*/
deprecated API::Node cgiHttpServer() { result = API::moduleImport("CGIHTTPServer") }
/** Provides models for the `CGIHTTPServer` module. */
deprecated module CgiHttpServer {
/**
* DEPRECATED: Use API-graphs directly instead.
*
* Provides models for the `CGIHTTPServer.CGIHTTPRequestHandler` class (Python 2 only).
*/
deprecated module CgiHttpRequestHandler {
/**
* DEPRECATED: Use API-graphs directly instead.
*
* Gets a reference to the `CGIHTTPServer.CgiHttpRequestHandler` class.
*/
deprecated API::Node classRef() {
result = cgiHttpServer().getMember("CGIHTTPRequestHandler")
}
}
}
// ---------------------------------------------------------------------------
// http (Python 3 only)
// ---------------------------------------------------------------------------
/**
* DEPRECATED: Use API-graphs directly instead.
*
* Gets a reference to the `http` module.
*/
deprecated API::Node http() { result = API::moduleImport("http") }
/** Provides models for the `http` module. */
deprecated module StdlibHttp {
// -------------------------------------------------------------------------
// http.server
// -------------------------------------------------------------------------
/**
* DEPRECATED: Use API-graphs directly instead.
*
* Gets a reference to the `http.server` module.
*/
deprecated API::Node server() { result = http().getMember("server") }
/**
* DEPRECATED: Use API-graphs directly instead.
*
* Provides models for the `http.server` module
*/
deprecated module Server {
/**
* DEPRECATED: Use API-graphs directly instead.
*
* Provides models for the `http.server.BaseHTTPRequestHandler` class (Python 3 only).
*
* See https://docs.python.org/3.9/library/http.server.html#http.server.BaseHTTPRequestHandler.
*/
deprecated module BaseHttpRequestHandler {
/** Gets a reference to the `http.server.BaseHttpRequestHandler` class. */
deprecated API::Node classRef() { result = server().getMember("BaseHTTPRequestHandler") }
}
/**
* DEPRECATED: Use API-graphs directly instead.
*
* Provides models for the `http.server.SimpleHTTPRequestHandler` class (Python 3 only).
*
* See https://docs.python.org/3.9/library/http.server.html#http.server.SimpleHTTPRequestHandler.
*/
deprecated module SimpleHttpRequestHandler {
/** Gets a reference to the `http.server.SimpleHttpRequestHandler` class. */
deprecated API::Node classRef() { result = server().getMember("SimpleHTTPRequestHandler") }
}
/**
* DEPRECATED: Use API-graphs directly instead.
*
* Provides models for the `http.server.CGIHTTPRequestHandler` class (Python 3 only).
*
* See https://docs.python.org/3.9/library/http.server.html#http.server.CGIHTTPRequestHandler.
*/
deprecated module CgiHttpRequestHandler {
/**
* DEPRECATED: Use API-graphs directly instead.
*
* Gets a reference to the `http.server.CGIHTTPRequestHandler` class.
*/
deprecated API::Node classRef() { result = server().getMember("CGIHTTPRequestHandler") }
}
}
}
/**
* Provides models for the `BaseHTTPRequestHandler` class and subclasses.
*
@@ -4523,6 +4361,124 @@ module StdlibPrivate {
}
}
/** A flow summary for `map`. */
class MapSummary extends SummarizedCallable {
MapSummary() { this = "builtins.map" }
override DataFlow::CallCfgNode getACall() { result = API::builtin("map").getACall() }
override DataFlow::ArgumentNode getACallback() {
result = API::builtin("map").getAValueReachableFromSource()
}
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
exists(int i | exists(any(Call c).getArg(i)) |
(
input = "Argument[" + (i + 1).toString() + "].ListElement"
or
input = "Argument[" + (i + 1).toString() + "].SetElement"
or
// We reduce generality slightly by not tracking tuple contents on list arguments beyond the first, for performance.
// TODO: Once we have TupleElementAny, this generality can be increased.
i = 0 and
exists(DataFlow::TupleElementContent tc, int j | j = tc.getIndex() |
input = "Argument[1].TupleElement[" + j.toString() + "]"
)
// TODO: Once we have DictKeyContent, we need to transform that into ListElementContent
) and
output = "Argument[0].Parameter[" + i.toString() + "]" and
preservesValue = true
)
or
input = "Argument[0].ReturnValue" and
output = "ReturnValue.ListElement" and
preservesValue = true
}
}
/** A flow summary for `filter`. */
class FilterSummary extends SummarizedCallable {
FilterSummary() { this = "builtins.filter" }
override DataFlow::CallCfgNode getACall() { result = API::builtin("filter").getACall() }
override DataFlow::ArgumentNode getACallback() {
result = API::builtin("filter").getAValueReachableFromSource()
}
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
(
input = "Argument[1].ListElement"
or
input = "Argument[1].SetElement"
or
exists(DataFlow::TupleElementContent tc, int i | i = tc.getIndex() |
input = "Argument[1].TupleElement[" + i.toString() + "]"
)
// TODO: Once we have DictKeyContent, we need to transform that into ListElementContent
) and
(output = "Argument[0].Parameter[0]" or output = "ReturnValue.ListElement") and
preservesValue = true
}
}
/**A summary for `enumerate`. */
class EnumerateSummary extends SummarizedCallable {
EnumerateSummary() { this = "builtins.enumerate" }
override DataFlow::CallCfgNode getACall() { result = API::builtin("enumerate").getACall() }
override DataFlow::ArgumentNode getACallback() {
result = API::builtin("enumerate").getAValueReachableFromSource()
}
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
(
input = "Argument[0].ListElement"
or
input = "Argument[0].SetElement"
or
exists(DataFlow::TupleElementContent tc, int i | i = tc.getIndex() |
input = "Argument[0].TupleElement[" + i.toString() + "]"
)
// TODO: Once we have DictKeyContent, we need to transform that into ListElementContent
) and
output = "ReturnValue.ListElement.TupleElement[1]" and
preservesValue = true
}
}
/** A flow summary for `zip`. */
class ZipSummary extends SummarizedCallable {
ZipSummary() { this = "builtins.zip" }
override DataFlow::CallCfgNode getACall() { result = API::builtin("zip").getACall() }
override DataFlow::ArgumentNode getACallback() {
result = API::builtin("zip").getAValueReachableFromSource()
}
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
exists(int i | exists(any(Call c).getArg(i)) |
(
input = "Argument[" + i.toString() + "].ListElement"
or
input = "Argument[" + i.toString() + "].SetElement"
or
// We reduce generality slightly by not tracking tuple contents on arguments beyond the first two, for performance.
// TODO: Once we have TupleElementAny, this generality can be increased.
i in [0 .. 1] and
exists(DataFlow::TupleElementContent tc, int j | j = tc.getIndex() |
input = "Argument[" + i.toString() + "].TupleElement[" + j.toString() + "]"
)
// TODO: Once we have DictKeyContent, we need to transform that into ListElementContent
) and
output = "ReturnValue.ListElement.TupleElement[" + i.toString() + "]" and
preservesValue = true
)
}
}
// ---------------------------------------------------------------------------
// Flow summaries for container methods
// ---------------------------------------------------------------------------

View File

@@ -738,21 +738,9 @@ class PythonFunctionValue extends FunctionValue {
else result = "function " + this.getQualifiedName()
}
override int minParameters() {
exists(Function f |
f = this.getScope() and
result = count(f.getAnArg()) - count(f.getDefinition().getArgs().getADefault())
)
}
override int minParameters() { result = this.getScope().getMinPositionalArguments() }
override int maxParameters() {
exists(Function f |
f = this.getScope() and
if exists(f.getVararg())
then result = 2147483647 // INT_MAX
else result = count(f.getAnArg())
)
}
override int maxParameters() { result = this.getScope().getMaxPositionalArguments() }
/** Gets a control flow node corresponding to a return statement in this function */
ControlFlowNode getAReturnedNode() { result = this.getScope().getAReturnValueFlowNode() }

View File

@@ -21,6 +21,8 @@ private module CleartextLoggingConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "Clear-text logging of sensitive information" vulnerabilities. */

View File

@@ -21,6 +21,8 @@ private module CleartextStorageConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "Clear-text storage of sensitive information" vulnerabilities. */

View File

@@ -17,6 +17,8 @@ private module CodeInjectionConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "code injection" vulnerabilities. */

View File

@@ -20,6 +20,8 @@ module CommandInjectionConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "command injection" vulnerabilities. */

View File

@@ -20,6 +20,8 @@ module CookieInjectionConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "cookie injection" vulnerabilities. */

View File

@@ -16,6 +16,8 @@ private module HeaderInjectionConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node node) { node instanceof HttpHeaderInjection::Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof HttpHeaderInjection::Sanitizer }
predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "HTTP Header injection" vulnerabilities. */

View File

@@ -19,6 +19,8 @@ private module LdapInjectionDnConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof DnSink }
predicate isBarrier(DataFlow::Node node) { node instanceof DnSanitizer }
predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "LDAP injection via the distinguished name (DN) parameter" vulnerabilities. */
@@ -30,6 +32,8 @@ private module LdapInjectionFilterConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof FilterSink }
predicate isBarrier(DataFlow::Node node) { node instanceof FilterSanitizer }
predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "LDAP injection via the filter parameter" vulnerabilities. */

View File

@@ -17,6 +17,8 @@ private module LogInjectionConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "log injection" vulnerabilities. */

View File

@@ -56,6 +56,8 @@ module NoSqlInjectionConfig implements DataFlow::StateConfigSig {
predicate isBarrier(DataFlow::Node node) {
node = any(NoSqlSanitizer noSqlSanitizer).getAnInput()
}
predicate observeDiffInformedIncrementalMode() { any() }
}
module NoSqlInjectionFlow = TaintTracking::GlobalWithState<NoSqlInjectionConfig>;

View File

@@ -31,6 +31,8 @@ private module PamAuthorizationConfig implements DataFlow::ConfigSig {
// Flow from handle to the authenticate call in the final step
exists(VulnPamAuthCall c | c.getArg(0) = node1 | node2 = c)
}
predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "PAM Authorization" vulnerabilities. */

View File

@@ -71,6 +71,8 @@ module PathInjectionConfig implements DataFlow::StateConfigSig {
stateFrom instanceof NotNormalized and
stateTo instanceof NormalizedUnchecked
}
predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "path injection" vulnerabilities. */

View File

@@ -35,6 +35,11 @@ module PolynomialReDoS {
/** Gets the regex that is being executed by this node. */
abstract RegExpTerm getRegExp();
/** Gets a term within the regexp that may perform polynomial back-tracking. */
final PolynomialBackTrackingTerm getABacktrackingTerm() {
result.getRootTerm() = this.getRegExp()
}
/**
* Gets the node to highlight in the alert message.
*/

View File

@@ -17,6 +17,24 @@ private module PolynomialReDoSConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
// Diff-informed incremental mode is currently disabled for this query due to
// API limitations. The query exposes sink.getABacktrackingTerm() as an alert
// location, but there is no way to express that information through
// getASelectedSinkLocation() because there is no @location in the CodeQL
// database that corresponds to a term inside a regular expression. As a
// result, this query could miss alerts in diff-informed incremental mode.
//
// To address this problem, we need to have a version of
// getASelectedSinkLocation() that uses hasLocationInfo() instead of
// returning Location objects.
predicate observeDiffInformedIncrementalMode() { none() }
Location getASelectedSinkLocation(DataFlow::Node sink) {
result = sink.(Sink).getHighlight().getLocation()
or
result = sink.(Sink).getABacktrackingTerm().getLocation()
}
}
/** Global taint-tracking for detecting "polynomial regular expression denial of service (ReDoS)" vulnerabilities. */

View File

@@ -17,6 +17,8 @@ private module ReflectedXssConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "reflected server-side cross-site scripting" vulnerabilities. */

View File

@@ -18,6 +18,14 @@ private module RegexInjectionConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSinkLocation(DataFlow::Node sink) {
result = sink.(Sink).getLocation()
or
result = sink.(Sink).getRegexExecution().getLocation()
}
}
/** Global taint-tracking for detecting "regular expression injection" vulnerabilities. */

View File

@@ -29,6 +29,12 @@ private module FullServerSideRequestForgeryConfig implements DataFlow::ConfigSig
or
node instanceof FullUrlControlSanitizer
}
predicate observeDiffInformedIncrementalMode() {
// The partial request forgery query depends on `fullyControlledRequest` to reject alerts about
// such full-controlled requests, regardless of the associated source.
none()
}
}
/**
@@ -58,6 +64,13 @@ private module PartialServerSideRequestForgeryConfig implements DataFlow::Config
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSinkLocation(DataFlow::Node sink) {
// Note: this query does not select the sink itself
result = sink.(Sink).getRequest().getLocation()
}
}
/**

View File

@@ -17,6 +17,8 @@ private module SqlInjectionConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "SQL injection" vulnerabilities. */

View File

@@ -26,6 +26,8 @@ private module StackTraceExposureConfig implements DataFlow::ConfigSig {
nodeTo = attr
)
}
predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "stack trace exposure" vulnerabilities. */

View File

@@ -17,6 +17,8 @@ private module TarSlipConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "tar slip" vulnerabilities. */

View File

@@ -17,6 +17,8 @@ private module TemplateInjectionConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node node) { node instanceof Sink }
predicate isBarrierIn(DataFlow::Node node) { node instanceof Sanitizer }
predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "template injection" vulnerabilities. */

View File

@@ -17,6 +17,8 @@ private module UnsafeDeserializationConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "code execution from deserialization" vulnerabilities. */

View File

@@ -28,6 +28,16 @@ module UnsafeShellCommandConstructionConfig implements DataFlow::ConfigSig {
// override to require the path doesn't have unmatched return steps
DataFlow::FlowFeature getAFeature() { result instanceof DataFlow::FeatureHasSourceCallContext }
predicate observeDiffInformedIncrementalMode() { any() }
Location getASelectedSinkLocation(DataFlow::Node sink) {
result = sink.(Sink).getLocation()
or
result = sink.(Sink).getStringConstruction().getLocation()
or
result = sink.(Sink).getCommandExecution().getLocation()
}
}
/** Global taint-tracking for detecting "shell command constructed from library input" vulnerabilities. */

View File

@@ -32,6 +32,8 @@ private module UrlRedirectConfig implements DataFlow::StateConfigSig {
) {
any(UrlRedirect::AdditionalFlowStep a).step(nodeFrom, stateFrom, nodeTo, stateTo)
}
predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "URL redirection" vulnerabilities. */

View File

@@ -33,6 +33,8 @@ module NormalHashFunction {
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
sensitiveDataExtraStepForCalls(node1, node2)
}
predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "use of a broken or weak cryptographic hashing algorithm on sensitive data" vulnerabilities. */
@@ -63,6 +65,8 @@ module ComputationallyExpensiveHashFunction {
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
sensitiveDataExtraStepForCalls(node1, node2)
}
predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "use of a broken or weak cryptographic hashing algorithm on passwords" vulnerabilities. */

View File

@@ -17,6 +17,8 @@ private module XmlBombConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "XML bomb" vulnerabilities. */

View File

@@ -17,6 +17,8 @@ private module XpathInjectionConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "Xpath Injection" vulnerabilities. */

View File

@@ -17,6 +17,8 @@ private module XxeConfig implements DataFlow::ConfigSig {
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting "XML External Entity (XXE)" vulnerabilities. */

View File

@@ -62,7 +62,7 @@ module HeuristicNames {
*/
string maybeAccountInfo() {
result = "(?is).*acc(ou)?nt.*" or
result = "(?is).*(puid|username|userid|session(id|key)).*" or
result = "(?is).*(puid|user.?name|user.?id|session.?(id|key)).*" or
result = "(?s).*([uU]|^|_|[a-z](?=U))([uU][iI][dD]).*"
}
@@ -71,8 +71,8 @@ module HeuristicNames {
* a password or an authorization key.
*/
string maybePassword() {
result = "(?is).*pass(wd|word|code|phrase)(?!.*question).*" or
result = "(?is).*(auth(entication|ori[sz]ation)?)key.*"
result = "(?is).*pass(wd|word|code|.?phrase)(?!.*question).*" or
result = "(?is).*(auth(entication|ori[sz]ation)?).?key.*"
}
/**

View File

@@ -1,3 +1,43 @@
## 1.4.8
No user-facing changes.
## 1.4.7
### Minor Analysis Improvements
- The `py/mixed-tuple-returns` query no longer flags instances where the tuple is passed into the function as an argument, as this led to too many false positives.
## 1.4.6
### Minor Analysis Improvements
- The `py/special-method-wrong-signature` has been modernized and rewritten to no longer rely on outdated APIs. Moreover, the query no longer flags cases where a default value is never used, as these alerts were rarely useful.
### Bug Fixes
- The `py/unused-global-variable` now no longer flags variables that are only used in forward references (e.g. the `Foo` in `def bar(x: "Foo"): ...`).
## 1.4.5
No user-facing changes.
## 1.4.4
No user-facing changes.
## 1.4.3
No user-facing changes.
## 1.4.2
No user-facing changes.
## 1.4.1
No user-facing changes.
## 1.4.0
### New Queries

View File

@@ -0,0 +1,108 @@
/** Definitions for reasoning about the expected first argument names for methods. */
import python
import semmle.python.ApiGraphs
import semmle.python.dataflow.new.internal.DataFlowDispatch
import DataFlow
/** Holds if `f` is a method of the class `c`. */
private predicate methodOfClass(Function f, Class c) {
exists(FunctionDef d | d.getDefinedFunction() = f and d.getScope() = c)
}
/** Holds if `c` is a metaclass. */
private predicate isMetaclass(Class c) {
c = API::builtin("type").getASubclass*().asSource().asExpr().(ClassExpr).getInnerScope()
}
/** Holds if `c` is a Zope interface. */
private predicate isZopeInterface(Class c) {
c =
API::moduleImport("zope")
.getMember("interface")
.getMember("Interface")
.getASubclass*()
.asSource()
.asExpr()
.(ClassExpr)
.getInnerScope()
}
/**
* Holds if `f` is used in the initialisation of `c`.
* This means `f` isn't being used as a normal method.
* Ideally it should be a `@staticmethod`; however this wasn't possible prior to Python 3.10.
* We exclude this case from the `not-named-self` query.
* However there is potential for a new query that specifically covers and alerts for this case.
*/
private predicate usedInInit(Function f, Class c) {
methodOfClass(f, c) and
exists(Call call |
call.getScope() = c and
DataFlow::localFlow(DataFlow::exprNode(f.getDefinition()), DataFlow::exprNode(call.getFunc()))
)
}
/**
* Holds if `f` has no arguments, and also has a decorator.
* We assume that the decorator affect the method in such a way that a `self` parameter is unneeded.
*/
private predicate noArgsWithDecorator(Function f) {
not exists(f.getAnArg()) and
exists(f.getADecorator())
}
/** Holds if the first parameter of `f` should be named `self`. */
predicate shouldBeSelf(Function f, Class c) {
methodOfClass(f, c) and
not isStaticmethod(f) and
not isClassmethod(f) and
not isMetaclass(c) and
not isZopeInterface(c) and
not usedInInit(f, c) and
not noArgsWithDecorator(f)
}
/** Holds if the first parameter of `f` should be named `cls`. */
predicate shouldBeCls(Function f, Class c) {
methodOfClass(f, c) and
not isStaticmethod(f) and
(
isClassmethod(f) and not isMetaclass(c)
or
isMetaclass(c) and not isClassmethod(f)
)
}
/** Holds if the first parameter of `f` is named `self`. */
predicate firstArgNamedSelf(Function f) { f.getArgName(0) = "self" }
/** Holds if the first parameter of `f` refers to the class - it is either named `cls`, or it is named `self` and is a method of a metaclass. */
predicate firstArgRefersToCls(Function f, Class c) {
methodOfClass(f, c) and
exists(string argname | argname = f.getArgName(0) |
argname = "cls"
or
/* Not PEP8, but relatively common */
argname = "mcls"
or
/* If c is a metaclass, allow arguments named `self`. */
argname = "self" and
isMetaclass(c)
)
}
/** Holds if the first parameter of `f` should be named `self`, but isn't. */
predicate firstArgShouldBeNamedSelfAndIsnt(Function f) {
shouldBeSelf(f, _) and
not firstArgNamedSelf(f)
}
/** Holds if the first parameter of `f` should be named `cls`, but isn't. */
predicate firstArgShouldReferToClsAndDoesnt(Function f) {
exists(Class c |
methodOfClass(f, c) and
shouldBeCls(f, c) and
not firstArgRefersToCls(f, c)
)
}

View File

@@ -1,4 +1,4 @@
class Entry(object):
@classmethod
def make(klass):
def make(self):
return Entry()

View File

@@ -5,20 +5,19 @@
<overview>
<p> The first parameter of a class method, a new method or any metaclass method
should be called <code>cls</code>. This makes the purpose of the parameter clear to other developers.
<p> The first parameter of a class method (including certain special methods such as <code>__new__</code>), or a method of a metaclass,
should be named <code>cls</code>.
</p>
</overview>
<recommendation>
<p>Change the name of the first parameter to <code>cls</code> as recommended by the style guidelines
<p>Ensure that the first parameter of class methods is named <code>cls</code>, as recommended by the style guidelines
in PEP 8.</p>
</recommendation>
<example>
<p>In the example, the first parameter to <code>make()</code> is <code>klass</code> which should be changed to <code>cls</code>
for ease of comprehension.
<p>In the following example, the first parameter of the class method <code>make</code> is named <code>self</code> instead of <code>cls</code>.
</p>
<sample src="NonCls.py" />
@@ -29,6 +28,7 @@ for ease of comprehension.
<li>Python PEP 8: <a href="http://www.python.org/dev/peps/pep-0008/#function-and-method-arguments">Function and method arguments</a>.</li>
<li>Python Tutorial: <a href="http://docs.python.org/2/tutorial/classes.html">Classes</a>.</li>
<li>Python Docs: <a href="https://docs.python.org/3/library/functions.html#classmethod">classmethod</a>.</li>
</references>

View File

@@ -1,7 +1,6 @@
/**
* @name First parameter of a class method is not named 'cls'
* @description Using an alternative name for the first parameter of a class method makes code more
* difficult to read; PEP8 states that the first parameter to class methods should be 'cls'.
* @description By the PEP8 style guide, the first parameter of a class method should be named `cls`.
* @kind problem
* @tags maintainability
* readability
@@ -13,30 +12,11 @@
*/
import python
predicate first_arg_cls(Function f) {
exists(string argname | argname = f.getArgName(0) |
argname = "cls"
or
/* Not PEP8, but relatively common */
argname = "mcls"
)
}
predicate is_type_method(Function f) {
exists(ClassValue c | c.getScope() = f.getScope() and c.getASuperType() = ClassValue::type())
}
predicate classmethod_decorators_only(Function f) {
forall(Expr decorator | decorator = f.getADecorator() | decorator.(Name).getId() = "classmethod")
}
import MethodArgNames
from Function f, string message
where
(f.getADecorator().(Name).getId() = "classmethod" or is_type_method(f)) and
not first_arg_cls(f) and
classmethod_decorators_only(f) and
not f.getName() = "__new__" and
firstArgShouldReferToClsAndDoesnt(f) and
(
if exists(f.getArgName(0))
then

View File

@@ -1,9 +1,9 @@
class Point:
def __init__(val, x, y): # first parameter is mis-named 'val'
def __init__(val, x, y): # BAD: first parameter is mis-named 'val'
val._x = x
val._y = y
class Point2:
def __init__(self, x, y): # first parameter is correctly named 'self'
def __init__(self, x, y): # GOOD: first parameter is correctly named 'self'
self._x = x
self._y = y

View File

@@ -6,22 +6,18 @@
<overview>
<p> Normal methods should have at least one parameter and the first parameter should be called <code>self</code>.
This makes the purpose of the parameter clear to other developers.
</p>
</overview>
<recommendation>
<p>If there is at least one parameter, then change the name of the first parameter to <code>self</code> as recommended by the style guidelines
<p>Ensure that the first parameter of a normal method is named <code>self</code>, as recommended by the style guidelines
in PEP 8.</p>
<p>If there are no parameters, then it cannot be a normal method. It may need to be marked as a <code>staticmethod</code>
or it could be moved out of the class as a normal function.
<p>If a <code>self</code> parameter is unneeded, the method should be decorated with <code>staticmethod</code>, or moved out of the class as a regular function.
</p>
</recommendation>
<example>
<p>The following methods can both be used to assign values to variables in a <code>point</code>
object. The second method makes the association clearer because the <code>self</code> parameter is
used.</p>
<p>In the following cases, the first argument of <code>Point.__init__</code> is named <code>val</code> instead; whereas in <code>Point2.__init__</code> it is correctly named <code>self</code>.</p>
<sample src="NonSelf.py" />
@@ -31,7 +27,7 @@ used.</p>
<li>Python PEP 8: <a href="http://www.python.org/dev/peps/pep-0008/#function-and-method-arguments">Function and
method arguments</a>.</li>
<li>Python Tutorial: <a href="http://docs.python.org/2/tutorial/classes.html">Classes</a>.</li>
<li>Python Docs: <a href="https://docs.python.org/3/library/functions.html#staticmethod">staticmethod</a>.</li>
</references>

View File

@@ -1,8 +1,6 @@
/**
* @name First parameter of a method is not named 'self'
* @description Using an alternative name for the first parameter of an instance method makes
* code more difficult to read; PEP8 states that the first parameter to instance
* methods should be 'self'.
* @description By the PEP8 style guide, the first parameter of a normal method should be named `self`.
* @kind problem
* @tags maintainability
* readability
@@ -14,45 +12,19 @@
*/
import python
import semmle.python.libraries.Zope
import MethodArgNames
predicate is_type_method(FunctionValue fv) {
exists(ClassValue c | c.declaredAttribute(_) = fv and c.getASuperType() = ClassValue::type())
}
predicate used_in_defining_scope(FunctionValue fv) {
exists(Call c | c.getScope() = fv.getScope().getScope() and c.getFunc().pointsTo(fv))
}
from Function f, FunctionValue fv, string message
from Function f, string message
where
exists(ClassValue cls, string name |
cls.declaredAttribute(name) = fv and
cls.isNewStyle() and
not name = "__new__" and
not name = "__metaclass__" and
not name = "__init_subclass__" and
not name = "__class_getitem__" and
/* declared in scope */
f.getScope() = cls.getScope()
) and
not f.getArgName(0) = "self" and
not is_type_method(fv) and
fv.getScope() = f and
not f.getName() = "lambda" and
not used_in_defining_scope(fv) and
firstArgShouldBeNamedSelfAndIsnt(f) and
(
(
if exists(f.getArgName(0))
then
message =
"Normal methods should have 'self', rather than '" + f.getArgName(0) +
"', as their first parameter."
else
message =
"Normal methods should have at least one parameter (the first of which should be 'self')."
) and
not f.hasVarArg()
) and
not fv instanceof ZopeInterfaceMethodValue
if exists(f.getArgName(0))
then
message =
"Normal methods should have 'self', rather than '" + f.getArgName(0) +
"', as their first parameter."
else
message =
"Normal methods should have at least one parameter (the first of which should be 'self')."
)
select f, message

View File

@@ -4,6 +4,7 @@
* @kind problem
* @tags reliability
* maintainability
* quality
* @problem.severity recommendation
* @sub-severity high
* @precision high
@@ -11,13 +12,15 @@
*/
import python
import semmle.python.ApiGraphs
predicate returns_tuple_of_size(Function func, int size, AstNode origin) {
exists(Return return, TupleValue val |
predicate returns_tuple_of_size(Function func, int size, Tuple tuple) {
exists(Return return, DataFlow::Node value |
value.asExpr() = return.getValue() and
return.getScope() = func and
return.getValue().pointsTo(val, origin)
any(DataFlow::LocalSourceNode n | n.asExpr() = tuple).flowsTo(value)
|
size = val.length()
size = count(int n | exists(tuple.getElt(n)))
)
}
@@ -25,6 +28,8 @@ from Function func, int s1, int s2, AstNode t1, AstNode t2
where
returns_tuple_of_size(func, s1, t1) and
returns_tuple_of_size(func, s2, t2) and
s1 < s2
s1 < s2 and
// Don't report on functions that have a return type annotation
not exists(func.getDefinition().(FunctionExpr).getReturns())
select func, func.getQualifiedName() + " returns $@ and $@.", t1, "tuple of size " + s1, t2,
"tuple of size " + s2

View File

@@ -4,6 +4,7 @@
* @kind problem
* @tags reliability
* correctness
* quality
* @problem.severity error
* @sub-severity low
* @precision high
@@ -11,12 +12,15 @@
*/
import python
import semmle.python.dataflow.new.internal.DataFlowDispatch as DD
predicate is_unary_op(string name) {
name in [
"__del__", "__repr__", "__neg__", "__pos__", "__abs__", "__invert__", "__complex__",
"__int__", "__float__", "__long__", "__oct__", "__hex__", "__str__", "__index__", "__enter__",
"__hash__", "__bool__", "__nonzero__", "__unicode__", "__len__", "__iter__", "__reversed__"
"__hash__", "__bool__", "__nonzero__", "__unicode__", "__len__", "__iter__", "__reversed__",
"__aenter__", "__aiter__", "__anext__", "__await__", "__ceil__", "__floor__", "__trunc__",
"__length_hint__", "__dir__", "__bytes__"
]
}
@@ -28,91 +32,138 @@ predicate is_binary_op(string name) {
"__and__", "__xor__", "__or__", "__ne__", "__radd__", "__rsub__", "__rmul__", "__rfloordiv__",
"__rdiv__", "__rtruediv__", "__rmod__", "__rdivmod__", "__rpow__", "__rlshift__", "__gt__",
"__rrshift__", "__rand__", "__rxor__", "__ror__", "__iadd__", "__isub__", "__imul__",
"__ifloordiv__", "__idiv__", "__itruediv__", "__ge__", "__imod__", "__idivmod__", "__ipow__",
"__ilshift__", "__irshift__", "__iand__", "__ixor__", "__ior__", "__coerce__", "__cmp__",
"__rcmp__", "__getattr___", "__getattribute___"
"__ifloordiv__", "__idiv__", "__itruediv__", "__ge__", "__imod__", "__ipow__", "__ilshift__",
"__irshift__", "__iand__", "__ixor__", "__ior__", "__coerce__", "__cmp__", "__rcmp__",
"__getattr__", "__getattribute__", "__buffer__", "__release_buffer__", "__matmul__",
"__rmatmul__", "__imatmul__", "__missing__", "__class_getitem__", "__mro_entries__",
"__format__"
]
}
predicate is_ternary_op(string name) {
name in ["__setattr__", "__set__", "__setitem__", "__getslice__", "__delslice__"]
name in ["__setattr__", "__set__", "__setitem__", "__getslice__", "__delslice__", "__set_name__"]
}
predicate is_quad_op(string name) { name = "__setslice__" or name = "__exit__" }
predicate is_quad_op(string name) { name in ["__setslice__", "__exit__", "__aexit__"] }
int argument_count(PythonFunctionValue f, string name, ClassValue cls) {
cls.declaredAttribute(name) = f and
(
is_unary_op(name) and result = 1
or
is_binary_op(name) and result = 2
or
is_ternary_op(name) and result = 3
or
is_quad_op(name) and result = 4
)
int argument_count(string name) {
is_unary_op(name) and result = 1
or
is_binary_op(name) and result = 2
or
is_ternary_op(name) and result = 3
or
is_quad_op(name) and result = 4
}
/**
* Returns 1 if `func` is a static method, and 0 otherwise. This predicate is used to adjust the
* number of expected arguments for a special method accordingly.
*/
int staticmethod_correction(Function func) {
if DD::isStaticmethod(func) then result = 1 else result = 0
}
predicate incorrect_special_method_defn(
PythonFunctionValue func, string message, boolean show_counts, string name, ClassValue owner
Function func, string message, boolean show_counts, string name, boolean is_unused_default
) {
exists(int required | required = argument_count(func, name, owner) |
exists(int required, int correction |
required = argument_count(name) - correction and correction = staticmethod_correction(func)
|
/* actual_non_default <= actual */
if required > func.maxParameters()
then message = "Too few parameters" and show_counts = true
if required > func.getMaxPositionalArguments()
then message = "Too few parameters" and show_counts = true and is_unused_default = false
else
if required < func.minParameters()
then message = "Too many parameters" and show_counts = true
if required < func.getMinPositionalArguments()
then message = "Too many parameters" and show_counts = true and is_unused_default = false
else (
func.minParameters() < required and
not func.getScope().hasVarArg() and
message = (required - func.minParameters()) + " default values(s) will never be used" and
show_counts = false
func.getMinPositionalArguments() < required and
not func.hasVarArg() and
message =
(required - func.getMinPositionalArguments()) + " default values(s) will never be used" and
show_counts = false and
is_unused_default = true
)
)
}
predicate incorrect_pow(FunctionValue func, string message, boolean show_counts, ClassValue owner) {
owner.declaredAttribute("__pow__") = func and
(
func.maxParameters() < 2 and message = "Too few parameters" and show_counts = true
predicate incorrect_pow(
Function func, string message, boolean show_counts, boolean is_unused_default
) {
exists(int correction | correction = staticmethod_correction(func) |
func.getMaxPositionalArguments() < 2 - correction and
message = "Too few parameters" and
show_counts = true and
is_unused_default = false
or
func.minParameters() > 3 and message = "Too many parameters" and show_counts = true
func.getMinPositionalArguments() > 3 - correction and
message = "Too many parameters" and
show_counts = true and
is_unused_default = false
or
func.minParameters() < 2 and
message = (2 - func.minParameters()) + " default value(s) will never be used" and
show_counts = false
func.getMinPositionalArguments() < 2 - correction and
message = (2 - func.getMinPositionalArguments()) + " default value(s) will never be used" and
show_counts = false and
is_unused_default = true
or
func.minParameters() = 3 and
func.getMinPositionalArguments() = 3 - correction and
message = "Third parameter to __pow__ should have a default value" and
show_counts = false
show_counts = false and
is_unused_default = false
)
}
predicate incorrect_get(FunctionValue func, string message, boolean show_counts, ClassValue owner) {
owner.declaredAttribute("__get__") = func and
(
func.maxParameters() < 3 and message = "Too few parameters" and show_counts = true
predicate incorrect_round(
Function func, string message, boolean show_counts, boolean is_unused_default
) {
exists(int correction | correction = staticmethod_correction(func) |
func.getMaxPositionalArguments() < 1 - correction and
message = "Too few parameters" and
show_counts = true and
is_unused_default = false
or
func.minParameters() > 3 and message = "Too many parameters" and show_counts = true
func.getMinPositionalArguments() > 2 - correction and
message = "Too many parameters" and
show_counts = true and
is_unused_default = false
or
func.minParameters() < 2 and
not func.getScope().hasVarArg() and
message = (2 - func.minParameters()) + " default value(s) will never be used" and
show_counts = false
func.getMinPositionalArguments() = 2 - correction and
message = "Second parameter to __round__ should have a default value" and
show_counts = false and
is_unused_default = false
)
}
string should_have_parameters(PythonFunctionValue f, string name, ClassValue owner) {
exists(int i | i = argument_count(f, name, owner) | result = i.toString())
or
owner.declaredAttribute(name) = f and
(name = "__get__" or name = "__pow__") and
result = "2 or 3"
predicate incorrect_get(
Function func, string message, boolean show_counts, boolean is_unused_default
) {
exists(int correction | correction = staticmethod_correction(func) |
func.getMaxPositionalArguments() < 3 - correction and
message = "Too few parameters" and
show_counts = true and
is_unused_default = false
or
func.getMinPositionalArguments() > 3 - correction and
message = "Too many parameters" and
show_counts = true and
is_unused_default = false
or
func.getMinPositionalArguments() < 2 - correction and
not func.hasVarArg() and
message = (2 - func.getMinPositionalArguments()) + " default value(s) will never be used" and
show_counts = false and
is_unused_default = true
)
}
string has_parameters(PythonFunctionValue f) {
exists(int i | i = f.minParameters() |
string should_have_parameters(string name) {
if name in ["__pow__", "__get__"]
then result = "2 or 3"
else result = argument_count(name).toString()
}
string has_parameters(Function f) {
exists(int i | i = f.getMinPositionalArguments() |
i = 0 and result = "no parameters"
or
i = 1 and result = "1 parameter"
@@ -121,23 +172,44 @@ string has_parameters(PythonFunctionValue f) {
)
}
from
PythonFunctionValue f, string message, string sizes, boolean show_counts, string name,
ClassValue owner
where
/** Holds if `f` is likely to be a placeholder, and hence not interesting enough to report. */
predicate isLikelyPlaceholderFunction(Function f) {
// Body has only a single statement.
f.getBody().getItem(0) = f.getBody().getLastItem() and
(
incorrect_special_method_defn(f, message, show_counts, name, owner)
// Body is a string literal. This is a common pattern for Zope interfaces.
f.getBody().getLastItem().(ExprStmt).getValue() instanceof StringLiteral
or
incorrect_pow(f, message, show_counts, owner) and name = "__pow__"
// Body just raises an exception.
f.getBody().getLastItem() instanceof Raise
or
incorrect_get(f, message, show_counts, owner) and name = "__get__"
// Body is a pass statement.
f.getBody().getLastItem() instanceof Pass
)
}
from
Function f, string message, string sizes, boolean show_counts, string name, Class owner,
boolean show_unused_defaults
where
owner.getAMethod() = f and
f.getName() = name and
(
incorrect_special_method_defn(f, message, show_counts, name, show_unused_defaults)
or
incorrect_pow(f, message, show_counts, show_unused_defaults) and name = "__pow__"
or
incorrect_get(f, message, show_counts, show_unused_defaults) and name = "__get__"
or
incorrect_round(f, message, show_counts, show_unused_defaults) and
name = "__round__"
) and
not isLikelyPlaceholderFunction(f) and
show_unused_defaults = false and
(
show_counts = false and sizes = ""
or
show_counts = true and
sizes =
", which has " + has_parameters(f) + ", but should have " +
should_have_parameters(f, name, owner)
sizes = ", which has " + has_parameters(f) + ", but should have " + should_have_parameters(name)
)
select f, message + " for special method " + name + sizes + ", in class $@.", owner, owner.getName()

View File

@@ -0,0 +1,108 @@
/**
* @name Type metrics
* @description Counts of various kinds of type annotations in Python code.
* @kind table
* @id py/type-metrics
*/
import python
class BuiltinType extends Name {
BuiltinType() { this.getId() in ["int", "float", "str", "bool", "bytes", "None"] }
}
newtype TAnnotatable =
TAnnotatedFunction(FunctionExpr f) { exists(f.getReturns()) } or
TAnnotatedParameter(Parameter p) { exists(p.getAnnotation()) } or
TAnnotatedAssignment(AnnAssign a) { exists(a.getAnnotation()) }
abstract class Annotatable extends TAnnotatable {
string toString() { result = "Annotatable" }
abstract Expr getAnnotation();
}
class AnnotatedFunction extends TAnnotatedFunction, Annotatable {
FunctionExpr function;
AnnotatedFunction() { this = TAnnotatedFunction(function) }
override Expr getAnnotation() { result = function.getReturns() }
}
class AnnotatedParameter extends TAnnotatedParameter, Annotatable {
Parameter parameter;
AnnotatedParameter() { this = TAnnotatedParameter(parameter) }
override Expr getAnnotation() { result = parameter.getAnnotation() }
}
class AnnotatedAssignment extends TAnnotatedAssignment, Annotatable {
AnnAssign assignment;
AnnotatedAssignment() { this = TAnnotatedAssignment(assignment) }
override Expr getAnnotation() { result = assignment.getAnnotation() }
}
/** Holds if `e` is a forward declaration of a type. */
predicate is_forward_declaration(Expr e) { e instanceof StringLiteral }
/** Holds if `e` is a type that may be difficult to analyze. */
predicate is_complex_type(Expr e) {
e instanceof Subscript and not is_optional_type(e)
or
e instanceof Tuple
or
e instanceof List
}
/** Holds if `e` is a type of the form `Optional[...]`. */
predicate is_optional_type(Subscript e) { e.getObject().(Name).getId() = "Optional" }
/** Holds if `e` is a simple type, that is either an identifier (excluding built-in types) or an attribute of a simple type. */
predicate is_simple_type(Expr e) {
e instanceof Name and not e instanceof BuiltinType
or
is_simple_type(e.(Attribute).getObject())
}
/** Holds if `e` is a built-in type. */
predicate is_builtin_type(Expr e) { e instanceof BuiltinType }
predicate type_count(
string kind, int total, int built_in_count, int forward_declaration_count, int simple_type_count,
int complex_type_count, int optional_type_count
) {
kind = "Parameter annotation" and
total = count(AnnotatedParameter p) and
built_in_count = count(AnnotatedParameter p | is_builtin_type(p.getAnnotation())) and
forward_declaration_count =
count(AnnotatedParameter p | is_forward_declaration(p.getAnnotation())) and
simple_type_count = count(AnnotatedParameter p | is_simple_type(p.getAnnotation())) and
complex_type_count = count(AnnotatedParameter p | is_complex_type(p.getAnnotation())) and
optional_type_count = count(AnnotatedParameter p | is_optional_type(p.getAnnotation()))
or
kind = "Return type annotation" and
total = count(AnnotatedFunction f) and
built_in_count = count(AnnotatedFunction f | is_builtin_type(f.getAnnotation())) and
forward_declaration_count = count(AnnotatedFunction f | is_forward_declaration(f.getAnnotation())) and
simple_type_count = count(AnnotatedFunction f | is_simple_type(f.getAnnotation())) and
complex_type_count = count(AnnotatedFunction f | is_complex_type(f.getAnnotation())) and
optional_type_count = count(AnnotatedFunction f | is_optional_type(f.getAnnotation()))
or
kind = "Annotated assignment" and
total = count(AnnotatedAssignment a) and
built_in_count = count(AnnotatedAssignment a | is_builtin_type(a.getAnnotation())) and
forward_declaration_count =
count(AnnotatedAssignment a | is_forward_declaration(a.getAnnotation())) and
simple_type_count = count(AnnotatedAssignment a | is_simple_type(a.getAnnotation())) and
complex_type_count = count(AnnotatedAssignment a | is_complex_type(a.getAnnotation())) and
optional_type_count = count(AnnotatedAssignment a | is_optional_type(a.getAnnotation()))
}
from
string message, int total, int built_in, int forward_decl, int simple, int complex, int optional
where type_count(message, total, built_in, forward_decl, simple, complex, optional)
select message, total, built_in, forward_decl, simple, complex, optional

View File

@@ -1,15 +0,0 @@
f = open("filename")
... # Actions to perform on file
f.close()
# File only closed if actions are completed successfully
with open("filename") as f:
...# Actions to perform on file
# File always closed
f = open("filename")
try:
... # Actions to perform on file
finally:
f.close()
# File always closed

View File

@@ -4,32 +4,30 @@
<qhelp>
<overview>
<p> If a file is opened then it should always be closed again, even if an
exception is raised.
Failing to ensure that all files are closed may result in failure due to too
many open files.</p>
<p>When a file is opened, it should always be closed.
</p>
<p>A file opened for writing that is not closed when the application exits may result in data loss, where not all of the data written may be saved to the file.
A file opened for reading or writing that is not closed may also use up file descriptors, which is a resource leak that in long running applications could lead to a failure to open additional files.
</p>
</overview>
<recommendation>
<p>Ensure that if you open a file it is always closed on exiting the method.
Wrap the code between the <code>open()</code> and <code>close()</code>
functions in a <code>with</code> statement or use a <code>try...finally</code>
statement. Using a <code>with</code> statement is preferred as it is shorter
and more readable.</p>
<p>Ensure that opened files are always closed, including when an exception could be raised.
The best practice is often to use a <code>with</code> statement to automatically clean up resources.
Otherwise, ensure that <code>.close()</code> is called in a <code>try...except</code> or <code>try...finally</code>
block to handle any possible exceptions.
</p>
</recommendation>
<example>
<p>The following code shows examples of different ways of closing a file. In the first example, the
file is closed only if the method is exited successfully. In the other examples, the file is always
closed on exiting the method.</p>
<p>In the following examples, in the case marked BAD, the file may not be closed if an exception is raised. In the cases marked GOOD, the file is always closed.</p>
<sample src="FileNotAlwaysClosed.py" />
<sample src="examples/FileNotAlwaysClosed.py" />
</example>
<references>
<li>Python Documentation: <a href="https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files">Reading and writing files</a>.</li>
<li>Python Language Reference: <a href="http://docs.python.org/reference/compound_stmts.html#the-with-statement">The with statement</a>,
<a href="http://docs.python.org/reference/compound_stmts.html#the-try-statement">The try statement</a>.</li>
<li>Python PEP 343: <a href="http://www.python.org/dev/peps/pep-0343">The "with" Statement</a>.</li>

View File

@@ -1,74 +1,26 @@
/**
* @name File is not always closed
* @description Opening a file without ensuring that it is always closed may cause resource leaks.
* @description Opening a file without ensuring that it is always closed may lead to data loss or resource leaks.
* @kind problem
* @tags efficiency
* correctness
* resources
* quality
* external/cwe/cwe-772
* @problem.severity warning
* @sub-severity high
* @precision medium
* @precision high
* @id py/file-not-closed
*/
import python
import FileOpen
import FileNotAlwaysClosedQuery
/**
* Whether resource is opened and closed in in a matched pair of methods,
* either `__enter__` and `__exit__` or `__init__` and `__del__`
*/
predicate opened_in_enter_closed_in_exit(ControlFlowNode open) {
file_not_closed_at_scope_exit(open) and
exists(FunctionValue entry, FunctionValue exit |
open.getScope() = entry.getScope() and
exists(ClassValue cls |
cls.declaredAttribute("__enter__") = entry and cls.declaredAttribute("__exit__") = exit
or
cls.declaredAttribute("__init__") = entry and cls.declaredAttribute("__del__") = exit
) and
exists(AttrNode attr_open, AttrNode attrclose |
attr_open.getScope() = entry.getScope() and
attrclose.getScope() = exit.getScope() and
expr_is_open(attr_open.(DefinitionNode).getValue(), open) and
attr_open.getName() = attrclose.getName() and
close_method_call(_, attrclose)
)
)
}
predicate file_not_closed_at_scope_exit(ControlFlowNode open) {
exists(EssaVariable v |
BaseFlow::reaches_exit(v) and
var_is_open(v, open) and
not file_is_returned(v, open)
)
or
call_to_open(open) and
not exists(AssignmentDefinition def | def.getValue() = open) and
not exists(Return r | r.getValue() = open.getNode())
}
predicate file_not_closed_at_exception_exit(ControlFlowNode open, ControlFlowNode exit) {
exists(EssaVariable v |
exit.(RaisingNode).viableExceptionalExit(_, _) and
not closes_arg(exit, v.getSourceVariable()) and
not close_method_call(exit, v.getAUse().(NameNode)) and
var_is_open(v, open) and
v.getAUse() = exit.getAChild*()
)
}
/* Check to see if a file is opened but not closed or returned */
from ControlFlowNode defn, string message
from FileOpen fo, string msg
where
not opened_in_enter_closed_in_exit(defn) and
(
file_not_closed_at_scope_exit(defn) and message = "File is opened but is not closed."
or
not file_not_closed_at_scope_exit(defn) and
file_not_closed_at_exception_exit(defn, _) and
message = "File may not be closed if an exception is raised."
)
select defn.getNode(), message
fileNotClosed(fo) and
msg = "File is opened but is not closed."
or
fileMayNotBeClosedOnException(fo, _) and
msg = "File may not be closed if an exception is raised."
select fo, msg

View File

@@ -0,0 +1,165 @@
/** Definitions for reasoning about whether files are closed. */
import python
import semmle.python.dataflow.new.internal.DataFlowDispatch
import semmle.python.ApiGraphs
/** A CFG node where a file is opened. */
abstract class FileOpenSource extends DataFlow::CfgNode { }
/** A call to the builtin `open` or `os.open`. */
class FileOpenCall extends FileOpenSource {
FileOpenCall() {
this = [API::builtin("open").getACall(), API::moduleImport("os").getMember("open").getACall()]
}
}
private DataFlow::TypeTrackingNode fileOpenInstance(DataFlow::TypeTracker t) {
t.start() and
result instanceof FileOpenSource
or
exists(DataFlow::TypeTracker t2 | result = fileOpenInstance(t2).track(t2, t))
}
/**
* A call that returns an instance of an open file object.
* This includes calls to methods that transitively call `open` or similar.
*/
class FileOpen extends DataFlow::CallCfgNode {
FileOpen() { fileOpenInstance(DataFlow::TypeTracker::end()).flowsTo(this) }
}
/** A call that may wrap a file object in a wrapper class or `os.fdopen`. */
class FileWrapperCall extends DataFlow::CallCfgNode {
DataFlow::Node wrapped;
FileWrapperCall() {
wrapped = this.getArg(_).getALocalSource() and
this.getFunction() = classTracker(_)
or
wrapped = this.getArg(0) and
this = API::moduleImport("os").getMember("fdopen").getACall()
or
wrapped = this.getArg(0) and
this = API::moduleImport("django").getMember("http").getMember("FileResponse").getACall()
}
/** Gets the file that this call wraps. */
DataFlow::Node getWrapped() { result = wrapped }
}
/** A node where a file is closed. */
abstract class FileClose extends DataFlow::CfgNode {
/** Holds if this file close will occur if an exception is thrown at `raises`. */
predicate guardsExceptions(DataFlow::CfgNode raises) {
cfgGetASuccessorStar(raises.asCfgNode().getAnExceptionalSuccessor(), this.asCfgNode())
or
// The expression is after the close call.
// This also covers the body of a `with` statement.
cfgGetASuccessorStar(this.asCfgNode(), raises.asCfgNode())
}
}
private predicate cfgGetASuccessor(ControlFlowNode src, ControlFlowNode sink) {
sink = src.getASuccessor()
}
pragma[inline]
private predicate cfgGetASuccessorPlus(ControlFlowNode src, ControlFlowNode sink) =
fastTC(cfgGetASuccessor/2)(src, sink)
pragma[inline]
private predicate cfgGetASuccessorStar(ControlFlowNode src, ControlFlowNode sink) {
src = sink
or
cfgGetASuccessorPlus(src, sink)
}
/** A call to the `.close()` method of a file object. */
class FileCloseCall extends FileClose {
FileCloseCall() { exists(DataFlow::MethodCallNode mc | mc.calls(this, "close")) }
}
/** A call to `os.close`. */
class OsCloseCall extends FileClose {
OsCloseCall() { this = API::moduleImport("os").getMember("close").getACall().getArg(0) }
}
/** A `with` statement. */
class WithStatement extends FileClose {
WithStatement() { this.asExpr() = any(With w).getContextExpr() }
}
/** Holds if an exception may be raised at `raises` if `file` is a file object. */
private predicate mayRaiseWithFile(DataFlow::CfgNode file, DataFlow::CfgNode raises) {
// Currently just consider any method called on `file`; e.g. `file.write()`; as potentially raising an exception
raises.(DataFlow::MethodCallNode).getObject() = file and
not file instanceof FileOpen and
not file instanceof FileClose
}
/** Holds if data flows from `nodeFrom` to `nodeTo` in one step that also includes file wrapper classes. */
private predicate fileAdditionalLocalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
exists(FileWrapperCall fw | nodeFrom = fw.getWrapped() and nodeTo = fw)
}
private predicate fileLocalFlowHelper0(
DataFlow::LocalSourceNode nodeFrom, DataFlow::LocalSourceNode nodeTo
) {
exists(DataFlow::Node nodeMid |
nodeFrom.flowsTo(nodeMid) and fileAdditionalLocalFlowStep(nodeMid, nodeTo)
)
}
private predicate fileLocalFlowHelper1(
DataFlow::LocalSourceNode nodeFrom, DataFlow::LocalSourceNode nodeTo
) {
fileLocalFlowHelper0*(nodeFrom, nodeTo)
}
/** Holds if data flows from `source` to `sink`, including file wrapper classes. */
pragma[inline]
private predicate fileLocalFlow(FileOpen source, DataFlow::Node sink) {
exists(DataFlow::LocalSourceNode mid | fileLocalFlowHelper1(source, mid) and mid.flowsTo(sink))
}
/** Holds if the file opened at `fo` is closed. */
predicate fileIsClosed(FileOpen fo) { exists(FileClose fc | fileLocalFlow(fo, fc)) }
/** Holds if the file opened at `fo` is returned to the caller. This makes the caller responsible for closing the file. */
predicate fileIsReturned(FileOpen fo) {
exists(Return ret, Expr retVal |
(
retVal = ret.getValue()
or
retVal = ret.getValue().(List).getAnElt()
or
retVal = ret.getValue().(Tuple).getAnElt()
) and
fileLocalFlow(fo, DataFlow::exprNode(retVal))
)
}
/** Holds if the file opened at `fo` is stored in a field. We assume that another method is then responsible for closing the file. */
predicate fileIsStoredInField(FileOpen fo) {
exists(DataFlow::AttrWrite aw | fileLocalFlow(fo, aw.getValue()))
}
/** Holds if the file opened at `fo` is not closed, and is expected to be closed. */
predicate fileNotClosed(FileOpen fo) {
not fileIsClosed(fo) and
not fileIsReturned(fo) and
not fileIsStoredInField(fo)
}
predicate fileMayNotBeClosedOnException(FileOpen fo, DataFlow::Node raises) {
fileIsClosed(fo) and
exists(DataFlow::CfgNode fileRaised |
mayRaiseWithFile(fileRaised, raises) and
fileLocalFlow(fo, fileRaised) and
not exists(FileClose fc |
fileLocalFlow(fo, fc) and
fc.guardsExceptions(raises)
)
)
}

View File

@@ -1,4 +1,8 @@
/** Contains predicates concerning when and where files are opened and closed. */
/**
* DEPRECATED: Use FileNotAlwaysClosedQuery instead.
* Contains predicates concerning when and where files are opened and closed.
*/
deprecated module;
import python
import semmle.python.pointsto.Filters

View File

@@ -0,0 +1,17 @@
def bad():
f = open("filename", "w")
f.write("could raise exception") # BAD: This call could raise an exception, leading to the file not being closed.
f.close()
def good1():
with open("filename", "w") as f:
f.write("always closed") # GOOD: The `with` statement ensures the file is always closed.
def good2():
f = open("filename", "w")
try:
f.write("always closed")
finally:
f.close() # GOOD: The `finally` block always ensures the file is closed.

View File

@@ -171,6 +171,10 @@ private module UntrustedDataToExternalApiConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
predicate isSink(DataFlow::Node sink) { sink instanceof ExternalApiDataNode }
predicate observeDiffInformedIncrementalMode() {
none() // Not used for PR analysis
}
}
/** Global taint-tracking from `RemoteFlowSource`s to `ExternalApiDataNode`s. */

View File

@@ -110,6 +110,10 @@ module InsecureContextConfiguration implements DataFlow::StateConfigSig {
)
)
}
predicate observeDiffInformedIncrementalMode() {
none() // Too complicated, but might be possible after some refactoring.
}
}
private module InsecureContextFlow = DataFlow::GlobalWithState<InsecureContextConfiguration>;

View File

@@ -23,7 +23,7 @@ from
where
PolynomialReDoSFlow::flowPath(source, sink) and
sinkNode = sink.getNode() and
regexp.getRootTerm() = sinkNode.getRegExp()
regexp = sinkNode.getABacktrackingTerm()
// not (
// source.getNode().(Source).getKind() = "url" and
// regexp.isAtEndLine()

View File

@@ -119,6 +119,8 @@ private module HardcodedCredentialsConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof HardcodedValueSource }
predicate isSink(DataFlow::Node sink) { sink instanceof CredentialSink }
predicate observeDiffInformedIncrementalMode() { any() }
}
module HardcodedCredentialsFlow = TaintTracking::Global<HardcodedCredentialsConfig>;

View File

@@ -5,8 +5,11 @@
<recommendation>
<p>To guard against SSRF attacks you should avoid putting user-provided input directly
into a request URL. Instead, either maintain a list of authorized URLs on the server and choose
from that list based on the input provided, or perform proper validation of the input.
into a request URL. On the application level, maintain a list of authorized URLs on the server and choose
from that list based on the input provided. If that is not possible, one should verify the IP address for all user-controlled
requests to ensure they are not private. This requires saving the verified IP address of each domain,
then utilizing a custom HTTP adapter to ensure that future requests to that domain use the verified IP address.
On the network level, you can segment the vulnerable application into its own LAN or block access to specific devices.
</p>
</recommendation>

View File

@@ -1,18 +0,0 @@
#Make a list of functions to increment their arguments by 0 to 9.
def make_incrementers():
result = []
for i in range(10):
def incrementer(x):
return x + i
result.append(incrementer)
return result
#This will fail
def test():
incs = make_incrementers()
for x in range(10):
for y in range(10):
assert incs[x](y) == x+y
test()

View File

@@ -1,60 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Nested functions are a useful feature of Python as it allows a function to access the variables of its enclosing function.
However, the programmer needs to be aware that when an inner function accesses a variable in an outer scope,
it is the variable that is captured, not the value of that variable.
</p>
<p>
Therefore, care must be taken when the captured variable is a loop variable, since it is the loop <em>variable</em> and
<em>not</em> the <em>value</em> of that variable that is captured.
This will mean that by the time that the inner function executes,
the loop variable will have its final value, not the value when the inner function was created.
</p>
</overview>
<recommendation>
<p>
The simplest way to fix this problem is to add a local variable of the same name as the outer variable and initialize that
using the outer variable as a default.
<code>
for var in seq:
...
def inner_func(arg):
...
use(var)
</code>
becomes
<code>
for var in seq:
...
def inner_func(arg, var=var):
...
use(var)
</code>
</p>
</recommendation>
<example>
<p>
In this example, a list of functions is created which should each increment its argument by its index in the list.
However, since <code>i</code> will be 9 when the functions execute, they will each increment their argument by 9.
</p>
<sample src="LoopVariableCapture.py" />
<p>
This can be fixed by adding the default value as shown below. The default value is computed when the function is created, so the desired effect is achieved.
</p>
<sample src="LoopVariableCapture2.py" />
</example>
<references>
<li>The Hitchhikers Guide to Python: <a href="http://docs.python-guide.org/en/latest/writing/gotchas/#late-binding-closures">Late Binding Closures</a></li>
<li>Python Language Reference: <a href="https://docs.python.org/reference/executionmodel.html#naming-and-binding">Naming and binding</a></li>
</references>
</qhelp>

View File

@@ -1,47 +0,0 @@
/**
* @name Loop variable capture
* @description Capture of a loop variable is not the same as capturing the value of a loop variable, and may be erroneous.
* @kind problem
* @tags correctness
* @problem.severity error
* @sub-severity low
* @precision high
* @id py/loop-variable-capture
*/
import python
// Gets the scope of the iteration variable of the looping scope
Scope iteration_variable_scope(AstNode loop) {
result = loop.(For).getScope()
or
result = loop.(Comp).getFunction()
}
predicate capturing_looping_construct(CallableExpr capturing, AstNode loop, Variable var) {
var.getScope() = iteration_variable_scope(loop) and
var.getAnAccess().getScope() = capturing.getInnerScope() and
capturing.getParentNode+() = loop and
(
loop.(For).getTarget() = var.getAnAccess()
or
var = loop.(Comp).getAnIterationVariable()
)
}
predicate escaping_capturing_looping_construct(CallableExpr capturing, AstNode loop, Variable var) {
capturing_looping_construct(capturing, loop, var) and
// Escapes if used out side of for loop or is a lambda in a comprehension
(
loop instanceof For and
exists(Expr e | e.pointsTo(_, _, capturing) | not loop.contains(e))
or
loop.(Comp).getElt() = capturing
or
loop.(Comp).getElt().(Tuple).getAnElt() = capturing
)
}
from CallableExpr capturing, AstNode loop, Variable var
where escaping_capturing_looping_construct(capturing, loop, var)
select capturing, "Capture of loop variable $@.", loop, var.getId()

View File

@@ -0,0 +1,47 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
In Python, a nested function or lambda expression that captures a variable from its surrounding scope is a <em>late-binding</em> closure,
meaning that the value of the variable is determined when the closure is called, not when it is created.
</p>
<p>
Care must be taken when the captured variable is a loop variable. If the closure is called after the loop ends, it will use the value of the variable on the last iteration of the loop, rather than the value at the iteration at which it was created.
</p>
</overview>
<recommendation>
<p>
Ensure that closures that capture loop variables aren't used outside of a single iteration of the loop.
To capture the value of a loop variable at the time the closure is created, use a default parameter, or <code>functools.partial</code>.
</p>
</recommendation>
<example>
<p>
In the following (BAD) example, a <code>tasks</code> list is created, but each task captures the loop variable <code>i</code>, and reads the same value when run.
</p>
<sample src="examples/bad.py" />
<p>
In the following (GOOD) example, each closure has an <code>i</code> default parameter, shadowing the outer <code>i</code> variable, the default value of which is determined as the value of the loop variable <code>i</code> at the time the closure is created.
</p>
<sample src="examples/good.py" />
<p>
In the following (GOOD) example, <code>functools.partial</code> is used to partially evaluate the lambda expression with the value of <code>i</code>.
</p>
<sample src="examples/good2.py" />
</example>
<references>
<li>The Hitchhiker's Guide to Python: <a href="http://docs.python-guide.org/en/latest/writing/gotchas/#late-binding-closures">Late Binding Closures</a>.</li>
<li>Python Language Reference: <a href="https://docs.python.org/reference/executionmodel.html#naming-and-binding">Naming and binding</a>.</li>
<li>Stack Overflow: <a href="https://stackoverflow.com/questions/3431676/creating-functions-or-lambdas-in-a-loop-or-comprehension">Creating functions (or lambdas) in a loop (or comprehension)</a>.</li>
<li>Python Language Reference: <a href="https://docs.python.org/3/library/functools.html#functools.partial">functools.partial</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,25 @@
/**
* @name Loop variable capture
* @description Capturing a loop variable is not the same as capturing its value, and can lead to unexpected behavior or bugs.
* @kind path-problem
* @tags correctness
* quality
* @problem.severity error
* @sub-severity low
* @precision high
* @id py/loop-variable-capture
*/
import python
import LoopVariableCaptureQuery
import EscapingCaptureFlow::PathGraph
from
CallableExpr capturing, AstNode loop, Variable var, string descr,
EscapingCaptureFlow::PathNode source, EscapingCaptureFlow::PathNode sink
where
escapingCapture(capturing, loop, var, source, sink) and
if capturing instanceof Lambda then descr = "lambda" else descr = "function"
select capturing, source, sink,
"This " + descr + " captures the loop variable $@, and may escape the loop by being stored at $@.",
loop, var.getId(), sink, "this location"

View File

@@ -0,0 +1,81 @@
/** Definitions for reasoning about loop variable capture issues. */
import python
import semmle.python.dataflow.new.DataFlow
/** A looping construct. */
abstract class Loop extends AstNode {
/**
* Gets a loop variable of this loop.
* For example, `x` and `y` in `for x,y in pairs: print(x+y)`
*/
abstract Variable getALoopVariable();
}
/** A `for` loop. */
private class ForLoop extends Loop, For {
override Variable getALoopVariable() {
this.getTarget() = result.getAnAccess().getParentNode*() and
result.getScope() = this.getScope()
}
}
/** Holds if the callable `capturing` captures the variable `var` from the loop `loop`. */
predicate capturesLoopVariable(CallableExpr capturing, Loop loop, Variable var) {
var.getAnAccess().getScope() = capturing.getInnerScope() and
capturing.getParentNode+() = loop and
var = loop.getALoopVariable()
}
/** Dataflow configuration for reasoning about callables that capture a loop variable and then may escape from the loop. */
module EscapingCaptureFlowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node node) { capturesLoopVariable(node.asExpr(), _, _) }
predicate isSink(DataFlow::Node node) {
// Stored in a dict/list.
exists(Assign assign, Subscript sub |
sub = assign.getATarget() and node.asExpr() = assign.getValue()
)
or
// Stored in a list.
exists(DataFlow::MethodCallNode mc | mc.calls(_, "append") and node = mc.getArg(0))
or
// Used in a yield statement, likely included in a collection.
// The element of comprehension expressions desugar to involve a yield statement internally.
exists(Yield y | node.asExpr() = y.getValue())
// Checks for storing in a field leads to false positives, so are omitted.
}
predicate isBarrierOut(DataFlow::Node node) { isSink(node) }
predicate isBarrier(DataFlow::Node node) {
// Incorrect virtual dispatch to __call__ methods is a source of FPs.
exists(Function call |
call.getName() = "__call__" and
call.getArg(0) = node.(DataFlow::ParameterNode).getParameter()
)
}
predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet cs) {
isSink(node) and
(
cs.(DataFlow::TupleElementContent).getIndex() in [0 .. 10] or
cs instanceof DataFlow::ListElementContent or
cs instanceof DataFlow::SetElementContent or
cs instanceof DataFlow::DictionaryElementAnyContent
)
}
}
/** Dataflow for reasoning about callables that capture a loop variable and then escape from the loop. */
module EscapingCaptureFlow = DataFlow::Global<EscapingCaptureFlowConfig>;
/** Holds if `capturing` is a callable that captures the variable `var` of the loop `loop`, and then may escape the loop via a flow path from `source` to `sink`. */
predicate escapingCapture(
CallableExpr capturing, Loop loop, Variable var, EscapingCaptureFlow::PathNode source,
EscapingCaptureFlow::PathNode sink
) {
capturesLoopVariable(capturing, loop, var) and
capturing = source.getNode().asExpr() and
EscapingCaptureFlow::flowPath(source, sink)
}

View File

@@ -0,0 +1,8 @@
# BAD: The loop variable `i` is captured.
tasks = []
for i in range(5):
tasks.append(lambda: print(i))
# This will print `4,4,4,4,4`, rather than `0,1,2,3,4` as likely intended.
for t in tasks:
t()

View File

@@ -0,0 +1,8 @@
# GOOD: A default parameter is used, so the variable `i` is not being captured.
tasks = []
for i in range(5):
tasks.append(lambda i=i: print(i))
# This will print `0,1,2,3,4``.
for t in tasks:
t()

View File

@@ -0,0 +1,9 @@
import functools
# GOOD: `functools.partial` takes care of capturing the _value_ of `i`.
tasks = []
for i in range(5):
tasks.append(functools.partial(lambda i: print(i), i))
# This will print `0,1,2,3,4``.
for t in tasks:
t()

View File

@@ -34,6 +34,14 @@ predicate complex_all(Module m) {
)
}
predicate used_in_forward_declaration(Name used, Module mod) {
exists(StringLiteral s, Annotation annotation |
s.getS() = used.getId() and
s.getEnclosingModule() = mod and
annotation.getASubExpression*() = s
)
}
predicate unused_global(Name unused, GlobalVariable v) {
not exists(ImportingStmt is | is.contains(unused)) and
forex(DefinitionNode defn | defn.getNode() = unused |
@@ -55,7 +63,8 @@ predicate unused_global(Name unused, GlobalVariable v) {
unused.defines(v) and
not name_acceptable_for_unused_variable(v) and
not complex_all(unused.getEnclosingModule())
)
) and
not used_in_forward_declaration(unused, unused.getEnclosingModule())
}
from Name unused, GlobalVariable v

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,9 @@
## 1.4.6
### Minor Analysis Improvements
- The `py/special-method-wrong-signature` has been modernized and rewritten to no longer rely on outdated APIs. Moreover, the query no longer flags cases where a default value is never used, as these alerts were rarely useful.
### Bug Fixes
- The `py/unused-global-variable` now no longer flags variables that are only used in forward references (e.g. the `Foo` in `def bar(x: "Foo"): ...`).

View File

@@ -0,0 +1,5 @@
## 1.4.7
### Minor Analysis Improvements
- The `py/mixed-tuple-returns` query no longer flags instances where the tuple is passed into the function as an argument, as this led to too many false positives.

View File

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

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 1.4.0
lastReleaseVersion: 1.4.8

View File

@@ -0,0 +1,7 @@
- queries: .
- include:
id:
- py/not-named-self
- py/not-named-cls
- py/file-not-closed
- py/special-method-wrong-signature

View File

@@ -109,6 +109,8 @@ private module TarSlipImprovConfig implements DataFlow::ConfigSig {
nodeFrom = nodeTo.(API::CallNode).getArg(0) and
nodeFrom = tarfileOpen().getReturn().getAValueReachableFromSource()
}
predicate observeDiffInformedIncrementalMode() { any() }
}
/** Global taint-tracking for detecting more "TarSlip" vulnerabilities. */

Some files were not shown because too many files have changed in this diff Show More