Merge branch 'main' into post-release-prep/codeql-cli-2.25.2

This commit is contained in:
Henry Mercer
2026-04-14 13:56:52 +01:00
committed by GitHub
229 changed files with 73508 additions and 65433 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
class BoolParent extends @py_bool_parent {
string toString() { result = "BoolParent" }
}
// Drop py_bools rows for Import and ImportStar parents,
// since the old schema does not include them in @py_bool_parent.
from BoolParent parent, int idx
where
py_bools(parent, idx) and
not parent instanceof @py_Import and
not parent instanceof @py_ImportStar
select parent, idx

View File

@@ -0,0 +1,3 @@
description: Remove is_lazy field from Import and ImportStar (downgrade PEP 810 lazy imports)
compatibility: backwards
py_bools.rel: run py_bools.qlo

View File

@@ -845,17 +845,19 @@ class If(stmt):
class Import(stmt):
__slots__ = "names",
__slots__ = "is_lazy", "names",
def __init__(self, names):
def __init__(self, names, is_lazy=False):
self.names = names
self.is_lazy = is_lazy
class ImportFrom(stmt):
__slots__ = "module",
__slots__ = "is_lazy", "module",
def __init__(self, module):
def __init__(self, module, is_lazy=False):
self.module = module
self.is_lazy = is_lazy
class Nonlocal(stmt):

View File

@@ -334,9 +334,11 @@ IfExp.field('body', expr, 'if-true expression')
IfExp.field('orelse', expr, 'if-false expression')
Import.field('names', alias_list, 'alias list')
Import.field('is_lazy', bool_, 'lazy')
ImportFrom.set_name('ImportStar')
ImportFrom.field('module', expr)
ImportFrom.field('is_lazy', bool_, 'lazy')
ImportMember.field('module', expr)
ImportMember.field('name', string)

View File

@@ -72,8 +72,8 @@ class AstDumper(object):
# just not print it in that case.
if field == "parenthesised" and value is None:
continue
# Likewise, the default value for `is_async` is `False`, so we don't need to print it.
if field == "is_async" and value is False:
# Likewise, the default value for `is_async` and `is_lazy` is `False`, so we don't need to print it.
if field in ("is_async", "is_lazy") and value is False:
continue
output.write("{} {}:".format(indent,field))
if isinstance(value, list):

View File

@@ -291,7 +291,7 @@ def create_placeholder_args(cls):
if cls in (ast.Raise, ast.Ellipsis):
return {}
fields = ast_fields[cls]
args = {field: None for field in fields if field != "is_async"}
args = {field: None for field in fields if field not in ("is_async", "is_lazy")}
for field in list_fields.get(cls, ()):
args[field] = []
if cls in (ast.GeneratorExp, ast.ListComp, ast.SetComp, ast.DictComp):

View File

@@ -211,6 +211,8 @@ HEADER = '''/**
* WARNING: Any modifications to this file will be lost.
* Relations can be changed by modifying master.py.
*/
overlay[local]
module;
'''
def main():

View File

@@ -0,0 +1,284 @@
Module: [2, 0] - [35, 0]
body: [
Import: [2, 0] - [2, 13]
is_lazy: True
names: [
alias: [2, 12] - [2, 13]
value:
ImportExpr: [2, 12] - [2, 13]
level: 0
name: 'a'
top: True
asname:
Name: [2, 12] - [2, 13]
variable: Variable('a', None)
ctx: Store
]
Import: [4, 0] - [4, 18]
is_lazy: True
names: [
alias: [4, 12] - [4, 14]
value:
ImportExpr: [4, 12] - [4, 14]
level: 0
name: 'b1'
top: True
asname:
Name: [4, 12] - [4, 14]
variable: Variable('b1', None)
ctx: Store
alias: [4, 16] - [4, 18]
value:
ImportExpr: [4, 16] - [4, 18]
level: 0
name: 'b2'
top: True
asname:
Name: [4, 16] - [4, 18]
variable: Variable('b2', None)
ctx: Store
]
Import: [6, 0] - [6, 20]
is_lazy: True
names: [
alias: [6, 12] - [6, 20]
value:
ImportExpr: [6, 12] - [6, 20]
level: 0
name: 'c1.c2.c3'
top: True
asname:
Name: [6, 12] - [6, 20]
variable: Variable('c1', None)
ctx: Store
]
Import: [8, 0] - [8, 23]
is_lazy: True
names: [
alias: [8, 12] - [8, 23]
value:
ImportExpr: [8, 12] - [8, 17]
level: 0
name: 'd1.d2'
top: False
asname:
Name: [8, 21] - [8, 23]
variable: Variable('d3', None)
ctx: Store
]
Import: [10, 0] - [10, 20]
is_lazy: True
names: [
alias: [10, 19] - [10, 20]
value:
ImportMember: [10, 19] - [10, 20]
module:
ImportExpr: [10, 10] - [10, 11]
level: 0
name: 'e'
top: False
name: 'f'
asname:
Name: [10, 19] - [10, 20]
variable: Variable('f', None)
ctx: Store
]
Import: [12, 0] - [12, 29]
is_lazy: True
names: [
alias: [12, 23] - [12, 25]
value:
ImportMember: [12, 23] - [12, 25]
module:
ImportExpr: [12, 10] - [12, 15]
level: 0
name: 'g1.g2'
top: False
name: 'h1'
asname:
Name: [12, 23] - [12, 25]
variable: Variable('h1', None)
ctx: Store
alias: [12, 27] - [12, 29]
value:
ImportMember: [12, 27] - [12, 29]
module:
ImportExpr: [12, 10] - [12, 15]
level: 0
name: 'g1.g2'
top: False
name: 'h2'
asname:
Name: [12, 27] - [12, 29]
variable: Variable('h2', None)
ctx: Store
]
Import: [14, 0] - [14, 32]
is_lazy: True
names: [
alias: [14, 20] - [14, 28]
value:
ImportMember: [14, 20] - [14, 28]
module:
ImportExpr: [14, 10] - [14, 12]
level: 0
name: 'i1'
top: False
name: 'j1'
asname:
Name: [14, 26] - [14, 28]
variable: Variable('j2', None)
ctx: Store
alias: [14, 30] - [14, 32]
value:
ImportMember: [14, 30] - [14, 32]
module:
ImportExpr: [14, 10] - [14, 12]
level: 0
name: 'i1'
top: False
name: 'j3'
asname:
Name: [14, 30] - [14, 32]
variable: Variable('j3', None)
ctx: Store
]
Import: [16, 0] - [16, 37]
is_lazy: True
names: [
alias: [16, 25] - [16, 33]
value:
ImportMember: [16, 25] - [16, 33]
module:
ImportExpr: [16, 10] - [16, 17]
level: 2
name: 'k1.k2'
top: False
name: 'l1'
asname:
Name: [16, 31] - [16, 33]
variable: Variable('l2', None)
ctx: Store
alias: [16, 35] - [16, 37]
value:
ImportMember: [16, 35] - [16, 37]
module:
ImportExpr: [16, 10] - [16, 17]
level: 2
name: 'k1.k2'
top: False
name: 'l3'
asname:
Name: [16, 35] - [16, 37]
variable: Variable('l3', None)
ctx: Store
]
Import: [18, 0] - [18, 20]
is_lazy: True
names: [
alias: [18, 19] - [18, 20]
value:
ImportMember: [18, 19] - [18, 20]
module:
ImportExpr: [18, 10] - [18, 11]
level: 1
name: None
top: False
name: 'm'
asname:
Name: [18, 19] - [18, 20]
variable: Variable('m', None)
ctx: Store
]
Import: [20, 0] - [20, 22]
is_lazy: True
names: [
alias: [20, 21] - [20, 22]
value:
ImportMember: [20, 21] - [20, 22]
module:
ImportExpr: [20, 10] - [20, 13]
level: 3
name: None
top: False
name: 'n'
asname:
Name: [20, 21] - [20, 22]
variable: Variable('n', None)
ctx: Store
]
ImportFrom: [22, 0] - [22, 20]
is_lazy: True
module:
ImportExpr: [22, 10] - [22, 11]
level: 0
name: 'o'
top: False
Assign: [26, 0] - [26, 8]
targets: [
Name: [26, 0] - [26, 4]
variable: Variable('lazy', None)
ctx: Store
]
value:
Num: [26, 7] - [26, 8]
n: 1
text: '1'
Assign: [28, 0] - [28, 11]
targets: [
Subscript: [28, 0] - [28, 7]
value:
Name: [28, 0] - [28, 4]
variable: Variable('lazy', None)
ctx: Load
index:
Num: [28, 5] - [28, 6]
n: 2
text: '2'
ctx: Store
]
value:
Num: [28, 10] - [28, 11]
n: 3
text: '3'
Assign: [30, 0] - [30, 12]
targets: [
Attribute: [30, 0] - [30, 8]
value:
Name: [30, 0] - [30, 4]
variable: Variable('lazy', None)
ctx: Load
attr: 'foo'
ctx: Store
]
value:
Num: [30, 11] - [30, 12]
n: 4
text: '4'
Expr: [32, 0] - [32, 6]
value:
Call: [32, 0] - [32, 6]
func:
Name: [32, 0] - [32, 4]
variable: Variable('lazy', None)
ctx: Load
positional_args: []
named_args: []
AnnAssign: [34, 0] - [34, 14]
value: None
annotation:
Name: [34, 10] - [34, 14]
variable: Variable('case', None)
ctx: Load
target:
Subscript: [34, 0] - [34, 7]
value:
Name: [34, 0] - [34, 4]
variable: Variable('lazy', None)
ctx: Load
index:
Num: [34, 5] - [34, 6]
n: 5
text: '5'
ctx: Store
]

