QL: More cleanup

This commit is contained in:
Tom Hvitved
2021-05-26 13:24:32 +02:00
parent f9f8dfb619
commit 68e9f9657b
31 changed files with 104 additions and 327 deletions

View File

@@ -27,7 +27,6 @@ echo "Build the QL extractor"
# clone the git dependencies using "git clone" because cargo's builtin git support is rather slow
REPO_DIR="${CARGO_HOME:-/home/vscode/.cargo}/git/db"
REPO_DIR_ERB="${REPO_DIR}/tree-sitter-embedded-template-4c796e3340c233b6"
REPO_DIR_QL="${REPO_DIR}/tree-sitter-ql-52319df96d97078e"
mkdir -p "${REPO_DIR}"

View File

@@ -1,5 +1,5 @@
name: codeql-ruby-consistency-queries
name: codeql-ql-consistency-queries
version: 0.0.0
libraryPathDependencies:
- codeql-ruby
extractor: ruby
- codeql-ql
extractor: ql

View File

@@ -1,3 +1,3 @@
name: codeql-ruby-examples
name: codeql-ql-examples
version: 0.0.0
libraryPathDependencies: codeql-ruby
libraryPathDependencies: codeql-ql

View File

@@ -1,4 +1,4 @@
- description: Standard Code Scanning queries for Ruby
- qlpack: codeql-ruby
- description: Standard Code Scanning queries for QL
- qlpack: codeql-ql
- apply: code-scanning-selectors.yml
from: codeql-suite-helpers

View File

@@ -1,5 +1,5 @@
- description: Standard LGTM queries for Ruby, including ones not displayed by default
- qlpack: codeql-ruby
- description: Standard LGTM queries for QL, including ones not displayed by default
- qlpack: codeql-ql
- apply: lgtm-selectors.yml
from: codeql-suite-helpers
# These are only for IDE use.

View File

@@ -1,4 +1,4 @@
- description: Standard LGTM queries for Ruby
- apply: codeql-suites/ruby-lgtm-full.qls
- description: Standard LGTM queries for QL
- apply: codeql-suites/ql-lgtm-full.qls
- apply: lgtm-displayed-only.yml
from: codeql-suite-helpers

View File

@@ -1,4 +1,4 @@
- description: Security-and-quality queries for Ruby
- qlpack: codeql-ruby
- description: Security-and-quality queries for QL
- qlpack: codeql-ql
- apply: security-and-quality-selectors.yml
from: codeql-suite-helpers

View File

@@ -1,4 +1,4 @@
- description: Security-extended queries for Ruby
- qlpack: codeql-ruby
- description: Security-extended queries for QL
- qlpack: codeql-ql
- apply: security-extended-selectors.yml
from: codeql-suite-helpers

View File

@@ -178,7 +178,9 @@ class File extends Container, @file {
token = this.getAToken() and
l = token.getLocation() and
line in [l.getStartLine() .. l.getEndLine()] and
if token instanceof @token_block_comment then comment = true else comment = false
if token instanceof @token_block_comment or token instanceof @token_line_comment
then comment = true
else comment = false
)
}

View File

@@ -0,0 +1,52 @@
private import codeql.Locations
/** A diagnostic emitted during extraction, such as a parse error */
class Diagnostic extends @diagnostic {
int severity;
string tag;
string message;
string fullMessage;
Location location;
Diagnostic() { diagnostics(this, severity, tag, message, fullMessage, location) }
/**
* Gets the numerical severity level associated with this diagnostic.
*/
int getSeverity() { result = severity }
/** Gets a string representation of the severity of this diagnostic. */
string getSeverityText() {
severity = 10 and result = "Debug"
or
severity = 20 and result = "Info"
or
severity = 30 and result = "Warning"
or
severity = 40 and result = "Error"
}
/** Gets the error code associated with this diagnostic, e.g. parse_error. */
string getTag() { result = tag }
/**
* Gets the error message text associated with this diagnostic.
*/
string getMessage() { result = message }
/**
* Gets the full error message text associated with this diagnostic.
*/
string getFullMessage() { result = fullMessage }
/** Gets the source location of this diagnostic. */
Location getLocation() { result = location }
/** Gets a textual representation of this diagnostic. */
string toString() { result = this.getMessage() }
}
/** A diagnostic relating to a particular error in extracting a file. */
class ExtractionError extends Diagnostic, @diagnostic_error {
ExtractionError() { this.getTag() = "parse_error" }
}

View File

