mirror of
https://github.com/github/codeql.git
synced 2026-04-17 04:54:02 +02:00
Merge branch 'main' into post-release-prep/codeql-cli-2.25.2
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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():
|
||||
|
||||
284
python/extractor/tests/parser/lazy_imports_new.expected
Normal file
284
python/extractor/tests/parser/lazy_imports_new.expected
Normal 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
|
||||
]
|
||||
34
python/extractor/tests/parser/lazy_imports_new.py
Normal file
34
python/extractor/tests/parser/lazy_imports_new.py
Normal 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
|
||||
@@ -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 ...`)
|
||||
|
||||
@@ -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
|
||||
)),
|
||||
|
||||
36
python/extractor/tsg-python/tsp/src/grammar.json
generated
36
python/extractor/tsg-python/tsp/src/grammar.json
generated
@@ -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"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
24
python/extractor/tsg-python/tsp/src/node-types.json
generated
24
python/extractor/tsg-python/tsp/src/node-types.json
generated
@@ -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
|
||||
|
||||
129879
python/extractor/tsg-python/tsp/src/parser.c
generated
129879
python/extractor/tsg-python/tsp/src/parser.c
generated
File diff suppressed because it is too large
Load Diff
@@ -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`.
|
||||
|
||||
@@ -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.
|
||||
@@ -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" }
|
||||
}
|
||||
|
||||
|
||||
@@ -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']
|
||||
7
python/ql/lib/semmle/python/frameworks/Gevent.model.yml
Normal file
7
python/ql/lib/semmle/python/frameworks/Gevent.model.yml
Normal 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']
|
||||
@@ -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]']
|
||||
|
||||
|
||||
@@ -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, _)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
description: Add is_lazy field to Import and ImportStar for PEP 810 lazy imports
|
||||
compatibility: backwards
|
||||
@@ -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() + "'"
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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.
|
||||
@@ -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 |
|
||||
12
python/ql/test/3/extractor-tests/lazy-imports/test.py
Normal file
12
python/ql/test/3/extractor-tests/lazy-imports/test.py
Normal 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 *
|
||||
11
python/ql/test/3/extractor-tests/lazy-imports/test.ql
Normal file
11
python/ql/test/3/extractor-tests/lazy-imports/test.ql
Normal 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)
|
||||
@@ -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
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
Security/CVE-2018-1281/BindToAllInterfaces.ql
|
||||
query: Security/CVE-2018-1281/BindToAllInterfaces.ql
|
||||
postprocess: utils/test/InlineExpectationsTestQuery.ql
|
||||
@@ -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('')
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
|
||||
67
python/scripts/create-extractor-pack.sh
Executable file
67
python/scripts/create-extractor-pack.sh
Executable 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
|
||||
Reference in New Issue
Block a user