View File

@@ -0,0 +1,34 @@
# Basic lazy imports (PEP 810)
lazy import a
lazy import b1, b2
lazy import c1.c2.c3
lazy import d1.d2 as d3
lazy from e import f
lazy from g1.g2 import h1, h2
lazy from i1 import j1 as j2, j3
lazy from ..k1.k2 import l1 as l2, l3
lazy from . import m
lazy from ... import n
lazy from o import *
# `lazy` used as a regular identifier (soft keyword behavior)
lazy = 1
lazy[2] = 3
lazy.foo = 4
lazy()
lazy[5] : case

View File

@@ -1777,6 +1777,13 @@
attr (@importfrom.importexpr) level = level
}
; Set is_lazy for lazy import statements (PEP 810)
[
(import_statement is_lazy: _)
(import_from_statement is_lazy: _)
] @lazy_import
{ attr (@lazy_import.node) is_lazy = #true }
;;;;;; End of Import (`from ... import ...`)
;;;;;; Raise (`raise ...`)

View File

@@ -109,6 +109,7 @@ module.exports = grammar({
),
import_statement: $ => seq(
optional(field('is_lazy', 'lazy')),
'import',
$._import_list
),
@@ -131,6 +132,7 @@ module.exports = grammar({
),
import_from_statement: $ => seq(
optional(field('is_lazy', 'lazy')),
'from',
field('module_name', choice(
$.relative_import,
@@ -1228,6 +1230,7 @@ module.exports = grammar({
'await',
'match',
'type',
'lazy',
),
$.identifier
)),

View File

@@ -144,6 +144,22 @@
"import_statement": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "is_lazy",
"content": {
"type": "STRING",
"value": "lazy"
}
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": "import"
@@ -232,6 +248,22 @@
"import_from_statement": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "is_lazy",
"content": {
"type": "STRING",
"value": "lazy"
}
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": "from"
@@ -6721,6 +6753,10 @@
{
"type": "STRING",
"value": "type"
},
{
"type": "STRING",
"value": "lazy"
}
]
},

View File

@@ -1811,6 +1811,16 @@
"type": "import_from_statement",
"named": true,
"fields": {
"is_lazy": {
"multiple": false,
"required": false,
"types": [
{
"type": "lazy",
"named": false
}
]
},
"module_name": {
"multiple": false,
"required": true,
@@ -1860,6 +1870,16 @@
"type": "import_statement",
"named": true,
"fields": {
"is_lazy": {
"multiple": false,
"required": false,
"types": [
{
"type": "lazy",
"named": false
}
]
},
"name": {
"multiple": true,
"required": true,
@@ -4154,6 +4174,10 @@
"type": "lambda",
"named": false
},
{
"type": "lazy",
"named": false
},
{
"type": "match",
"named": false

File diff suppressed because it is too large Load Diff

View File

@@ -60,7 +60,13 @@ extern "C" {
/// Free any memory allocated for this array. Note that this does not free any
/// memory allocated for the array's contents.
#define array_delete(self) _array__delete((self), (void *)(self)->contents, sizeof(*self))
#define array_delete(self) \
do { \
if ((self)->contents) ts_free((self)->contents); \
(self)->contents = NULL; \
(self)->size = 0; \
(self)->capacity = 0; \
} while (0)
/// Push a new `element` onto the end of the array.
#define array_push(self, element) \
@@ -130,12 +136,11 @@ extern "C" {
/// Swap one array with another
#define array_swap(self, other) \
do { \
struct Swap swapped_contents = _array__swap( \
(void *)(self)->contents, &(self)->size, &(self)->capacity, \
(void *)(other)->contents, &(other)->size, &(other)->capacity \
); \
(self)->contents = swapped_contents.self_contents; \
(other)->contents = swapped_contents.other_contents; \
void *_array_swap_tmp = (void *)(self)->contents; \
(self)->contents = (other)->contents; \
(other)->contents = _array_swap_tmp; \
_array__swap(&(self)->size, &(self)->capacity, \
&(other)->size, &(other)->capacity); \
} while (0)
/// Get the size of the array contents
@@ -188,12 +193,6 @@ extern "C" {
// The `Array` type itself was not altered as a solution in order to avoid breakage
// with existing consumers (in particular, parsers with external scanners).
/// This is not what you're looking for, see `array_delete`.
static inline void _array__delete(void *self, void *contents, size_t self_size) {
if (contents) ts_free(contents);
if (self) memset(self, 0, self_size);
}
/// This is not what you're looking for, see `array_erase`.
static inline void _array__erase(void* self_contents, uint32_t *size,
size_t element_size, uint32_t index) {
@@ -228,31 +227,15 @@ static inline void *_array__assign(void* self_contents, uint32_t *self_size, uin
return new_contents;
}
struct Swap {
void *self_contents;
void *other_contents;
};
/// This is not what you're looking for, see `array_swap`.
// static inline void _array__swap(Array *self, Array *other) {
static inline struct Swap _array__swap(void *self_contents, uint32_t *self_size, uint32_t *self_capacity,
void *other_contents, uint32_t *other_size, uint32_t *other_capacity) {
void *new_self_contents = other_contents;
uint32_t new_self_size = *other_size;
uint32_t new_self_capacity = *other_capacity;
void *new_other_contents = self_contents;
*other_size = *self_size;
*other_capacity = *self_capacity;
*self_size = new_self_size;
*self_capacity = new_self_capacity;
struct Swap out = {
.self_contents = new_self_contents,
.other_contents = new_other_contents,
};
return out;
static inline void _array__swap(uint32_t *self_size, uint32_t *self_capacity,
uint32_t *other_size, uint32_t *other_capacity) {
uint32_t tmp_size = *self_size;
uint32_t tmp_capacity = *self_capacity;
*self_size = *other_size;
*self_capacity = *other_capacity;
*other_size = tmp_size;
*other_capacity = tmp_capacity;
}
/// This is not what you're looking for, see `array_push` or `array_grow_by`.

View File

@@ -0,0 +1,5 @@
---
category: minorAnalysis
---
- The Python extractor now supports the new `lazy import ...` and `lazy from ... import ...` (as defined in [PEP-810](https://peps.python.org/pep-0810/)) that will be part of Python 3.15.

View File

@@ -698,6 +698,9 @@ class Import_ extends @py_Import, Stmt {
/** Gets an alias of this import statement. */
Alias getAName() { result = this.getNames().getAnItem() }
/** Whether the lazy property of this import statement is true. */
predicate isLazy() { py_bools(this, 2) }
override string toString() { result = "Import" }
}
@@ -720,6 +723,9 @@ class ImportStar_ extends @py_ImportStar, Stmt {
/** Gets the module of this import * statement. */
Expr getModule() { py_exprs(result, _, this, 1) }
/** Whether the lazy property of this import * statement is true. */
predicate isLazy() { py_bools(this, 2) }
override string toString() { result = "ImportStar" }
}

View File

@@ -0,0 +1,9 @@
extensions:
- addsTo:
pack: codeql/python-all
extensible: typeModel
data:
# See https://eventlet.readthedocs.io/en/latest/patching.html
- ['socket.socket', 'eventlet', 'Member[green].Member[socket].Member[socket].ReturnValue']
# eventlet also re-exports as eventlet.socket for convenience
- ['socket.socket', 'eventlet', 'Member[socket].Member[socket].ReturnValue']

View File

@@ -0,0 +1,7 @@
extensions:
- addsTo:
pack: codeql/python-all
extensible: typeModel
data:
# See https://www.gevent.org/api/gevent.socket.html
- ['socket.socket', 'gevent', 'Member[socket].Member[socket].ReturnValue']

View File

@@ -27,6 +27,8 @@ extensions:
extensible: sinkModel
data:
- ["zipfile.ZipFile","Member[extractall].Argument[0,path:]", "path-injection"]
# See https://docs.python.org/3/library/socket.html#socket.socket.bind
- ["socket.socket", "Member[bind].Argument[0,address:]", "bind-socket-all-interfaces"]
- addsTo:
pack: codeql/python-all
@@ -184,6 +186,8 @@ extensions:
pack: codeql/python-all
extensible: typeModel
data:
# See https://docs.python.org/3/library/socket.html#socket.socket
- ['socket.socket', 'socket', 'Member[socket].ReturnValue']
# See https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urlparse
- ["urllib.parse.ParseResult~Subclass", 'urllib', 'Member[parse].Member[urlparse]']

View File

@@ -10,6 +10,10 @@
* `type, path, kind`
* - Summaries:
* `type, path, input, output, kind`
* - Barriers:
* `type, path, kind`
* - BarrierGuards:
* `type, path, acceptingValue, kind`
* - Types:
* `type1, type2, path`
*
@@ -42,7 +46,8 @@
* 3. The `input` and `output` columns specify how data enters and leaves the element selected by the
* first `(type, path)` tuple. Both strings are `.`-separated access paths
* of the same syntax as the `path` column.
* 4. The `kind` column is a tag that can be referenced from QL to determine to
* 4. The `acceptingValue` column of barrier guard models specifies which branch of the guard is blocking flow. It can be "true" or "false".
* 5. The `kind` column is a tag that can be referenced from QL to determine to
* which classes the interpreted elements should be added. For example, for
* sources `"remote"` indicates a default remote flow source, and for summaries
* `"taint"` indicates a default additional taint step and `"value"` indicates a
@@ -355,11 +360,11 @@ private predicate barrierModel(string type, string path, string kind, string mod
/** Holds if a barrier guard model exists for the given parameters. */
private predicate barrierGuardModel(
string type, string path, string branch, string kind, string model
string type, string path, string acceptingValue, string kind, string model
) {
// No deprecation adapter for barrier models, they were not around back then.
exists(QlBuiltins::ExtensionId madId |
Extensions::barrierGuardModel(type, path, branch, kind, madId) and
Extensions::barrierGuardModel(type, path, acceptingValue, kind, madId) and
model = "MaD:" + madId.toString()
)
}
@@ -783,16 +788,16 @@ module ModelOutput {
}
/**
* Holds if a barrier model contributed `barrier` with the given `kind` for the given `branch`.
* Holds if a barrier model contributed `barrier` with the given `kind` for the given `acceptingValue`.
*/
cached
API::Node getABarrierGuardNode(string kind, boolean branch, string model) {
exists(string type, string path, string branch_str |
branch = true and branch_str = "true"
API::Node getABarrierGuardNode(string kind, boolean acceptingValue, string model) {
exists(string type, string path, string acceptingValue_str |
acceptingValue = true and acceptingValue_str = "true"
or
branch = false and branch_str = "false"
acceptingValue = false and acceptingValue_str = "false"
|
barrierGuardModel(type, path, branch_str, kind, model) and
barrierGuardModel(type, path, acceptingValue_str, kind, model) and
result = getNodeFromPath(type, path)
)
}
@@ -856,12 +861,12 @@ module ModelOutput {
API::Node getABarrierNode(string kind) { result = getABarrierNode(kind, _) }
/**
* Holds if an external model contributed `barrier-guard` with the given `kind` and `branch`.
* Holds if an external model contributed `barrier-guard` with the given `kind` and `acceptingValue`.
*
* INTERNAL: Do not use.
*/
API::Node getABarrierGuardNode(string kind, boolean branch) {
result = getABarrierGuardNode(kind, branch, _)
API::Node getABarrierGuardNode(string kind, boolean acceptingValue) {
result = getABarrierGuardNode(kind, acceptingValue, _)
}
/**

View File

@@ -33,11 +33,11 @@ extensible predicate barrierModel(
* of the given `kind` and `madId` is the data extension row number.
* `path` is assumed to lead to a parameter of a call (possibly `self`), and
* the call is guarding the parameter.
* `branch` is either `true` or `false`, indicating which branch of the guard
* is protecting the parameter.
* `acceptingValue` is either `true` or `false`, indicating which branch of
* the guard is protecting the parameter.
*/
extensible predicate barrierGuardModel(
string type, string path, string branch, string kind, QlBuiltins::ExtensionId madId
string type, string path, string acceptingValue, string kind, QlBuiltins::ExtensionId madId
);
/**

View File

@@ -517,6 +517,7 @@ py_extracted_version(int module : @py_Module ref,
/* <Field> Import.location = 0, location */
/* <Field> Import.names = 1, alias_list */
/* <Field> Import.is_lazy = 2, bool */
/* <Field> ImportExpr.location = 0, location */
/* <Field> ImportExpr.parenthesised = 1, bool */
@@ -526,6 +527,7 @@ py_extracted_version(int module : @py_Module ref,
/* <Field> ImportStar.location = 0, location */
/* <Field> ImportStar.module = 1, expr */
/* <Field> ImportStar.is_lazy = 2, bool */
/* <Field> ImportMember.location = 0, location */
/* <Field> ImportMember.parenthesised = 1, bool */
@@ -1127,7 +1129,7 @@ case @py_unaryop.kind of
@py_ast_node = @py_Class | @py_Function | @py_Module | @py_StringPart | @py_comprehension | @py_dict_item | @py_expr | @py_pattern | @py_stmt | @py_type_parameter;
@py_bool_parent = @py_For | @py_Function | @py_Print | @py_With | @py_expr | @py_pattern;
@py_bool_parent = @py_For | @py_Function | @py_Import | @py_ImportStar | @py_Print | @py_With | @py_expr | @py_pattern;
@py_dict_item_list_parent = @py_Call | @py_ClassExpr | @py_Dict;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
description: Add is_lazy field to Import and ImportStar for PEP 810 lazy imports
compatibility: backwards

View File

@@ -2,7 +2,7 @@
* @name Binding a socket to all network interfaces
* @description Binding a socket to all interfaces opens it up to traffic from any IPv4 address
* and is therefore associated with security risks.
* @kind problem
* @kind path-problem
* @tags security
* external/cwe/cwe-200
* @problem.severity error
@@ -14,7 +14,9 @@
import python
import semmle.python.dataflow.new.DataFlow
import semmle.python.ApiGraphs
import semmle.python.dataflow.new.TaintTracking
private import semmle.python.frameworks.data.ModelsAsData
import BindToAllInterfacesFlow::PathGraph
/** Gets a hostname that can be used to bind to all interfaces. */
private string vulnerableHostname() {
@@ -26,45 +28,26 @@ private string vulnerableHostname() {
]
}
/** Gets a reference to a hostname that can be used to bind to all interfaces. */
private DataFlow::TypeTrackingNode vulnerableHostnameRef(DataFlow::TypeTracker t, string hostname) {
t.start() and
exists(StringLiteral allInterfacesStringLiteral | hostname = vulnerableHostname() |
allInterfacesStringLiteral.getText() = hostname and
result.asExpr() = allInterfacesStringLiteral
)
or
exists(DataFlow::TypeTracker t2 | result = vulnerableHostnameRef(t2, hostname).track(t2, t))
private module BindToAllInterfacesConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source.asExpr().(StringLiteral).getText() = vulnerableHostname()
}
predicate isSink(DataFlow::Node sink) {
ModelOutput::sinkNode(sink, "bind-socket-all-interfaces") and
// Network socket addresses are tuples like (host, port), so we require
// the bind() argument to originate from a tuple expression. This excludes
// AF_UNIX sockets, which pass a plain string path to bind().
any(DataFlow::LocalSourceNode n | n.asExpr() instanceof Tuple).flowsTo(sink)
}
}
/** Gets a reference to a hostname that can be used to bind to all interfaces. */
DataFlow::Node vulnerableHostnameRef(string hostname) {
vulnerableHostnameRef(DataFlow::TypeTracker::end(), hostname).flowsTo(result)
}
private module BindToAllInterfacesFlow = TaintTracking::Global<BindToAllInterfacesConfig>;
/** Gets a reference to a tuple for which the first element is a hostname that can be used to bind to all interfaces. */
private DataFlow::TypeTrackingNode vulnerableAddressTuple(DataFlow::TypeTracker t, string hostname) {
t.start() and
result.asExpr() = any(Tuple tup | tup.getElt(0) = vulnerableHostnameRef(hostname).asExpr())
or
exists(DataFlow::TypeTracker t2 | result = vulnerableAddressTuple(t2, hostname).track(t2, t))
}
private import BindToAllInterfacesFlow
/** Gets a reference to a tuple for which the first element is a hostname that can be used to bind to all interfaces. */
DataFlow::Node vulnerableAddressTuple(string hostname) {
vulnerableAddressTuple(DataFlow::TypeTracker::end(), hostname).flowsTo(result)
}
/**
* Gets an instance of `socket.socket` using _some_ address family.
*
* See https://docs.python.org/3/library/socket.html
*/
API::Node socketInstance() { result = API::moduleImport("socket").getMember("socket").getReturn() }
from DataFlow::CallCfgNode bindCall, DataFlow::Node addressArg, string hostname
where
bindCall = socketInstance().getMember("bind").getACall() and
addressArg = bindCall.getArg(0) and
addressArg = vulnerableAddressTuple(hostname)
select bindCall.asExpr(), "'" + hostname + "' binds a socket to all interfaces."
from PathNode source, PathNode sink
where flowPath(source, sink)
select sink.getNode(), source, sink,
"Binding a socket to all interfaces (using $@) is a security risk.", source.getNode(),
"'" + source.getNode().asExpr().(StringLiteral).getText() + "'"

View File

@@ -12,10 +12,10 @@
*/
import python
private import LegacyPointsTo
private import semmle.python.ApiGraphs
predicate originIsLocals(ControlFlowNodeWithPointsTo n) {
n.pointsTo(_, _, Value::named("locals").getACall())
predicate originIsLocals(ControlFlowNode n) {
API::builtin("locals").getReturn().getAValueReachableFromSource().asCfgNode() = n
}
predicate modification_of_locals(ControlFlowNode f) {
@@ -37,5 +37,8 @@ where
// in module level scope `locals() == globals()`
// see https://docs.python.org/3/library/functions.html#locals
// FP report in https://github.com/github/codeql/issues/6674
not a.getScope() instanceof ModuleScope
not a.getScope() instanceof Module and
// in class level scope `locals()` reflects the class namespace,
// so modifications do take effect.
not a.getScope() instanceof Class
select a, "Modification of the locals() dictionary will have no effect on the local variables."

View File

@@ -13,7 +13,7 @@
*/
import python
private import LegacyPointsTo
private import semmle.python.dataflow.new.internal.DataFlowDispatch
predicate calls_close(Call c) { exists(Attribute a | c.getFunc() = a and a.getName() = "close") }
@@ -23,18 +23,12 @@ predicate only_stmt_in_finally(Try t, Call c) {
)
}
predicate points_to_context_manager(ControlFlowNodeWithPointsTo f, ClassValue cls) {
forex(Value v | f.pointsTo(v) | v.getClass() = cls) and
cls.isContextManager()
}
from Call close, Try t, ClassValue cls
from Call close, Try t, Class cls
where
only_stmt_in_finally(t, close) and
calls_close(close) and
exists(ControlFlowNode f | f = close.getFunc().getAFlowNode().(AttrNode).getObject() |
points_to_context_manager(f, cls)
)
classInstanceTracker(cls).asExpr() = close.getFunc().(Attribute).getObject() and
DuckTyping::isContextManager(cls)
select close,
"Instance of context-manager class $@ is closed in a finally block. Consider using 'with' statement.",
cls, cls.getName()

View File

@@ -13,7 +13,7 @@
*/
import python
private import LegacyPointsTo
private import semmle.python.ApiGraphs
predicate typing_import(ImportingStmt is) {
exists(Module m |
@@ -34,11 +34,7 @@ predicate unique_yield(Stmt s) {
/** Holds if `contextlib.suppress` may be used in the same scope as `s` */
predicate suppression_in_scope(Stmt s) {
exists(With w |
w.getContextExpr()
.(Call)
.getFunc()
.(ExprWithPointsTo)
.pointsTo(Value::named("contextlib.suppress")) and
w.getContextExpr() = API::moduleImport("contextlib").getMember("suppress").getACall().asExpr() and
w.getScope() = s.getScope()
)
}

View File

@@ -12,11 +12,49 @@
*/
import python
private import LegacyPointsTo
private import semmle.python.dataflow.new.internal.DataFlowDispatch
private import semmle.python.dataflow.new.internal.Builtins
private import semmle.python.ApiGraphs
from Call call, ClassValue ex
/**
* Holds if `cls` is a user-defined exception class, i.e. it transitively
* extends one of the builtin exception base classes.
*/
predicate isUserDefinedExceptionClass(Class cls) {
cls.getABase() =
API::builtin(["BaseException", "Exception"]).getAValueReachableFromSource().asExpr()
or
isUserDefinedExceptionClass(getADirectSuperclass(cls))
}
/**
* Gets the name of a builtin exception class.
*/
string getBuiltinExceptionName() {
result = Builtins::getBuiltinName() and
(
result.matches("%Error") or
result.matches("%Exception") or
result.matches("%Warning") or
result =
["GeneratorExit", "KeyboardInterrupt", "StopIteration", "StopAsyncIteration", "SystemExit"]
)
}
/**
* Holds if `call` is an instantiation of an exception class.
*/
predicate isExceptionInstantiation(Call call) {
exists(Class cls |
classTracker(cls).asExpr() = call.getFunc() and
isUserDefinedExceptionClass(cls)
)
or
call.getFunc() = API::builtin(getBuiltinExceptionName()).getAValueReachableFromSource().asExpr()
}
from Call call
where
call.getFunc().(ExprWithPointsTo).pointsTo(ex) and
ex.getASuperType() = ClassValue::exception() and
isExceptionInstantiation(call) and
exists(ExprStmt s | s.getValue() = call)
select call, "Instantiating an exception, but not raising it, has no effect."

View File

@@ -12,10 +12,12 @@
*/
import python
private import LegacyPointsTo
private import semmle.python.ApiGraphs
from CallNode call, string name
where call.getFunction().(ControlFlowNodeWithPointsTo).pointsTo(Value::siteQuitter(name))
where
name = ["exit", "quit"] and
call = API::builtin(name).getACall().asCfgNode()
select call,
"The '" + name +
"' site.Quitter object may not exist if the 'site' module is not loaded or is modified."

View File

@@ -0,0 +1,5 @@
---
category: minorAnalysis
---
- The `py/bind-socket-all-network-interfaces` query now uses the global data-flow library, leading to better precision and more results. Also, wrappers of `socket.socket` in the `eventlet` and `gevent` libraries are now also recognized as socket binding operations.

View File

@@ -0,0 +1,9 @@
| 2 | Import | lazy |
| 3 | Import | lazy |
| 4 | Import | lazy |
| 5 | Import | lazy |
| 6 | Import | lazy |
| 7 | from l import * | lazy |
| 10 | Import | normal |
| 11 | Import | normal |
| 12 | from w import * | normal |

View File

@@ -0,0 +1,12 @@
# Lazy imports (PEP 810)
lazy import a
lazy from b import c
lazy from d import e as f
lazy import g.h as i
lazy from ..j import k
lazy from l import *
# Non-lazy imports
import x
from y import z
from w import *

View File

@@ -0,0 +1,11 @@
import python
string lazy(Stmt s) {
if s.(Import).isLazy() or s.(ImportStar).isLazy() then result = "lazy" else result = "normal"
}
from Stmt s
where
s.getLocation().getFile().getShortName() = "test.py" and
(s instanceof Import or s instanceof ImportStar)
select s.getLocation().getStartLine(), s.toString(), lazy(s)

View File

@@ -1,5 +1,63 @@
| BindToAllInterfaces_test.py:5:1:5:26 | Attribute() | '0.0.0.0' binds a socket to all interfaces. |
| BindToAllInterfaces_test.py:9:1:9:18 | Attribute() | '' binds a socket to all interfaces. |
| BindToAllInterfaces_test.py:17:1:17:26 | Attribute() | '0.0.0.0' binds a socket to all interfaces. |
| BindToAllInterfaces_test.py:21:1:21:11 | Attribute() | '0.0.0.0' binds a socket to all interfaces. |
| BindToAllInterfaces_test.py:26:1:26:20 | Attribute() | '::' binds a socket to all interfaces. |
#select
| BindToAllInterfaces_test.py:5:9:5:24 | ControlFlowNode for Tuple | BindToAllInterfaces_test.py:5:9:5:17 | ControlFlowNode for StringLiteral | BindToAllInterfaces_test.py:5:9:5:24 | ControlFlowNode for Tuple | Binding a socket to all interfaces (using $@) is a security risk. | BindToAllInterfaces_test.py:5:9:5:17 | ControlFlowNode for StringLiteral | '0.0.0.0' |
| BindToAllInterfaces_test.py:9:9:9:16 | ControlFlowNode for Tuple | BindToAllInterfaces_test.py:9:9:9:10 | ControlFlowNode for StringLiteral | BindToAllInterfaces_test.py:9:9:9:16 | ControlFlowNode for Tuple | Binding a socket to all interfaces (using $@) is a security risk. | BindToAllInterfaces_test.py:9:9:9:10 | ControlFlowNode for StringLiteral | '' |
| BindToAllInterfaces_test.py:17:9:17:24 | ControlFlowNode for Tuple | BindToAllInterfaces_test.py:16:14:16:22 | ControlFlowNode for StringLiteral | BindToAllInterfaces_test.py:17:9:17:24 | ControlFlowNode for Tuple | Binding a socket to all interfaces (using $@) is a security risk. | BindToAllInterfaces_test.py:16:14:16:22 | ControlFlowNode for StringLiteral | '0.0.0.0' |
| BindToAllInterfaces_test.py:21:8:21:10 | ControlFlowNode for tup | BindToAllInterfaces_test.py:16:14:16:22 | ControlFlowNode for StringLiteral | BindToAllInterfaces_test.py:21:8:21:10 | ControlFlowNode for tup | Binding a socket to all interfaces (using $@) is a security risk. | BindToAllInterfaces_test.py:16:14:16:22 | ControlFlowNode for StringLiteral | '0.0.0.0' |
| BindToAllInterfaces_test.py:26:9:26:18 | ControlFlowNode for Tuple | BindToAllInterfaces_test.py:26:9:26:12 | ControlFlowNode for StringLiteral | BindToAllInterfaces_test.py:26:9:26:18 | ControlFlowNode for Tuple | Binding a socket to all interfaces (using $@) is a security risk. | BindToAllInterfaces_test.py:26:9:26:12 | ControlFlowNode for StringLiteral | '::' |
| BindToAllInterfaces_test.py:39:17:39:41 | ControlFlowNode for Tuple | BindToAllInterfaces_test.py:34:26:34:34 | ControlFlowNode for StringLiteral | BindToAllInterfaces_test.py:39:17:39:41 | ControlFlowNode for Tuple | Binding a socket to all interfaces (using $@) is a security risk. | BindToAllInterfaces_test.py:34:26:34:34 | ControlFlowNode for StringLiteral | '0.0.0.0' |
| BindToAllInterfaces_test.py:48:9:48:18 | ControlFlowNode for Tuple | BindToAllInterfaces_test.py:46:35:46:43 | ControlFlowNode for StringLiteral | BindToAllInterfaces_test.py:48:9:48:18 | ControlFlowNode for Tuple | Binding a socket to all interfaces (using $@) is a security risk. | BindToAllInterfaces_test.py:46:35:46:43 | ControlFlowNode for StringLiteral | '0.0.0.0' |
| BindToAllInterfaces_test.py:53:10:53:25 | ControlFlowNode for Tuple | BindToAllInterfaces_test.py:53:10:53:18 | ControlFlowNode for StringLiteral | BindToAllInterfaces_test.py:53:10:53:25 | ControlFlowNode for Tuple | Binding a socket to all interfaces (using $@) is a security risk. | BindToAllInterfaces_test.py:53:10:53:18 | ControlFlowNode for StringLiteral | '0.0.0.0' |
| BindToAllInterfaces_test.py:58:10:58:25 | ControlFlowNode for Tuple | BindToAllInterfaces_test.py:58:10:58:18 | ControlFlowNode for StringLiteral | BindToAllInterfaces_test.py:58:10:58:25 | ControlFlowNode for Tuple | Binding a socket to all interfaces (using $@) is a security risk. | BindToAllInterfaces_test.py:58:10:58:18 | ControlFlowNode for StringLiteral | '0.0.0.0' |
edges
| BindToAllInterfaces_test.py:5:9:5:17 | ControlFlowNode for StringLiteral | BindToAllInterfaces_test.py:5:9:5:24 | ControlFlowNode for Tuple | provenance | Sink:MaD:63 |
| BindToAllInterfaces_test.py:9:9:9:10 | ControlFlowNode for StringLiteral | BindToAllInterfaces_test.py:9:9:9:16 | ControlFlowNode for Tuple | provenance | Sink:MaD:63 |
| BindToAllInterfaces_test.py:16:1:16:10 | ControlFlowNode for ALL_LOCALS | BindToAllInterfaces_test.py:17:9:17:24 | ControlFlowNode for Tuple | provenance | Sink:MaD:63 |
| BindToAllInterfaces_test.py:16:1:16:10 | ControlFlowNode for ALL_LOCALS | BindToAllInterfaces_test.py:20:1:20:3 | ControlFlowNode for tup | provenance | |
| BindToAllInterfaces_test.py:16:14:16:22 | ControlFlowNode for StringLiteral | BindToAllInterfaces_test.py:16:1:16:10 | ControlFlowNode for ALL_LOCALS | provenance | |
| BindToAllInterfaces_test.py:20:1:20:3 | ControlFlowNode for tup | BindToAllInterfaces_test.py:21:8:21:10 | ControlFlowNode for tup | provenance | Sink:MaD:63 |
| BindToAllInterfaces_test.py:26:9:26:12 | ControlFlowNode for StringLiteral | BindToAllInterfaces_test.py:26:9:26:18 | ControlFlowNode for Tuple | provenance | Sink:MaD:63 |
| BindToAllInterfaces_test.py:33:18:33:21 | ControlFlowNode for self [Return] [Attribute bind_addr] | BindToAllInterfaces_test.py:41:10:41:17 | ControlFlowNode for Server() [Attribute bind_addr] | provenance | |
| BindToAllInterfaces_test.py:34:9:34:12 | [post] ControlFlowNode for self [Attribute bind_addr] | BindToAllInterfaces_test.py:33:18:33:21 | ControlFlowNode for self [Return] [Attribute bind_addr] | provenance | |
| BindToAllInterfaces_test.py:34:26:34:34 | ControlFlowNode for StringLiteral | BindToAllInterfaces_test.py:34:9:34:12 | [post] ControlFlowNode for self [Attribute bind_addr] | provenance | |
| BindToAllInterfaces_test.py:37:15:37:18 | ControlFlowNode for self [Attribute bind_addr] | BindToAllInterfaces_test.py:39:17:39:20 | ControlFlowNode for self [Attribute bind_addr] | provenance | |
| BindToAllInterfaces_test.py:39:17:39:20 | ControlFlowNode for self [Attribute bind_addr] | BindToAllInterfaces_test.py:39:17:39:30 | ControlFlowNode for Attribute | provenance | |
| BindToAllInterfaces_test.py:39:17:39:30 | ControlFlowNode for Attribute | BindToAllInterfaces_test.py:39:17:39:41 | ControlFlowNode for Tuple | provenance | Sink:MaD:63 |
| BindToAllInterfaces_test.py:41:1:41:6 | ControlFlowNode for server [Attribute bind_addr] | BindToAllInterfaces_test.py:42:1:42:6 | ControlFlowNode for server [Attribute bind_addr] | provenance | |
| BindToAllInterfaces_test.py:41:10:41:17 | ControlFlowNode for Server() [Attribute bind_addr] | BindToAllInterfaces_test.py:41:1:41:6 | ControlFlowNode for server [Attribute bind_addr] | provenance | |
| BindToAllInterfaces_test.py:42:1:42:6 | ControlFlowNode for server [Attribute bind_addr] | BindToAllInterfaces_test.py:37:15:37:18 | ControlFlowNode for self [Attribute bind_addr] | provenance | |
| BindToAllInterfaces_test.py:46:1:46:4 | ControlFlowNode for host | BindToAllInterfaces_test.py:48:9:48:18 | ControlFlowNode for Tuple | provenance | Sink:MaD:63 |
| BindToAllInterfaces_test.py:46:8:46:44 | ControlFlowNode for Attribute() | BindToAllInterfaces_test.py:46:1:46:4 | ControlFlowNode for host | provenance | |
| BindToAllInterfaces_test.py:46:35:46:43 | ControlFlowNode for StringLiteral | BindToAllInterfaces_test.py:46:8:46:44 | ControlFlowNode for Attribute() | provenance | dict.get |
| BindToAllInterfaces_test.py:53:10:53:18 | ControlFlowNode for StringLiteral | BindToAllInterfaces_test.py:53:10:53:25 | ControlFlowNode for Tuple | provenance | Sink:MaD:63 |
| BindToAllInterfaces_test.py:58:10:58:18 | ControlFlowNode for StringLiteral | BindToAllInterfaces_test.py:58:10:58:25 | ControlFlowNode for Tuple | provenance | Sink:MaD:63 |
nodes
| BindToAllInterfaces_test.py:5:9:5:17 | ControlFlowNode for StringLiteral | semmle.label | ControlFlowNode for StringLiteral |
| BindToAllInterfaces_test.py:5:9:5:24 | ControlFlowNode for Tuple | semmle.label | ControlFlowNode for Tuple |
| BindToAllInterfaces_test.py:9:9:9:10 | ControlFlowNode for StringLiteral | semmle.label | ControlFlowNode for StringLiteral |
| BindToAllInterfaces_test.py:9:9:9:16 | ControlFlowNode for Tuple | semmle.label | ControlFlowNode for Tuple |
| BindToAllInterfaces_test.py:16:1:16:10 | ControlFlowNode for ALL_LOCALS | semmle.label | ControlFlowNode for ALL_LOCALS |
| BindToAllInterfaces_test.py:16:14:16:22 | ControlFlowNode for StringLiteral | semmle.label | ControlFlowNode for StringLiteral |
| BindToAllInterfaces_test.py:17:9:17:24 | ControlFlowNode for Tuple | semmle.label | ControlFlowNode for Tuple |
| BindToAllInterfaces_test.py:20:1:20:3 | ControlFlowNode for tup | semmle.label | ControlFlowNode for tup |
| BindToAllInterfaces_test.py:21:8:21:10 | ControlFlowNode for tup | semmle.label | ControlFlowNode for tup |
| BindToAllInterfaces_test.py:26:9:26:12 | ControlFlowNode for StringLiteral | semmle.label | ControlFlowNode for StringLiteral |
| BindToAllInterfaces_test.py:26:9:26:18 | ControlFlowNode for Tuple | semmle.label | ControlFlowNode for Tuple |
| BindToAllInterfaces_test.py:33:18:33:21 | ControlFlowNode for self [Return] [Attribute bind_addr] | semmle.label | ControlFlowNode for self [Return] [Attribute bind_addr] |
| BindToAllInterfaces_test.py:34:9:34:12 | [post] ControlFlowNode for self [Attribute bind_addr] | semmle.label | [post] ControlFlowNode for self [Attribute bind_addr] |
| BindToAllInterfaces_test.py:34:26:34:34 | ControlFlowNode for StringLiteral | semmle.label | ControlFlowNode for StringLiteral |
| BindToAllInterfaces_test.py:37:15:37:18 | ControlFlowNode for self [Attribute bind_addr] | semmle.label | ControlFlowNode for self [Attribute bind_addr] |
| BindToAllInterfaces_test.py:39:17:39:20 | ControlFlowNode for self [Attribute bind_addr] | semmle.label | ControlFlowNode for self [Attribute bind_addr] |
| BindToAllInterfaces_test.py:39:17:39:30 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| BindToAllInterfaces_test.py:39:17:39:41 | ControlFlowNode for Tuple | semmle.label | ControlFlowNode for Tuple |
| BindToAllInterfaces_test.py:41:1:41:6 | ControlFlowNode for server [Attribute bind_addr] | semmle.label | ControlFlowNode for server [Attribute bind_addr] |
| BindToAllInterfaces_test.py:41:10:41:17 | ControlFlowNode for Server() [Attribute bind_addr] | semmle.label | ControlFlowNode for Server() [Attribute bind_addr] |
| BindToAllInterfaces_test.py:42:1:42:6 | ControlFlowNode for server [Attribute bind_addr] | semmle.label | ControlFlowNode for server [Attribute bind_addr] |
| BindToAllInterfaces_test.py:46:1:46:4 | ControlFlowNode for host | semmle.label | ControlFlowNode for host |
| BindToAllInterfaces_test.py:46:8:46:44 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| BindToAllInterfaces_test.py:46:35:46:43 | ControlFlowNode for StringLiteral | semmle.label | ControlFlowNode for StringLiteral |
| BindToAllInterfaces_test.py:48:9:48:18 | ControlFlowNode for Tuple | semmle.label | ControlFlowNode for Tuple |
| BindToAllInterfaces_test.py:53:10:53:18 | ControlFlowNode for StringLiteral | semmle.label | ControlFlowNode for StringLiteral |
| BindToAllInterfaces_test.py:53:10:53:25 | ControlFlowNode for Tuple | semmle.label | ControlFlowNode for Tuple |
| BindToAllInterfaces_test.py:58:10:58:18 | ControlFlowNode for StringLiteral | semmle.label | ControlFlowNode for StringLiteral |
| BindToAllInterfaces_test.py:58:10:58:25 | ControlFlowNode for Tuple | semmle.label | ControlFlowNode for Tuple |
subpaths

View File

@@ -1 +1,2 @@
Security/CVE-2018-1281/BindToAllInterfaces.ql
query: Security/CVE-2018-1281/BindToAllInterfaces.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -2,25 +2,61 @@ import socket
# binds to all interfaces, insecure
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0', 31137))
s.bind(('0.0.0.0', 31137)) # $ Alert[py/bind-socket-all-network-interfaces]
# binds to all interfaces, insecure
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', 4040))
s.bind(('', 4040)) # $ Alert[py/bind-socket-all-network-interfaces]
# binds only to a dedicated interface, secure
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('84.68.10.12', 8080))
# binds to all interfaces, insecure
ALL_LOCALS = "0.0.0.0"
s.bind((ALL_LOCALS, 9090))
ALL_LOCALS = "0.0.0.0" # $ Source
s.bind((ALL_LOCALS, 9090)) # $ Alert[py/bind-socket-all-network-interfaces]
# binds to all interfaces, insecure
tup = (ALL_LOCALS, 8080)
s.bind(tup)
s.bind(tup) # $ Alert[py/bind-socket-all-network-interfaces]
# IPv6
s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
s.bind(("::", 8080)) # NOT OK
s.bind(("::", 8080)) # $ Alert[py/bind-socket-all-network-interfaces]
# FN cases from https://github.com/github/codeql/issues/21582
# Address stored in a class attribute
class Server:
def __init__(self):
self.bind_addr = '0.0.0.0' # $ Source
self.port = 31137
def start(self):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((self.bind_addr, self.port)) # $ Alert[py/bind-socket-all-network-interfaces]
server = Server()
server.start()
# os.environ.get with insecure default
import os
host = os.environ.get('APP_HOST', '0.0.0.0') # $ Source
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((host, 8080)) # $ Alert[py/bind-socket-all-network-interfaces]
# gevent.socket (alternative socket module)
from gevent import socket as gsocket
gs = gsocket.socket(gsocket.AF_INET, gsocket.SOCK_STREAM)
gs.bind(('0.0.0.0', 31137)) # $ Alert[py/bind-socket-all-network-interfaces]
# eventlet.green.socket (another alternative socket module)
from eventlet.green import socket as esocket
es = esocket.socket(esocket.AF_INET, esocket.SOCK_STREAM)
es.bind(('0.0.0.0', 31137)) # $ Alert[py/bind-socket-all-network-interfaces]
# AF_UNIX socket binding should not be flagged
us = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
us.bind('')

View File

@@ -1 +1 @@
| test.py:168:9:168:17 | Attribute() | Instance of context-manager class $@ is closed in a finally block. Consider using 'with' statement. | test.py:151:1:151:17 | class CM | CM |
| test.py:168:9:168:17 | Attribute() | Instance of context-manager class $@ is closed in a finally block. Consider using 'with' statement. | test.py:151:1:151:17 | Class CM | CM |

View File

@@ -174,3 +174,9 @@ def assert_ok(seq):
# False positive. ODASA-8042. Fixed in PR #2401.
class false_positive:
e = (x for x in [])
# In class-level scope `locals()` reflects the class namespace,
# so modifications do take effect.
class MyClass:
locals()['x'] = 43 # OK
y = x

View File

@@ -0,0 +1,67 @@
#!/bin/bash
#
# Build a local Python extractor pack from source.
#
# Usage with the CodeQL CLI (run from the repository root):
#
# codeql database create <db> -l python -s <src> --search-path .
# codeql test run --search-path . python/ql/test/<test-dir>
#
set -eux
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
platform="linux64"
elif [[ "$OSTYPE" == "darwin"* ]]; then
platform="osx64"
else
echo "Unknown OS"
exit 1
fi
cd "$(dirname "$0")/.."
# Build the tsg-python Rust binary
(cd extractor/tsg-python && cargo build --release)
tsg_bin="extractor/tsg-python/target/release/tsg-python"
# Generate python3src.zip from the Python extractor source.
# make_zips.py creates the zip in the source directory and then copies it to the
# given output directory. We use a temporary directory to avoid a same-file copy
# error, then move the zip back.
tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' EXIT
(cd extractor && python3 make_zips.py "$tmpdir")
cp "$tmpdir/python3src.zip" extractor/python3src.zip
# Assemble the extractor pack
rm -rf extractor-pack
mkdir -p extractor-pack/tools/${platform}
# Root-level metadata and schema files
cp codeql-extractor.yml extractor-pack/
cp ql/lib/semmlecode.python.dbscheme extractor-pack/
cp ql/lib/semmlecode.python.dbscheme.stats extractor-pack/
# Python extractor engine files (into tools/)
cp extractor/python_tracer.py extractor-pack/tools/
cp extractor/index.py extractor-pack/tools/
cp extractor/setup.py extractor-pack/tools/
cp extractor/convert_setup.py extractor-pack/tools/
cp extractor/get_venv_lib.py extractor-pack/tools/
cp extractor/imp.py extractor-pack/tools/
cp extractor/LICENSE-PSF.md extractor-pack/tools/
cp extractor/python3src.zip extractor-pack/tools/
cp -r extractor/data extractor-pack/tools/
# Shell tool scripts (autobuild, pre-finalize, lgtm-scripts)
cp tools/autobuild.sh extractor-pack/tools/
cp tools/autobuild.cmd extractor-pack/tools/
cp tools/pre-finalize.sh extractor-pack/tools/
cp tools/pre-finalize.cmd extractor-pack/tools/
cp -r tools/lgtm-scripts extractor-pack/tools/
# Downgrades
cp -r downgrades extractor-pack/
# Platform-specific Rust binary
cp "${tsg_bin}" extractor-pack/tools/${platform}/tsg-python