@@ -3,7 +3,7 @@
* @description Generates use-definition pairs that provide the data
* for jump-to-definition in the code viewer.
* @kind definitions
* @id ruby/ide-jump-to-definition
* @id ql/ide-jump-to-definition
* @tags ide-contextual-queries/local-definitions
*/

View File

@@ -3,7 +3,7 @@
* @description Generates use-definition pairs that provide the data
* for find-references in the code viewer.
* @kind definitions
* @id ruby/ide-find-references
* @id ql/ide-find-references
* @tags ide-contextual-queries/local-references
*/

View File

@@ -2,7 +2,7 @@
* @name Print AST
* @description Produces a representation of a file's Abstract Syntax Tree.
* This query is used by the VS Code extension.
* @id ruby/print-ast
* @id ql/print-ast
* @kind graph
* @tags ide-contextual-queries/print-ast
*/

View File

@@ -1 +1,2 @@
import codeql.files.FileSystem
import codeql_ql.ast.internal.TreeSitter

View File

@@ -1,5 +1,5 @@
name: codeql-ruby
name: codeql-ql
version: 0.0.0
dbscheme: ql.dbscheme
suites: codeql-suites
extractor: ruby
extractor: ql

View File

@@ -2,10 +2,10 @@
* @name Extraction errors
* @description List all extraction errors for files in the source code directory.
* @kind diagnostic
* @id rb/diagnostics/extraction-errors
* @id ql/diagnostics/extraction-errors
*/
import ruby
import ql
import codeql_ql.Diagnostics
/** Gets the SARIF severity to associate an error. */

View File

@@ -3,10 +3,10 @@
* @description Lists all files in the source code directory that were extracted
* without encountering an error in the file.
* @kind diagnostic
* @id rb/diagnostics/successfully-extracted-files
* @id ql/diagnostics/successfully-extracted-files
*/
import ruby
import ql
import codeql_ql.Diagnostics
from File f

View File

@@ -3,10 +3,10 @@
* @kind metric
* @description The number of lines in each file.
* @metricType file
* @id rb/lines-per-file
* @id ql/lines-per-file
*/
import ruby
import ql
from File f, int n
where n = f.getNumberOfLines()

View File

@@ -4,10 +4,10 @@
* @description Measures the number of lines of code in each file, ignoring lines that
* contain only comments or whitespace.
* @metricType file
* @id rb/lines-of-code-in-files
* @id ql/lines-of-code-in-files
*/
import ruby
import ql
from File f, int n
where n = f.getNumberOfLinesOfCode()

View File

@@ -3,10 +3,10 @@
* @kind metric
* @description Measures the number of lines of comments in each file.
* @metricType file
* @id rb/lines-of-comments-in-files
* @id ql/lines-of-comments-in-files
*/
import ruby
import ql
from File f, int n
where n = f.getNumberOfLinesOfComments()

View File

@@ -1,65 +0,0 @@
/**
* @name Use detect
* @description Use 'detect' instead of 'select' followed by 'first' or 'last'.
* @kind problem
* @problem.severity warning
* @id rb/use-detect
* @tags performance rubocop
* @precision high
*
* This is an implementation of Rubocop rule
* https://github.com/rubocop/rubocop-performance/blob/master/lib/rubocop/cop/performance/detect.rb
*/
import ruby
import codeql_ql.dataflow.SSA
/** A call that extracts the first or last element of a list. */
class EndCall extends MethodCall {
string detect;
EndCall() {
detect = "detect" and
(
this.getMethodName() = "first" and
this.getNumberOfArguments() = 0
or
this.getNumberOfArguments() = 1 and
this.getArgument(0).(IntegerLiteral).getValueText() = "0"
)
or
detect = "reverse_detect" and
(
this.getMethodName() = "last" and
this.getNumberOfArguments() = 0
or
this.getNumberOfArguments() = 1 and
this.getArgument(0).(UnaryMinusExpr).getOperand().(IntegerLiteral).getValueText() = "1"
)
}
string detectCall() { result = detect }
}
Expr getUniqueRead(Expr e) {
exists(AssignExpr ae |
e = ae.getRightOperand() and
forex(Ssa::WriteDefinition def | def.getWriteAccess() = ae.getLeftOperand() |
strictcount(def.getARead()) = 1 and
not def = any(Ssa::PhiNode phi).getAnInput() and
def.getARead() = result.getAControlFlowNode()
)
)
}
class SelectBlock extends MethodCall {
SelectBlock() {
this.getMethodName() in ["select", "filter", "find_all"] and
exists(this.getBlock())
}
}
from EndCall call, SelectBlock selectBlock
where getUniqueRead*(selectBlock) = call.getReceiver()
select call, "Replace this call and $@ with '" + call.detectCall() + "'.", selectBlock,
"'select' call"

View File

@@ -1,26 +0,0 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>
When creating a file, POSIX systems allow permissions to be specified
for owner, group and others separately. Permissions should be kept as
strict as possible, preventing access to the files contents by other users.
</p>
</overview>
<recommendation>
<p>
Restrict the file permissions of files to prevent any but the owner being able to read or write to that file
</p>
</recommendation>
<references>
<li>
Wikipedia:
<a href="https://en.wikipedia.org/wiki/File_system_permissions">File system permissions</a>.
</li>
</references>
</qhelp>

View File

@@ -1,99 +0,0 @@
/**
* @name Overly permissive file permissions
* @description Allowing files to be readable or writable by users other than the owner may allow sensitive information to be accessed.
* @kind path-problem
* @problem.severity warning
* @id rb/overly-permissive-file
* @tags external/cwe/cwe-732
* security
* @precision low
*/
import ruby
import codeql_ql.DataFlow
import DataFlow::PathGraph
private import codeql_ql.dataflow.SSA
// TODO: account for flows through tuple assignments
/** An expression referencing the File or FileUtils module */
class FileModuleAccess extends Expr {
FileModuleAccess() {
this.(ConstantAccess).getName() = "File"
or
this.(ConstantAccess).getName() = "FileUtils"
or
exists(FileModuleAccess fma, Ssa::WriteDefinition def |
def.getARead() = this.getAControlFlowNode() and
def.getWriteAccess().getParent().(Assignment).getRightOperand() = fma
)
}
}
bindingset[p]
int world_permission(int p) { result = p.bitAnd(7) }
// 70 oct = 56 dec
bindingset[p]
int group_permission(int p) { result = p.bitAnd(56) }
bindingset[p]
string access(int p) {
p.bitAnd(2) != 0 and result = "writable"
or
p.bitAnd(4) != 0 and result = "readable"
}
/** An expression specifing a file permission that allows group/others read or write access */
class PermissivePermissionsExpr extends Expr {
// TODO: non-literal expressions?
PermissivePermissionsExpr() {
exists(int perm, string acc |
perm = this.(IntegerLiteral).getValue() and
(acc = access(world_permission(perm)) or acc = access(group_permission(perm)))
)
or
// adding/setting read or write permissions for all/group/other
this.(StringLiteral).getValueText().regexpMatch(".*[ago][^-=+]*[+=][xXst]*[rw].*")
}
}
/** A call to a method of File or FileUtils that may modify file permissions */
class PermissionSettingMethodCall extends MethodCall {
private string methodName;
private Expr permArg;
PermissionSettingMethodCall() {
this.getReceiver() instanceof FileModuleAccess and
this.getMethodName() = methodName and
(
methodName in ["chmod", "chmod_R", "lchmod"] and permArg = this.getArgument(0)
or
methodName = "mkfifo" and permArg = this.getArgument(1)
or
methodName in ["new", "open"] and permArg = this.getArgument(2)
or
methodName in ["install", "makedirs", "mkdir", "mkdir_p", "mkpath"] and
permArg = this.getKeywordArgument("mode")
// TODO: defaults for optional args? This may depend on the umask
)
}
Expr getPermissionArgument() { result = permArg }
}
class PermissivePermissionsConfig extends DataFlow::Configuration {
PermissivePermissionsConfig() { this = "PermissivePermissionsConfig" }
override predicate isSource(DataFlow::Node source) {
exists(PermissivePermissionsExpr ppe | source.asExpr().getExpr() = ppe)
}
override predicate isSink(DataFlow::Node sink) {
exists(PermissionSettingMethodCall c | sink.asExpr().getExpr() = c.getPermissionArgument())
}
}
from DataFlow::PathNode source, DataFlow::PathNode sink, PermissivePermissionsConfig conf
where conf.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Overly permissive mask sets file to $@.", source.getNode(),
source.getNode().toString()

View File

@@ -1,7 +1,7 @@
/**
* @id rb/summary/lines-of-code
* @name Total lines of Ruby code in the database
* @description The total number of lines of Ruby code from the source code
* @id ql/summary/lines-of-code
* @name Total lines of QL code in the database
* @description The total number of lines of QL code from the source code
* directory, including external libraries and auto-generated files. This is a
* useful metric of the size of a database. This query counts the lines of
* code, excluding whitespace or comments.
@@ -10,6 +10,6 @@
* lines-of-code
*/
import ruby
import ql
select sum(File f | exists(f.getRelativePath()) | f.getNumberOfLinesOfCode())

View File

@@ -1,14 +1,14 @@
/**
* @id rb/summary/lines-of-user-code
* @name Total Lines of user written Ruby code in the database
* @description The total number of lines of Ruby code from the source code
* @id ql/summary/lines-of-user-code
* @name Total Lines of user written QL code in the database
* @description The total number of lines of QL code from the source code
* directory, excluding external library and auto-generated files. This
* query counts the lines of code, excluding whitespace or comments.
* @kind metric
* @tags summary
*/
import ruby
import ql
select sum(File f |
f.fromSource() and

View File

@@ -1,13 +1,13 @@
/**
* @id rb/summary/number-of-files-extracted-with-errors
* @id ql/summary/number-of-files-extracted-with-errors
* @name Total number of files that were extracted with errors
* @description The total number of Ruby code files that we extracted, but where
* @description The total number of QL code files that we extracted, but where
* at least one extraction error occurred in the process.
* @kind metric
* @tags summary
*/
import ruby
import ql
import codeql_ql.Diagnostics
select count(File f |

View File

@@ -1,13 +1,13 @@
/**
* @id rb/summary/number-of-successfully-extracted-files
* @id ql/summary/number-of-successfully-extracted-files
* @name Total number of files that were extracted without error
* @description The total number of Ruby code files that we extracted without
* @description The total number of QL code files that we extracted without
* encountering any extraction errors
* @kind metric
* @tags summary
*/
import ruby
import ql
import codeql_ql.Diagnostics
select count(File f |

View File

@@ -1,28 +0,0 @@
/**
* @name Useless assignment to local variable
* @description An assignment to a local variable that is not used later on, or whose value is always
* overwritten, has no effect.
* @kind problem
* @problem.severity warning
* @id rb/useless-assignment-to-local
* @tags maintainability
* external/cwe/cwe-563
* @precision low
*/
import ruby
import codeql_ql.dataflow.SSA
class RelevantLocalVariableWriteAccess extends LocalVariableWriteAccess {
RelevantLocalVariableWriteAccess() {
not this.getVariable().getName().charAt(0) = "_" and
not this = any(Parameter p).getAVariable().getDefiningAccess()
}
}
from RelevantLocalVariableWriteAccess write, LocalVariable v
where
v = write.getVariable() and
exists(write.getAControlFlowNode()) and
not exists(Ssa::WriteDefinition def | def.getWriteAccess() = write)
select write, "This assignment to $@ is useless, since its value is never read.", v, v.getName()

View File

@@ -1,32 +0,0 @@
/**
* @name Potentially uninitialized local variable
* @description Using a local variable before it is initialized gives the variable a default
* 'nil' value.
* @kind problem
* @problem.severity error
* @id rb/uninitialized-local-variable
* @tags reliability
* correctness
* @precision low
*/
import ruby
import codeql_ql.dataflow.SSA
class RelevantLocalVariableReadAccess extends LocalVariableReadAccess {
RelevantLocalVariableReadAccess() {
not exists(MethodCall c |
c.getReceiver() = this and
c.getMethodName() = "nil?"
)
}
}
from RelevantLocalVariableReadAccess read, LocalVariable v
where
v = read.getVariable() and
exists(Ssa::Definition def |
def.getAnUltimateDefinition() instanceof Ssa::UninitializedDefinition and
read = def.getARead().getExpr()
)
select read, "Local variable $@ may be used before it is initialized.", v, v.getName()

View File

@@ -1,27 +0,0 @@
/**
* @name Unused parameter.
* @description A parameter that is not used later on, or whose value is always overwritten,
* can be removed.
* @kind problem
* @problem.severity warning
* @id rb/unused-parameter
* @tags maintainability
* external/cwe/cwe-563
* @precision low
*/
import ruby
import codeql_ql.dataflow.SSA
class RelevantParameterVariable extends LocalVariable {
RelevantParameterVariable() {
exists(Parameter p |
this = p.getAVariable() and
not this.getName().charAt(0) = "_"
)
}
}
from RelevantParameterVariable v
where not exists(Ssa::WriteDefinition def | def.getWriteAccess() = v.getDefiningAccess())
select v, "Unused parameter."

View File

@@ -1,7 +1,7 @@
name: codeql-ruby-tests
name: codeql-ql-tests
version: 0.0.0
libraryPathDependencies:
- codeql-ruby
- codeql-ruby-examples
extractor: ruby
- codeql-ql
- codeql-ql-examples
extractor: ql
tests: .