Merge remote-tracking branch 'origin/main' into nickrolfe/oj

This commit is contained in:
Nick Rolfe
2021-10-07 16:40:36 +01:00
103 changed files with 8243 additions and 5744 deletions

View File

@@ -26,12 +26,12 @@ fn main() -> std::io::Result<()> {
for line in env::var("LGTM_INDEX_FILTERS")
.unwrap_or_default()
.split("\n")
.split('\n')
{
if line.starts_with("include:") {
cmd.arg("--include").arg(&line[8..]);
} else if line.starts_with("exclude:") {
cmd.arg("--exclude").arg(&line[8..]);
if let Some(stripped) = line.strip_prefix("include:") {
cmd.arg("--include").arg(stripped);
} else if let Some(stripped) = line.strip_prefix("exclude:") {
cmd.arg("--exclude").arg(stripped);
}
}
let exit = &cmd.spawn()?.wait()?;

View File

@@ -165,7 +165,7 @@ pub fn extract(
schema: &NodeTypeMap,
trap_writer: &mut TrapWriter,
path: &Path,
source: &Vec<u8>,
source: &[u8],
ranges: &[Range],
) -> std::io::Result<()> {
let span = span!(
@@ -180,17 +180,16 @@ pub fn extract(
let mut parser = Parser::new();
parser.set_language(language).unwrap();
parser.set_included_ranges(&ranges).unwrap();
parser.set_included_ranges(ranges).unwrap();
let tree = parser.parse(&source, None).expect("Failed to parse file");
trap_writer.comment(format!("Auto-generated TRAP file for {}", path.display()));
let file_label = &trap_writer.populate_file(path);
let mut visitor = Visitor {
source: &source,
trap_writer: trap_writer,
source,
trap_writer,
// TODO: should we handle path strings that are not valid UTF8 better?
path: format!("{}", path.display()),
file_label: *file_label,
token_counter: 0,
toplevel_child_counter: 0,
stack: Vec::new(),
language_prefix,
@@ -206,15 +205,7 @@ pub fn extract(
/// HTML entities.
fn escape_key<'a, S: Into<Cow<'a, str>>>(key: S) -> Cow<'a, str> {
fn needs_escaping(c: char) -> bool {
match c {
'&' => true,
'{' => true,
'}' => true,
'"' => true,
'@' => true,
'#' => true,
_ => false,
}
matches!(c, '&' | '{' | '}' | '"' | '@' | '#')
}
let key = key.into();
@@ -297,11 +288,9 @@ struct Visitor<'a> {
/// source file.
file_label: Label,
/// The source code as a UTF-8 byte array
source: &'a Vec<u8>,
source: &'a [u8],
/// A TrapWriter to accumulate trap entries
trap_writer: &'a mut TrapWriter,
/// A counter for tokens
token_counter: usize,
/// A counter for top-level child nodes
toplevel_child_counter: usize,
/// Language prefix
@@ -345,7 +334,7 @@ impl Visitor<'_> {
full_error_message: String,
node: Node,
) {
let (start_line, start_column, end_line, end_column) = location_for(&self.source, node);
let (start_line, start_column, end_line, end_column) = location_for(self.source, node);
let loc = self.trap_writer.location(
self.file_label,
start_line,
@@ -376,7 +365,7 @@ impl Visitor<'_> {
let id = self.trap_writer.fresh_id();
self.stack.push((id, 0, Vec::new()));
return true;
true
}
fn leave_node(&mut self, field_name: Option<&'static str>, node: Node) {
@@ -384,7 +373,7 @@ impl Visitor<'_> {
return;
}
let (id, _, child_nodes) = self.stack.pop().expect("Vistor: empty stack");
let (start_line, start_column, end_line, end_column) = location_for(&self.source, node);
let (start_line, start_column, end_line, end_column) = location_for(self.source, node);
let loc = self.trap_writer.location(
self.file_label,
start_line,
@@ -425,13 +414,10 @@ impl Visitor<'_> {
vec![
Arg::Label(id),
Arg::Int(*kind_id),
Arg::Label(self.file_label),
Arg::Int(self.token_counter),
sliced_source_arg(self.source, node),
Arg::Label(loc),
],
);
self.token_counter += 1;
}
EntryKind::Table {
fields,
@@ -446,11 +432,10 @@ impl Visitor<'_> {
Arg::Int(parent_index),
],
);
let mut all_args = Vec::new();
all_args.push(Arg::Label(id));
let mut all_args = vec![Arg::Label(id)];
all_args.extend(args);
all_args.push(Arg::Label(loc));
self.trap_writer.add_tuple(&table_name, all_args);
self.trap_writer.add_tuple(table_name, all_args);
}
}
_ => {
@@ -485,8 +470,8 @@ impl Visitor<'_> {
fn complex_node(
&mut self,
node: &Node,
fields: &Vec<Field>,
child_nodes: &Vec<ChildNode>,
fields: &[Field],
child_nodes: &[ChildNode],
parent_id: Label,
) -> Option<Vec<Arg>> {
let mut map: Map<&Option<String>, (&Field, Vec<Arg>)> = Map::new();
@@ -523,22 +508,20 @@ impl Visitor<'_> {
);
self.record_parse_error_for_node(error_message, full_error_message, *node);
}
} else {
if child_node.field_name.is_some() || child_node.type_name.named {
let error_message = format!(
"value for unknown field: {}::{} and type {:?}",
node.kind(),
&child_node.field_name.unwrap_or("child"),
&child_node.type_name
);
let full_error_message = format!(
"{}:{}: {}",
&self.path,
node.start_position().row + 1,
error_message
);
self.record_parse_error_for_node(error_message, full_error_message, *node);
}
} else if child_node.field_name.is_some() || child_node.type_name.named {
let error_message = format!(
"value for unknown field: {}::{} and type {:?}",
node.kind(),
&child_node.field_name.unwrap_or("child"),
&child_node.type_name
);
let full_error_message = format!(
"{}:{}: {}",
&self.path,
node.start_position().row + 1,
error_message
);
self.record_parse_error_for_node(error_message, full_error_message, *node);
}
}
let mut args = Vec::new();
@@ -586,13 +569,12 @@ impl Visitor<'_> {
);
break;
}
let mut args = Vec::new();
args.push(Arg::Label(parent_id));
let mut args = vec![Arg::Label(parent_id)];
if *has_index {
args.push(Arg::Int(index))
}
args.push(child_value.clone());
self.trap_writer.add_tuple(&table_name, args);
self.trap_writer.add_tuple(table_name, args);
}
}
}
@@ -610,13 +592,10 @@ impl Visitor<'_> {
if tp == single_type {
return true;
}
match &self.schema.get(single_type).unwrap().kind {
EntryKind::Union { members } => {
if self.type_matches_set(tp, members) {
return true;
}
if let EntryKind::Union { members } = &self.schema.get(single_type).unwrap().kind {
if self.type_matches_set(tp, members) {
return true;
}
_ => {}
}
}
node_types::FieldTypeInfo::Multiple { types, .. } => {
@@ -646,7 +625,7 @@ impl Visitor<'_> {
}
// Emit a slice of a source file as an Arg.
fn sliced_source_arg(source: &Vec<u8>, n: Node) -> Arg {
fn sliced_source_arg(source: &[u8], n: Node) -> Arg {
let range = n.byte_range();
Arg::String(String::from_utf8_lossy(&source[range.start..range.end]).into_owned())
}
@@ -654,7 +633,7 @@ fn sliced_source_arg(source: &Vec<u8>, n: Node) -> Arg {
// Emit a pair of `TrapEntry`s for the provided node, appropriately calibrated.
// The first is the location and label definition, and the second is the
// 'Located' entry.
fn location_for<'a>(source: &Vec<u8>, n: Node) -> (usize, usize, usize, usize) {
fn location_for(source: &[u8], n: Node) -> (usize, usize, usize, usize) {
// Tree-sitter row, column values are 0-based while CodeQL starts
// counting at 1. In addition Tree-sitter's row and column for the
// end position are exclusive while CodeQL's end positions are inclusive.
@@ -812,18 +791,18 @@ impl fmt::Display for Arg {
/// the string is sliced at the provided limit. If there is a multi-byte character
/// at the limit then the returned slice will be slightly shorter than the limit to
/// avoid splitting that multi-byte character.
fn limit_string(string: &String, max_size: usize) -> &str {
fn limit_string(string: &str, max_size: usize) -> &str {
if string.len() <= max_size {
return string;
}
let p = string.as_ptr();
let p = string.as_bytes();
let mut index = max_size;
// We want to clip the string at [max_size]; however, the character at that position
// may span several bytes. We need to find the first byte of the character. In UTF-8
// encoded data any byte that matches the bit pattern 10XXXXXX is not a start byte.
// Therefore we decrement the index as long as there are bytes matching this pattern.
// This ensures we cut the string at the border between one character and another.
while index > 0 && unsafe { (*p.offset(index as isize) & 0b11000000) == 0b10000000 } {
while index > 0 && (p[index] & 0b11000000) == 0b10000000 {
index -= 1;
}
&string[0..index]

View File

@@ -2,7 +2,6 @@ mod extractor;
extern crate num_cpus;
use clap;
use flate2::write::GzEncoder;
use rayon::prelude::*;
use std::fs;
@@ -57,7 +56,7 @@ impl TrapCompression {
* (minimum of 1). If unspecified, should be considered as set to -1."
*/
fn num_codeql_threads() -> usize {
let threads_str = std::env::var("CODEQL_THREADS").unwrap_or("-1".to_owned());
let threads_str = std::env::var("CODEQL_THREADS").unwrap_or_else(|_| "-1".to_owned());
match threads_str.parse::<i32>() {
Ok(num) if num <= 0 => {
let reduction = -num as usize;
@@ -124,6 +123,10 @@ fn main() -> std::io::Result<()> {
let language = tree_sitter_ruby::language();
let erb = tree_sitter_embedded_template::language();
// Look up tree-sitter kind ids now, to avoid string comparisons when scanning ERB files.
let erb_directive_id = erb.id_for_node_kind("directive", true);
let erb_output_directive_id = erb.id_for_node_kind("output_directive", true);
let erb_code_id = erb.id_for_node_kind("code", true);
let schema = node_types::read_node_types_str("ruby", tree_sitter_ruby::NODE_TYPES)?;
let erb_schema =
node_types::read_node_types_str("erb", tree_sitter_embedded_template::NODE_TYPES)?;
@@ -149,7 +152,13 @@ fn main() -> std::io::Result<()> {
&[],
)?;
let (ranges, line_breaks) = scan_erb(erb, &source);
let (ranges, line_breaks) = scan_erb(
erb,
&source,
erb_directive_id,
erb_output_directive_id,
erb_code_id,
);
for i in line_breaks {
if i < source.len() {
source[i] = b'\n';
@@ -181,12 +190,12 @@ fn main() -> std::io::Result<()> {
}
fn write_trap(
trap_dir: &PathBuf,
trap_dir: &Path,
path: PathBuf,
trap_writer: extractor::TrapWriter,
trap_compression: &TrapCompression,
) -> std::io::Result<()> {
let trap_file = path_for(&trap_dir, &path, trap_compression.extension());
let trap_file = path_for(trap_dir, &path, trap_compression.extension());
std::fs::create_dir_all(&trap_file.parent().unwrap())?;
let trap_file = std::fs::File::create(&trap_file)?;
let mut trap_file = BufWriter::new(trap_file);
@@ -199,7 +208,13 @@ fn write_trap(
}
}
fn scan_erb(erb: Language, source: &std::vec::Vec<u8>) -> (Vec<Range>, Vec<usize>) {
fn scan_erb(
erb: Language,
source: &[u8],
directive_id: u16,
output_directive_id: u16,
code_id: u16,
) -> (Vec<Range>, Vec<usize>) {
let mut parser = Parser::new();
parser.set_language(erb).unwrap();
let tree = parser.parse(&source, None).expect("Failed to parse file");
@@ -207,10 +222,10 @@ fn scan_erb(erb: Language, source: &std::vec::Vec<u8>) -> (Vec<Range>, Vec<usize
let mut line_breaks = vec![];
for n in tree.root_node().children(&mut tree.walk()) {
let kind = n.kind();
if kind == "directive" || kind == "output_directive" {
let kind_id = n.kind_id();
if kind_id == directive_id || kind_id == output_directive_id {
for c in n.children(&mut tree.walk()) {
if c.kind() == "code" {
if c.kind_id() == code_id {
let mut range = c.range();
if range.end_byte < source.len() {
line_breaks.push(range.end_byte);
@@ -222,7 +237,7 @@ fn scan_erb(erb: Language, source: &std::vec::Vec<u8>) -> (Vec<Range>, Vec<usize
}
}
}
if result.len() == 0 {
if result.is_empty() {
let root = tree.root_node();
// Add an empty range at the end of the file
result.push(Range {

View File

@@ -68,10 +68,10 @@ impl<'a> fmt::Display for Table<'a> {
}
write!(f, "{}", key)?;
}
write!(f, "]\n")?;
writeln!(f, "]")?;
}
write!(f, "{}(\n", self.name)?;
writeln!(f, "{}(", self.name)?;
for (column_index, column) in self.columns.iter().enumerate() {
write!(f, " ")?;
if column.unique {
@@ -92,7 +92,7 @@ impl<'a> fmt::Display for Table<'a> {
if column_index + 1 != self.columns.len() {
write!(f, ",")?;
}
write!(f, "\n")?;
writeln!(f)?;
}
write!(f, ");")?;

View File

@@ -3,7 +3,6 @@ mod language;
mod ql;
mod ql_gen;
use clap;
use language::Language;
use std::collections::BTreeMap as Map;
use std::collections::BTreeSet as Set;
@@ -33,7 +32,7 @@ fn make_field_type<'a>(
.map(|t| nodes.get(t).unwrap().dbscheme_name.as_str())
.collect();
(
ql::Type::AtType(&dbscheme_union),
ql::Type::At(dbscheme_union),
Some(dbscheme::Entry::Union(dbscheme::Union {
name: dbscheme_union,
members,
@@ -41,14 +40,14 @@ fn make_field_type<'a>(
)
}
node_types::FieldTypeInfo::Single(t) => {
let dbscheme_name = &nodes.get(&t).unwrap().dbscheme_name;
(ql::Type::AtType(dbscheme_name), None)
let dbscheme_name = &nodes.get(t).unwrap().dbscheme_name;
(ql::Type::At(dbscheme_name), None)
}
node_types::FieldTypeInfo::ReservedWordInt(int_mapping) => {
// The field will be an `int` in the db, and we add a case split to
// create other db types for each integer value.
let mut branches: Vec<(usize, &'a str)> = Vec::new();
for (_, (value, name)) in int_mapping {
for (value, name) in int_mapping.values() {
branches.push((*value, name));
}
let case = dbscheme::Entry::Case(dbscheme::Case {
@@ -74,12 +73,12 @@ fn add_field_for_table_storage<'a>(
let parent_name = &nodes.get(&field.parent).unwrap().dbscheme_name;
// This field can appear zero or multiple times, so put
// it in an auxiliary table.
let (field_ql_type, field_type_entry) = make_field_type(parent_name, &field, nodes);
let (field_ql_type, field_type_entry) = make_field_type(parent_name, field, nodes);
let parent_column = dbscheme::Column {
unique: !has_index,
db_type: dbscheme::DbColumnType::Int,
name: &parent_name,
ql_type: ql::Type::AtType(&parent_name),
name: parent_name,
ql_type: ql::Type::At(parent_name),
ql_type_is_ref: true,
};
let index_column = dbscheme::Column {
@@ -97,7 +96,7 @@ fn add_field_for_table_storage<'a>(
ql_type_is_ref: true,
};
let field_table = dbscheme::Table {
name: &table_name,
name: table_name,
columns: if has_index {
vec![parent_column, index_column, field_column]
} else {
@@ -106,7 +105,7 @@ fn add_field_for_table_storage<'a>(
// In addition to the field being unique, the combination of
// parent+index is unique, so add a keyset for them.
keysets: if has_index {
Some(vec![&parent_name, "index"])
Some(vec![parent_name, "index"])
} else {
None
},
@@ -122,7 +121,7 @@ fn add_field_for_column_storage<'a>(
) -> (dbscheme::Column<'a>, Option<dbscheme::Entry<'a>>) {
// This field must appear exactly once, so we add it as
// a column to the main table for the node type.
let (field_ql_type, field_type_entry) = make_field_type(parent_name, &field, nodes);
let (field_ql_type, field_type_entry) = make_field_type(parent_name, field, nodes);
(
dbscheme::Column {
unique: false,
@@ -142,9 +141,9 @@ fn add_field_for_column_storage<'a>(
/// 2. A set of names of the members of the `<lang>_ast_node` union.
/// 3. A map where the keys are the dbscheme names for token kinds, and the
/// values are their integer representations.
fn convert_nodes<'a>(
nodes: &'a node_types::NodeTypeMap,
) -> (Vec<dbscheme::Entry<'a>>, Set<&'a str>, Map<&'a str, usize>) {
fn convert_nodes(
nodes: &node_types::NodeTypeMap,
) -> (Vec<dbscheme::Entry>, Set<&str>, Map<&str, usize>) {
let mut entries: Vec<dbscheme::Entry> = Vec::new();
let mut ast_node_members: Set<&str> = Set::new();
let token_kinds: Map<&str, usize> = nodes
@@ -156,7 +155,7 @@ fn convert_nodes<'a>(
_ => None,
})
.collect();
for (_, node) in nodes {
for node in nodes.values() {
match &node.kind {
node_types::EntryKind::Union { members: n_members } => {
// It's a tree-sitter supertype node, for which we create a union
@@ -173,12 +172,12 @@ fn convert_nodes<'a>(
node_types::EntryKind::Table { name, fields } => {
// It's a product type, defined by a table.
let mut main_table = dbscheme::Table {
name: &name,
name,
columns: vec![dbscheme::Column {
db_type: dbscheme::DbColumnType::Int,
name: "id",
unique: true,
ql_type: ql::Type::AtType(&node.dbscheme_name),
ql_type: ql::Type::At(&node.dbscheme_name),
ql_type_is_ref: false,
}],
keysets: None,
@@ -238,7 +237,7 @@ fn convert_nodes<'a>(
unique: false,
db_type: dbscheme::DbColumnType::Int,
name: "loc",
ql_type: ql::Type::AtType("location"),
ql_type: ql::Type::At("location"),
ql_type_is_ref: true,
});
@@ -264,14 +263,14 @@ fn create_ast_node_parent_table<'a>(name: &'a str, ast_node_name: &'a str) -> db
db_type: dbscheme::DbColumnType::Int,
name: "child",
unique: false,
ql_type: ql::Type::AtType(ast_node_name),
ql_type: ql::Type::At(ast_node_name),
ql_type_is_ref: true,
},
dbscheme::Column {
db_type: dbscheme::DbColumnType::Int,
name: "parent",
unique: false,
ql_type: ql::Type::AtType(name),
ql_type: ql::Type::At(name),
ql_type_is_ref: true,
},
dbscheme::Column {
@@ -295,7 +294,7 @@ fn create_tokeninfo<'a>(name: &'a str, type_name: &'a str) -> dbscheme::Table<'a
db_type: dbscheme::DbColumnType::Int,
name: "id",
unique: true,
ql_type: ql::Type::AtType(type_name),
ql_type: ql::Type::At(type_name),
ql_type_is_ref: false,
},
dbscheme::Column {
@@ -305,20 +304,6 @@ fn create_tokeninfo<'a>(name: &'a str, type_name: &'a str) -> dbscheme::Table<'a
ql_type: ql::Type::Int,
ql_type_is_ref: true,
},
dbscheme::Column {
unique: false,
db_type: dbscheme::DbColumnType::Int,
name: "file",
ql_type: ql::Type::AtType("file"),
ql_type_is_ref: true,
},
dbscheme::Column {
unique: false,
db_type: dbscheme::DbColumnType::Int,
name: "idx",
ql_type: ql::Type::Int,
ql_type_is_ref: true,
},
dbscheme::Column {
unique: false,
db_type: dbscheme::DbColumnType::String,
@@ -330,7 +315,7 @@ fn create_tokeninfo<'a>(name: &'a str, type_name: &'a str) -> dbscheme::Table<'a
unique: false,
db_type: dbscheme::DbColumnType::Int,
name: "loc",
ql_type: ql::Type::AtType("location"),
ql_type: ql::Type::At("location"),
ql_type_is_ref: true,
},
],
@@ -343,9 +328,9 @@ fn create_token_case<'a>(name: &'a str, token_kinds: Map<&'a str, usize>) -> dbs
.map(|(&name, kind_id)| (*kind_id, name))
.collect();
dbscheme::Case {
name: name,
name,
column: "kind",
branches: branches,
branches,
}
}
@@ -365,7 +350,7 @@ fn create_files_table<'a>() -> dbscheme::Entry<'a> {
unique: true,
db_type: dbscheme::DbColumnType::Int,
name: "id",
ql_type: ql::Type::AtType("file"),
ql_type: ql::Type::At("file"),
ql_type_is_ref: false,
},
dbscheme::Column {
@@ -387,7 +372,7 @@ fn create_folders_table<'a>() -> dbscheme::Entry<'a> {
unique: true,
db_type: dbscheme::DbColumnType::Int,
name: "id",
ql_type: ql::Type::AtType("folder"),
ql_type: ql::Type::At("folder"),
ql_type_is_ref: false,
},
dbscheme::Column {
@@ -410,14 +395,14 @@ fn create_locations_default_table<'a>() -> dbscheme::Entry<'a> {
unique: true,
db_type: dbscheme::DbColumnType::Int,
name: "id",
ql_type: ql::Type::AtType("location_default"),
ql_type: ql::Type::At("location_default"),
ql_type_is_ref: false,
},
dbscheme::Column {
unique: false,
db_type: dbscheme::DbColumnType::Int,
name: "file",
ql_type: ql::Type::AtType("file"),
ql_type: ql::Type::At("file"),
ql_type_is_ref: true,
},
dbscheme::Column {
@@ -467,14 +452,14 @@ fn create_containerparent_table<'a>() -> dbscheme::Entry<'a> {
unique: false,
db_type: dbscheme::DbColumnType::Int,
name: "parent",
ql_type: ql::Type::AtType("container"),
ql_type: ql::Type::At("container"),
ql_type_is_ref: true,
},
dbscheme::Column {
unique: true,
db_type: dbscheme::DbColumnType::Int,
name: "child",
ql_type: ql::Type::AtType("container"),
ql_type: ql::Type::At("container"),
ql_type_is_ref: true,
},
],
@@ -505,7 +490,7 @@ fn create_diagnostics<'a>() -> (dbscheme::Case<'a>, dbscheme::Table<'a>) {
unique: true,
db_type: dbscheme::DbColumnType::Int,
name: "id",
ql_type: ql::Type::AtType("diagnostic"),
ql_type: ql::Type::At("diagnostic"),
ql_type_is_ref: false,
},
dbscheme::Column {
@@ -540,7 +525,7 @@ fn create_diagnostics<'a>() -> (dbscheme::Case<'a>, dbscheme::Table<'a>) {
unique: false,
db_type: dbscheme::DbColumnType::Int,
name: "location",
ql_type: ql::Type::AtType("location_default"),
ql_type: ql::Type::At("location_default"),
ql_type_is_ref: true,
},
],
@@ -639,7 +624,7 @@ fn main() -> std::io::Result<()> {
let token_name = format!("{}_token", &prefix);
let tokeninfo_name = format!("{}_tokeninfo", &prefix);
let reserved_word_name = format!("{}_reserved_word", &prefix);
let nodes = node_types::read_node_types_str(&prefix, &language.node_types)?;
let nodes = node_types::read_node_types_str(&prefix, language.node_types)?;
let (dbscheme_entries, mut ast_node_members, token_kinds) = convert_nodes(&nodes);
ast_node_members.insert(&token_name);
dbscheme::write(&mut dbscheme_writer, &dbscheme_entries)?;

View File

@@ -43,15 +43,15 @@ impl<'a> fmt::Display for Class<'a> {
}
write!(f, "{}", supertype)?;
}
write!(f, " {{ \n")?;
writeln!(f, " {{ ")?;
if let Some(charpred) = &self.characteristic_predicate {
write!(
writeln!(
f,
" {}\n",
" {}",
Predicate {
qldoc: None,
name: self.name.clone(),
name: self.name,
overridden: false,
return_type: None,
formal_parameters: vec![],
@@ -61,7 +61,7 @@ impl<'a> fmt::Display for Class<'a> {
}
for predicate in &self.predicates {
write!(f, " {}\n", predicate)?;
writeln!(f, " {}", predicate)?;
}
write!(f, "}}")?;
@@ -82,9 +82,9 @@ impl<'a> fmt::Display for Module<'a> {
if let Some(qldoc) = &self.qldoc {
write!(f, "/** {} */", qldoc)?;
}
write!(f, "module {} {{ \n", self.name)?;
writeln!(f, "module {} {{ ", self.name)?;
for decl in &self.body {
write!(f, " {}\n", decl)?;
writeln!(f, " {}", decl)?;
}
write!(f, "}}")?;
Ok(())
@@ -100,7 +100,7 @@ pub enum Type<'a> {
String,
/// A database type that will need to be referred to with an `@` prefix.
AtType(&'a str),
At(&'a str),
/// A user-defined type.
Normal(&'a str),
@@ -112,7 +112,7 @@ impl<'a> fmt::Display for Type<'a> {
Type::Int => write!(f, "int"),
Type::String => write!(f, "string"),
Type::Normal(name) => write!(f, "{}", name),
Type::AtType(name) => write!(f, "@{}", name),
Type::At(name) => write!(f, "@{}", name),
}
}
}
@@ -197,7 +197,7 @@ impl<'a> fmt::Display for Expression<'a> {
second_expr,
} => {
write!(f, "{}(", name)?;
if vars.len() > 0 {
if !vars.is_empty() {
for (index, var) in vars.iter().enumerate() {
if index > 0 {
write!(f, ", ")?;

View File

@@ -104,7 +104,7 @@ pub fn create_ast_node_class<'a>(ast_node: &'a str, ast_node_parent: &'a str) ->
qldoc: Some(String::from("The base class for all AST nodes")),
name: "AstNode",
is_abstract: false,
supertypes: vec![ql::Type::AtType(ast_node)].into_iter().collect(),
supertypes: vec![ql::Type::At(ast_node)].into_iter().collect(),
characteristic_predicate: None,
predicates: vec![
to_string,
@@ -119,14 +119,14 @@ pub fn create_ast_node_class<'a>(ast_node: &'a str, ast_node_parent: &'a str) ->
}
pub fn create_token_class<'a>(token_type: &'a str, tokeninfo: &'a str) -> ql::Class<'a> {
let tokeninfo_arity = 6;
let tokeninfo_arity = 4;
let get_value = ql::Predicate {
qldoc: Some(String::from("Gets the value of this token.")),
name: "getValue",
overridden: false,
return_type: Some(ql::Type::String),
formal_parameters: vec![],
body: create_get_field_expr_for_column_storage("result", tokeninfo, 3, tokeninfo_arity),
body: create_get_field_expr_for_column_storage("result", tokeninfo, 1, tokeninfo_arity),
};
let get_location = ql::Predicate {
qldoc: Some(String::from("Gets the location of this token.")),
@@ -134,7 +134,7 @@ pub fn create_token_class<'a>(token_type: &'a str, tokeninfo: &'a str) -> ql::Cl
overridden: true,
return_type: Some(ql::Type::Normal("Location")),
formal_parameters: vec![],
body: create_get_field_expr_for_column_storage("result", tokeninfo, 4, tokeninfo_arity),
body: create_get_field_expr_for_column_storage("result", tokeninfo, 2, tokeninfo_arity),
};
let to_string = ql::Predicate {
qldoc: Some(String::from(
@@ -153,7 +153,7 @@ pub fn create_token_class<'a>(token_type: &'a str, tokeninfo: &'a str) -> ql::Cl
qldoc: Some(String::from("A token.")),
name: "Token",
is_abstract: false,
supertypes: vec![ql::Type::AtType(token_type), ql::Type::Normal("AstNode")]
supertypes: vec![ql::Type::At(token_type), ql::Type::Normal("AstNode")]
.into_iter()
.collect(),
characteristic_predicate: None,
@@ -167,14 +167,14 @@ pub fn create_token_class<'a>(token_type: &'a str, tokeninfo: &'a str) -> ql::Cl
}
// Creates the `ReservedWord` class.
pub fn create_reserved_word_class<'a>(db_name: &'a str) -> ql::Class<'a> {
pub fn create_reserved_word_class(db_name: &str) -> ql::Class {
let class_name = "ReservedWord";
let get_a_primary_ql_class = create_get_a_primary_ql_class(&class_name);
let get_a_primary_ql_class = create_get_a_primary_ql_class(class_name);
ql::Class {
qldoc: Some(String::from("A reserved word.")),
name: class_name,
is_abstract: false,
supertypes: vec![ql::Type::AtType(db_name), ql::Type::Normal("Token")]
supertypes: vec![ql::Type::At(db_name), ql::Type::Normal("Token")]
.into_iter()
.collect(),
characteristic_predicate: None,
@@ -190,8 +190,8 @@ fn create_none_predicate<'a>(
return_type: Option<ql::Type<'a>>,
) -> ql::Predicate<'a> {
ql::Predicate {
qldoc: qldoc,
name: name,
qldoc,
name,
overridden,
return_type,
formal_parameters: Vec::new(),
@@ -201,7 +201,7 @@ fn create_none_predicate<'a>(
/// Creates an overridden `getAPrimaryQlClass` predicate that returns the given
/// name.
fn create_get_a_primary_ql_class<'a>(class_name: &'a str) -> ql::Predicate<'a> {
fn create_get_a_primary_ql_class(class_name: &str) -> ql::Predicate {
ql::Predicate {
qldoc: Some(String::from(
"Gets the name of the primary QL class for this element.",
@@ -223,7 +223,7 @@ fn create_get_a_primary_ql_class<'a>(class_name: &'a str) -> ql::Predicate<'a> {
///
/// `def_table` - the name of the table that defines the entity and its location.
/// `arity` - the total number of columns in the table
fn create_get_location_predicate<'a>(def_table: &'a str, arity: usize) -> ql::Predicate<'a> {
fn create_get_location_predicate(def_table: &str, arity: usize) -> ql::Predicate {
ql::Predicate {
qldoc: Some(String::from("Gets the location of this element.")),
name: "getLocation",
@@ -248,7 +248,7 @@ fn create_get_location_predicate<'a>(def_table: &'a str, arity: usize) -> ql::Pr
/// # Arguments
///
/// `def_table` - the name of the table that defines the entity and its text.
fn create_get_text_predicate<'a>(def_table: &'a str) -> ql::Predicate<'a> {
fn create_get_text_predicate(def_table: &str) -> ql::Predicate {
ql::Predicate {
qldoc: Some(String::from("Gets the text content of this element.")),
name: "getText",
@@ -339,13 +339,13 @@ fn create_field_getters<'a>(
) -> (ql::Predicate<'a>, Option<ql::Expression<'a>>) {
let return_type = match &field.type_info {
node_types::FieldTypeInfo::Single(t) => {
Some(ql::Type::Normal(&nodes.get(&t).unwrap().ql_class_name))
Some(ql::Type::Normal(&nodes.get(t).unwrap().ql_class_name))
}
node_types::FieldTypeInfo::Multiple {
types: _,
dbscheme_union: _,
ql_class,
} => Some(ql::Type::Normal(&ql_class)),
} => Some(ql::Type::Normal(ql_class)),
node_types::FieldTypeInfo::ReservedWordInt(_) => Some(ql::Type::String),
};
let formal_parameters = match &field.storage {
@@ -381,13 +381,13 @@ fn create_field_getters<'a>(
(
create_get_field_expr_for_column_storage(
get_value_result_var_name,
&main_table_name,
main_table_name,
column_index,
main_table_arity,
),
create_get_field_expr_for_column_storage(
get_value_result_var_name,
&main_table_name,
main_table_name,
column_index,
main_table_arity,
),
@@ -400,12 +400,12 @@ fn create_field_getters<'a>(
} => (
create_get_field_expr_for_table_storage(
get_value_result_var_name,
&field_table_name,
field_table_name,
if *has_index { Some("i") } else { None },
),
create_get_field_expr_for_table_storage(
get_value_result_var_name,
&field_table_name,
field_table_name,
if *has_index { Some("_") } else { None },
),
),
@@ -453,7 +453,7 @@ fn create_field_getters<'a>(
let qldoc = match &field.name {
Some(name) => format!("Gets the node corresponding to the field `{}`.", name),
None => {
if formal_parameters.len() == 0 {
if formal_parameters.is_empty() {
"Gets the child of this node.".to_owned()
} else {
"Gets the `i`th child of this node.".to_owned()
@@ -474,7 +474,7 @@ fn create_field_getters<'a>(
}
/// Converts the given node types into CodeQL classes wrapping the dbscheme.
pub fn convert_nodes<'a>(nodes: &'a node_types::NodeTypeMap) -> Vec<ql::TopLevel<'a>> {
pub fn convert_nodes(nodes: &node_types::NodeTypeMap) -> Vec<ql::TopLevel> {
let mut classes: Vec<ql::TopLevel> = Vec::new();
let mut token_kinds = BTreeSet::new();
for (type_name, node) in nodes {
@@ -491,7 +491,7 @@ pub fn convert_nodes<'a>(nodes: &'a node_types::NodeTypeMap) -> Vec<ql::TopLevel
if type_name.named {
let get_a_primary_ql_class = create_get_a_primary_ql_class(&node.ql_class_name);
let mut supertypes: BTreeSet<ql::Type> = BTreeSet::new();
supertypes.insert(ql::Type::AtType(&node.dbscheme_name));
supertypes.insert(ql::Type::At(&node.dbscheme_name));
supertypes.insert(ql::Type::Normal("Token"));
classes.push(ql::TopLevel::Class(ql::Class {
qldoc: Some(format!("A class representing `{}` tokens.", type_name.kind)),
@@ -511,7 +511,7 @@ pub fn convert_nodes<'a>(nodes: &'a node_types::NodeTypeMap) -> Vec<ql::TopLevel
name: &node.ql_class_name,
is_abstract: false,
supertypes: vec![
ql::Type::AtType(&node.dbscheme_name),
ql::Type::At(&node.dbscheme_name),
ql::Type::Normal("AstNode"),
]
.into_iter()
@@ -542,25 +542,25 @@ pub fn convert_nodes<'a>(nodes: &'a node_types::NodeTypeMap) -> Vec<ql::TopLevel
let main_class_name = &node.ql_class_name;
let mut main_class = ql::Class {
qldoc: Some(format!("A class representing `{}` nodes.", type_name.kind)),
name: &main_class_name,
name: main_class_name,
is_abstract: false,
supertypes: vec![
ql::Type::AtType(&node.dbscheme_name),
ql::Type::At(&node.dbscheme_name),
ql::Type::Normal("AstNode"),
]
.into_iter()
.collect(),
characteristic_predicate: None,
predicates: vec![
create_get_a_primary_ql_class(&main_class_name),
create_get_location_predicate(&main_table_name, main_table_arity),
create_get_a_primary_ql_class(main_class_name),
create_get_location_predicate(main_table_name, main_table_arity),
],
};
if fields.is_empty() {
main_class
.predicates
.push(create_get_text_predicate(&main_table_name));
.push(create_get_text_predicate(main_table_name));
} else {
let mut main_table_column_index: usize = 0;
let mut get_child_exprs: Vec<ql::Expression> = Vec::new();
@@ -571,7 +571,7 @@ pub fn convert_nodes<'a>(nodes: &'a node_types::NodeTypeMap) -> Vec<ql::TopLevel
// - the QL expressions to access the fields that will be part of getAFieldOrChild.
for field in fields {
let (get_pred, get_child_expr) = create_field_getters(
&main_table_name,
main_table_name,
main_table_arity,
&mut main_table_column_index,
field,

View File

@@ -83,13 +83,13 @@ pub enum Storage {
pub fn read_node_types(prefix: &str, node_types_path: &Path) -> std::io::Result<NodeTypeMap> {
let file = fs::File::open(node_types_path)?;
let node_types = serde_json::from_reader(file)?;
Ok(convert_nodes(&prefix, &node_types))
let node_types: Vec<NodeInfo> = serde_json::from_reader(file)?;
Ok(convert_nodes(prefix, &node_types))
}
pub fn read_node_types_str(prefix: &str, node_types_json: &str) -> std::io::Result<NodeTypeMap> {
let node_types = serde_json::from_str(node_types_json)?;
Ok(convert_nodes(&prefix, &node_types))
let node_types: Vec<NodeInfo> = serde_json::from_str(node_types_json)?;
Ok(convert_nodes(prefix, &node_types))
}
fn convert_type(node_type: &NodeType) -> TypeName {
@@ -99,31 +99,31 @@ fn convert_type(node_type: &NodeType) -> TypeName {
}
}
fn convert_types(node_types: &Vec<NodeType>) -> Set<TypeName> {
let iter = node_types.iter().map(convert_type).collect();
std::collections::BTreeSet::from(iter)
fn convert_types(node_types: &[NodeType]) -> Set<TypeName> {
node_types.iter().map(convert_type).collect()
}
pub fn convert_nodes(prefix: &str, nodes: &Vec<NodeInfo>) -> NodeTypeMap {
pub fn convert_nodes(prefix: &str, nodes: &[NodeInfo]) -> NodeTypeMap {
let mut entries = NodeTypeMap::new();
let mut token_kinds = Set::new();
// First, find all the token kinds
for node in nodes {
if node.subtypes.is_none() {
if node.fields.as_ref().map_or(0, |x| x.len()) == 0 && node.children.is_none() {
let type_name = TypeName {
kind: node.kind.clone(),
named: node.named,
};
token_kinds.insert(type_name);
}
if node.subtypes.is_none()
&& node.fields.as_ref().map_or(0, |x| x.len()) == 0
&& node.children.is_none()
{
let type_name = TypeName {
kind: node.kind.clone(),
named: node.named,
};
token_kinds.insert(type_name);
}
}
for node in nodes {
let flattened_name = &node_type_name(&node.kind, node.named);
let dbscheme_name = escape_name(&flattened_name);
let dbscheme_name = escape_name(flattened_name);
let ql_class_name = dbscheme_name_to_class_name(&dbscheme_name);
let dbscheme_name = format!("{}_{}", prefix, &dbscheme_name);
if let Some(subtypes) = &node.subtypes {
@@ -138,7 +138,7 @@ pub fn convert_nodes(prefix: &str, nodes: &Vec<NodeInfo>) -> NodeTypeMap {
dbscheme_name,
ql_class_name,
kind: EntryKind::Union {
members: convert_types(&subtypes),
members: convert_types(subtypes),
},
},
);
@@ -160,7 +160,7 @@ pub fn convert_nodes(prefix: &str, nodes: &Vec<NodeInfo>) -> NodeTypeMap {
if let Some(node_fields) = &node.fields {
for (field_name, field_info) in node_fields {
add_field(
&prefix,
prefix,
&type_name,
Some(field_name.to_string()),
field_info,
@@ -172,7 +172,7 @@ pub fn convert_nodes(prefix: &str, nodes: &Vec<NodeInfo>) -> NodeTypeMap {
if let Some(children) = &node.children {
// Treat children as if they were a field called 'child'.
add_field(
&prefix,
prefix,
&type_name,
None,
children,
@@ -253,13 +253,11 @@ fn add_field(
// All possible types for this field are reserved words. The db
// representation will be an `int` with a `case @foo.field = ...` to
// enumerate the possible values.
let mut counter = 0;
let mut field_token_ints: BTreeMap<String, (usize, String)> = BTreeMap::new();
for t in converted_types {
for (counter, t) in converted_types.into_iter().enumerate() {
let dbscheme_variant_name =
escape_name(&format!("{}_{}_{}", &prefix, parent_flattened_name, t.kind));
field_token_ints.insert(t.kind.to_owned(), (counter, dbscheme_variant_name));
counter += 1;
}
FieldTypeInfo::ReservedWordInt(field_token_ints)
} else if field_info.types.len() == 1 {
@@ -330,7 +328,7 @@ fn node_type_name(kind: &str, named: bool) -> String {
}
}
const RESERVED_KEYWORDS: [&'static str; 14] = [
const RESERVED_KEYWORDS: [&str; 14] = [
"boolean", "case", "date", "float", "int", "key", "of", "order", "ref", "string", "subtype",
"type", "unique", "varchar",
];

View File

@@ -6,7 +6,7 @@ import files.FileSystem
* A location as given by a file, a start line, a start column,
* an end line, and an end column.
*
* For more information about locations see [LGTM locations](https://lgtm.com/help/ql/locations).
* For more information about locations see [LGTM locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
class Location extends @location {
/** Gets the file for this location. */
@@ -40,7 +40,7 @@ class Location extends @location {
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [LGTM locations](https://lgtm.com/help/ql/locations).
* [LGTM locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn

View File

@@ -141,7 +141,7 @@ abstract class Container extends @container {
/**
* Gets a URL representing the location of this container.
*
* For more information see https://lgtm.com/help/ql/locations#providing-urls.
* For more information see https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/#providing-urls.
*/
abstract string getURL();

View File

@@ -503,3 +503,38 @@ module CodeExecution {
abstract DataFlow::Node getCode();
}
}
/**
* A data-flow node that parses XML content.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `XmlParserCall::Range` instead.
*/
class XmlParserCall extends DataFlow::Node {
XmlParserCall::Range range;
XmlParserCall() { this = range }
/** Gets the argument that specifies the XML content to be parsed. */
DataFlow::Node getInput() { result = range.getInput() }
/** Holds if this XML parser call is configured to process external entities */
predicate externalEntitiesEnabled() { range.externalEntitiesEnabled() }
}
/** Provides a class for modeling new XML parsing APIs. */
module XmlParserCall {
/**
* A data-flow node that parses XML content.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `class XmlParserCall` instead.
*/
abstract class Range extends DataFlow::Node {
/** Gets the argument that specifies the XML content to be parsed. */
abstract DataFlow::Node getInput();
/** Holds if this XML parser call is configured to process external entities */
abstract predicate externalEntitiesEnabled();
}
}

View File

@@ -7,4 +7,5 @@ private import codeql.ruby.frameworks.ActiveRecord
private import codeql.ruby.frameworks.ActionView
private import codeql.ruby.frameworks.StandardLibrary
private import codeql.ruby.frameworks.Files
private import codeql.ruby.frameworks.HTTPClients
private import codeql.ruby.frameworks.HttpClients
private import codeql.ruby.frameworks.XmlParsing

View File

@@ -8,7 +8,7 @@ private import codeql.ruby.dataflow.internal.DataFlowImplCommon
/**
* A call.
*/
class Call extends Expr, TCall {
class Call extends Expr instanceof CallImpl {
override string getAPrimaryQlClass() { result = "Call" }
/**
@@ -23,7 +23,7 @@ class Call extends Expr, TCall {
* yield 0, bar: 1
* ```
*/
final Expr getArgument(int n) { result = this.(CallImpl).getArgumentImpl(n) }
final Expr getArgument(int n) { result = super.getArgumentImpl(n) }
/**
* Gets an argument of this method call.
@@ -49,7 +49,7 @@ class Call extends Expr, TCall {
/**
* Gets the number of arguments of this method call.
*/
final int getNumberOfArguments() { result = this.(CallImpl).getNumberOfArgumentsImpl() }
final int getNumberOfArguments() { result = super.getNumberOfArgumentsImpl() }
/** Gets a potential target of this call, if any. */
final Callable getATarget() {
@@ -59,7 +59,7 @@ class Call extends Expr, TCall {
}
override AstNode getAChild(string pred) {
result = super.getAChild(pred)
result = Expr.super.getAChild(pred)
or
pred = "getArgument" and result = this.getArgument(_)
}
@@ -68,7 +68,7 @@ class Call extends Expr, TCall {
/**
* A method call.
*/
class MethodCall extends Call, TMethodCall {
class MethodCall extends Call instanceof MethodCallImpl {
override string getAPrimaryQlClass() { result = "MethodCall" }
/**
@@ -84,7 +84,7 @@ class MethodCall extends Call, TMethodCall {
* the call to `qux` is the `Expr` for `Baz`; for the call to `corge` there
* is no result.
*/
final Expr getReceiver() { result = this.(MethodCallImpl).getReceiverImpl() }
final Expr getReceiver() { result = super.getReceiverImpl() }
/**
* Gets the name of the method being called. For example, in:
@@ -95,7 +95,7 @@ class MethodCall extends Call, TMethodCall {
*
* the result is `"bar"`.
*/
final string getMethodName() { result = this.(MethodCallImpl).getMethodNameImpl() }
final string getMethodName() { result = super.getMethodNameImpl() }
/**
* Gets the block of this method call, if any.
@@ -103,12 +103,12 @@ class MethodCall extends Call, TMethodCall {
* foo.each { |x| puts x }
* ```
*/
Block getBlock() { none() }
final Block getBlock() { result = super.getBlockImpl() }
override string toString() { result = "call to " + this.getMethodName() }
override AstNode getAChild(string pred) {
result = super.getAChild(pred)
result = Call.super.getAChild(pred)
or
pred = "getReceiver" and result = this.getReceiver()
or
@@ -123,7 +123,7 @@ class MethodCall extends Call, TMethodCall {
* a[0] = 10
* ```
*/
class SetterMethodCall extends MethodCall {
class SetterMethodCall extends MethodCall, TMethodCallSynth {
SetterMethodCall() { this = TMethodCallSynth(_, _, _, true, _) }
final override string getAPrimaryQlClass() { result = "SetterMethodCall" }
@@ -135,7 +135,7 @@ class SetterMethodCall extends MethodCall {
* a[0]
* ```
*/
class ElementReference extends MethodCall, TElementReference {
class ElementReference extends MethodCall instanceof ElementReferenceImpl {
final override string getAPrimaryQlClass() { result = "ElementReference" }
final override string toString() { result = "...[...]" }
@@ -147,11 +147,7 @@ class ElementReference extends MethodCall, TElementReference {
* yield x, y
* ```
*/
class YieldCall extends Call, TYieldCall {
Ruby::Yield g;
YieldCall() { this = TYieldCall(g) }
class YieldCall extends Call instanceof YieldCallImpl {
final override string getAPrimaryQlClass() { result = "YieldCall" }
final override string toString() { result = "yield ..." }
@@ -167,7 +163,7 @@ class YieldCall extends Call, TYieldCall {
* end
* ```
*/
class SuperCall extends MethodCall, TSuperCall {
class SuperCall extends MethodCall instanceof SuperCallImpl {
final override string getAPrimaryQlClass() { result = "SuperCall" }
}

View File

@@ -139,6 +139,8 @@ class ConstantReadAccess extends ConstantAccess {
result = lookupConst(resolveScopeExpr(this.getScopeExpr()), this.getName())
}
override string getValueText() { result = this.getValue().getValueText() }
final override string getAPrimaryQlClass() { result = "ConstantReadAccess" }
}

View File

@@ -1,4 +1,5 @@
private import codeql.ruby.AST
private import codeql.ruby.CFG
private import internal.AST
private import internal.TreeSitter
@@ -7,7 +8,12 @@ private import internal.TreeSitter
*
* This is the root QL class for all expressions.
*/
class Expr extends Stmt, TExpr { }
class Expr extends Stmt, TExpr {
/** Gets the textual (constant) value of this expression, if any. */
string getValueText() {
forex(CfgNodes::ExprCfgNode n | n = this.getAControlFlowNode() | result = n.getValueText())
}
}
/**
* A reference to the current object. For example:

View File

@@ -16,7 +16,7 @@ class Literal extends Expr, TLiteral {
* For complex literals, such as arrays, hashes, and strings with
* interpolations, this predicate has no result.
*/
string getValueText() { none() }
override string getValueText() { none() }
}
/**

View File

@@ -7,9 +7,9 @@ private import internal.TreeSitter
private import internal.Variable
/** A variable declared in a scope. */
class Variable extends TVariable {
class Variable instanceof VariableImpl {
/** Gets the name of this variable. */
string getName() { none() }
final string getName() { result = super.getNameImpl() }
/** Holds if the name of this variable is `name`. */
final predicate hasName(string name) { this.getName() = name }
@@ -18,10 +18,12 @@ class Variable extends TVariable {
final string toString() { result = this.getName() }
/** Gets the location of this variable. */
Location getLocation() { none() }
final Location getLocation() { result = super.getLocationImpl() }
/** Gets the scope this variable is declared in. */
Scope getDeclaringScope() { none() }
final Scope getDeclaringScope() {
toGenerated(result) = this.(VariableReal).getDeclaringScopeImpl()
}
/** Gets an access to this variable. */
VariableAccess getAnAccess() { result.getVariable() = this }
@@ -29,10 +31,10 @@ class Variable extends TVariable {
/** A local variable. */
class LocalVariable extends Variable, TLocalVariable {
override LocalVariableAccess getAnAccess() { none() }
override LocalVariableAccess getAnAccess() { result.getVariable() = this }
/** Gets the access where this local variable is first introduced. */
VariableAccess getDefiningAccess() { none() }
VariableAccess getDefiningAccess() { result = this.(LocalVariableReal).getDefiningAccessImpl() }
/**
* Holds if this variable is captured. For example in
@@ -52,12 +54,12 @@ class LocalVariable extends Variable, TLocalVariable {
}
/** A global variable. */
class GlobalVariable extends VariableReal, TGlobalVariable instanceof GlobalVariable::Range {
class GlobalVariable extends Variable instanceof GlobalVariableImpl {
final override GlobalVariableAccess getAnAccess() { result.getVariable() = this }
}
/** An instance variable. */
class InstanceVariable extends VariableReal, TInstanceVariable instanceof InstanceVariable::Range {
class InstanceVariable extends Variable instanceof InstanceVariableImpl {
/** Holds is this variable is a class instance variable. */
final predicate isClassInstanceVariable() { super.isClassInstanceVariable() }
@@ -65,14 +67,14 @@ class InstanceVariable extends VariableReal, TInstanceVariable instanceof Instan
}
/** A class variable. */
class ClassVariable extends VariableReal, TClassVariable instanceof ClassVariable::Range {
class ClassVariable extends Variable instanceof ClassVariableImpl {
final override ClassVariableAccess getAnAccess() { result.getVariable() = this }
}
/** An access to a variable. */
class VariableAccess extends Expr, TVariableAccess {
class VariableAccess extends Expr instanceof VariableAccessImpl {
/** Gets the variable this identifier refers to. */
final Variable getVariable() { result = this.(VariableAccessImpl).getVariableImpl() }
final Variable getVariable() { result = super.getVariableImpl() }
/**
* Holds if this access is a write access belonging to the explicit
@@ -106,6 +108,8 @@ class VariableAccess extends Expr, TVariableAccess {
* as is the first access to `e`.
*/
predicate isImplicitWrite() { implicitWriteAccess(toGenerated(this)) }
final override string toString() { result = VariableAccessImpl.super.toString() }
}
/** An access to a variable where the value is updated. */
@@ -122,7 +126,7 @@ class VariableReadAccess extends VariableAccess {
}
/** An access to a local variable. */
class LocalVariableAccess extends VariableAccess, TLocalVariableAccess {
class LocalVariableAccess extends VariableAccess instanceof LocalVariableAccessImpl {
final override string getAPrimaryQlClass() { result = "LocalVariableAccess" }
/**
@@ -150,7 +154,7 @@ class LocalVariableWriteAccess extends LocalVariableAccess, VariableWriteAccess
class LocalVariableReadAccess extends LocalVariableAccess, VariableReadAccess { }
/** An access to a global variable. */
class GlobalVariableAccess extends VariableAccess, TGlobalVariableAccess {
class GlobalVariableAccess extends VariableAccess instanceof GlobalVariableAccessImpl {
final override string getAPrimaryQlClass() { result = "GlobalVariableAccess" }
}
@@ -161,7 +165,7 @@ class GlobalVariableWriteAccess extends GlobalVariableAccess, VariableWriteAcces
class GlobalVariableReadAccess extends GlobalVariableAccess, VariableReadAccess { }
/** An access to an instance variable. */
class InstanceVariableAccess extends VariableAccess, TInstanceVariableAccess {
class InstanceVariableAccess extends VariableAccess instanceof InstanceVariableAccessImpl {
final override string getAPrimaryQlClass() { result = "InstanceVariableAccess" }
}
@@ -172,7 +176,7 @@ class InstanceVariableWriteAccess extends InstanceVariableAccess, VariableWriteA
class InstanceVariableReadAccess extends InstanceVariableAccess, VariableReadAccess { }
/** An access to a class variable. */
class ClassVariableAccess extends VariableAccess, TClassVariableAccess {
class ClassVariableAccess extends VariableAccess instanceof ClassVariableAccessRealImpl {
final override string getAPrimaryQlClass() { result = "ClassVariableAccess" }
}

View File

@@ -12,7 +12,7 @@ predicate isScopeResolutionMethodCall(Ruby::ScopeResolution g, Ruby::Identifier
not exists(Ruby::Call c | c.getMethod() = g)
}
abstract class CallImpl extends Call {
abstract class CallImpl extends Expr, TCall {
abstract AstNode getArgumentImpl(int n);
/**
@@ -27,10 +27,12 @@ abstract class CallImpl extends Call {
abstract int getNumberOfArgumentsImpl();
}
abstract class MethodCallImpl extends CallImpl, MethodCall {
abstract class MethodCallImpl extends CallImpl, TMethodCall {
abstract AstNode getReceiverImpl();
abstract string getMethodNameImpl();
abstract Block getBlockImpl();
}
class MethodCallSynth extends MethodCallImpl, TMethodCallSynth {
@@ -47,6 +49,8 @@ class MethodCallSynth extends MethodCallImpl, TMethodCallSynth {
final override AstNode getArgumentImpl(int n) { synthChild(this, n + 1, result) and n >= 0 }
final override int getNumberOfArgumentsImpl() { this = TMethodCallSynth(_, _, _, _, result) }
final override Block getBlockImpl() { none() }
}
class IdentifierMethodCall extends MethodCallImpl, TIdentifierMethodCall {
@@ -61,6 +65,8 @@ class IdentifierMethodCall extends MethodCallImpl, TIdentifierMethodCall {
final override Expr getArgumentImpl(int n) { none() }
final override int getNumberOfArgumentsImpl() { result = 0 }
final override Block getBlockImpl() { none() }
}
class ScopeResolutionMethodCall extends MethodCallImpl, TScopeResolutionMethodCall {
@@ -76,6 +82,8 @@ class ScopeResolutionMethodCall extends MethodCallImpl, TScopeResolutionMethodCa
final override Expr getArgumentImpl(int n) { none() }
final override int getNumberOfArgumentsImpl() { result = 0 }
final override Block getBlockImpl() { none() }
}
class RegularMethodCall extends MethodCallImpl, TRegularMethodCall {
@@ -114,7 +122,7 @@ class RegularMethodCall extends MethodCallImpl, TRegularMethodCall {
count(g.getArguments().getChild(_)) + count(g.getMethod().(Ruby::ArgumentList).getChild(_))
}
final override Block getBlock() { toGenerated(result) = g.getBlock() }
final override Block getBlockImpl() { toGenerated(result) = g.getBlock() }
}
class ElementReferenceImpl extends MethodCallImpl, TElementReference {
@@ -129,9 +137,13 @@ class ElementReferenceImpl extends MethodCallImpl, TElementReference {
final override int getNumberOfArgumentsImpl() { result = count(g.getChild(_)) }
final override string getMethodNameImpl() { result = "[]" }
final override Block getBlockImpl() { none() }
}
class TokenSuperCall extends SuperCall, MethodCallImpl, TTokenSuperCall {
abstract class SuperCallImpl extends MethodCallImpl, TSuperCall { }
class TokenSuperCall extends SuperCallImpl, TTokenSuperCall {
private Ruby::Super g;
TokenSuperCall() { this = TTokenSuperCall(g) }
@@ -143,9 +155,11 @@ class TokenSuperCall extends SuperCall, MethodCallImpl, TTokenSuperCall {
final override Expr getArgumentImpl(int n) { none() }
final override int getNumberOfArgumentsImpl() { result = 0 }
final override Block getBlockImpl() { none() }
}
class RegularSuperCall extends SuperCall, MethodCallImpl, TRegularSuperCall {
class RegularSuperCall extends SuperCallImpl, TRegularSuperCall {
private Ruby::Call g;
RegularSuperCall() { this = TRegularSuperCall(g) }
@@ -158,10 +172,14 @@ class RegularSuperCall extends SuperCall, MethodCallImpl, TRegularSuperCall {
final override int getNumberOfArgumentsImpl() { result = count(g.getArguments().getChild(_)) }
final override Block getBlock() { toGenerated(result) = g.getBlock() }
final override Block getBlockImpl() { toGenerated(result) = g.getBlock() }
}
class YieldCallImpl extends CallImpl, YieldCall {
class YieldCallImpl extends CallImpl, TYieldCall {
Ruby::Yield g;
YieldCallImpl() { this = TYieldCall(g) }
final override Expr getArgumentImpl(int n) { toGenerated(result) = g.getChild().getChild(n) }
final override int getNumberOfArgumentsImpl() { result = count(g.getChild().getChild(_)) }

View File

@@ -220,7 +220,7 @@ private string resolveScopeExpr(ConstantReadAccess c, int priority) {
)
}
pragma[noinline]
pragma[nomagic]
private string resolveScopeExprConstantReadAccess(ConstantReadAccess c, int priority, string name) {
result = resolveScopeExpr(c.getScopeExpr(), priority) and
name = c.getName()

View File

@@ -21,6 +21,8 @@ abstract class UnaryOperationImpl extends OperationImpl, MethodCallImpl, TUnaryO
final override Expr getArgumentImpl(int n) { none() }
final override int getNumberOfArgumentsImpl() { result = 0 }
final override Block getBlockImpl() { none() }
}
class UnaryOperationGenerated extends UnaryOperationImpl {
@@ -77,6 +79,8 @@ abstract class BinaryOperationImpl extends OperationImpl, MethodCallImpl, TBinar
final override Expr getArgumentImpl(int n) { n = 0 and result = this.getRightOperandImpl() }
final override int getNumberOfArgumentsImpl() { result = 1 }
final override Block getBlockImpl() { none() }
}
class BinaryOperationReal extends BinaryOperationImpl {

View File

@@ -34,10 +34,10 @@ module Ruby {
/** A token. */
class Token extends @ruby_token, AstNode {
/** Gets the value of this token. */
string getValue() { ruby_tokeninfo(this, _, _, _, result, _) }
string getValue() { ruby_tokeninfo(this, _, result, _) }
/** Gets the location of this token. */
override Location getLocation() { ruby_tokeninfo(this, _, _, _, _, result) }
override Location getLocation() { ruby_tokeninfo(this, _, _, result) }
/** Gets a string representation of this element. */
override string toString() { result = getValue() }
@@ -1875,10 +1875,10 @@ module Erb {
/** A token. */
class Token extends @erb_token, AstNode {
/** Gets the value of this token. */
string getValue() { erb_tokeninfo(this, _, _, _, result, _) }
string getValue() { erb_tokeninfo(this, _, result, _) }
/** Gets the location of this token. */
override Location getLocation() { erb_tokeninfo(this, _, _, _, _, result) }
override Location getLocation() { erb_tokeninfo(this, _, _, result) }
/** Gets a string representation of this element. */
override string toString() { result = getValue() }

View File

@@ -282,13 +282,13 @@ private module Cached {
}
cached
predicate access(Ruby::Identifier access, VariableReal::Range variable) {
predicate access(Ruby::Identifier access, VariableReal variable) {
exists(string name |
variable.getName() = name and
variable.getNameImpl() = name and
name = access.getValue()
|
variable.getDeclaringScope() = scopeOf(access) and
not access.getLocation().strictlyBefore(variable.getLocation()) and
variable.getDeclaringScopeImpl() = scopeOf(access) and
not access.getLocation().strictlyBefore(variable.getLocationImpl()) and
// In case of overlapping parameter names, later parameters should not
// be considered accesses to the first parameter
if parameterAssignment(_, _, access)
@@ -296,7 +296,7 @@ private module Cached {
else any()
or
exists(Scope::Range declScope |
variable.getDeclaringScope() = declScope and
variable.getDeclaringScopeImpl() = declScope and
inherits(scopeOf(access), name, declScope)
)
)
@@ -366,126 +366,117 @@ private predicate inherits(Scope::Range scope, string name, Scope::Range outer)
)
}
class TVariableReal = TGlobalVariable or TClassVariable or TInstanceVariable or TLocalVariableReal;
abstract class VariableImpl extends TVariable {
abstract string getNameImpl();
module VariableReal {
class Range extends TVariableReal {
abstract string getName();
final string toString() { result = this.getNameImpl() }
string toString() { result = this.getName() }
abstract Location getLocation();
abstract Scope::Range getDeclaringScope();
}
abstract Location getLocationImpl();
}
class TVariableReal = TGlobalVariable or TClassVariable or TInstanceVariable or TLocalVariableReal;
class TLocalVariable = TLocalVariableReal or TLocalVariableSynth;
module LocalVariable {
class Range extends VariableReal::Range, TLocalVariableReal {
private Scope::Range scope;
private string name;
private Ruby::Identifier i;
/**
* This class only exists to avoid negative recursion warnings. Ideally,
* we would use `VariableImpl` directly, but that results in incorrect
* negative recursion warnings. Adding new root-defs for the predicates
* below works around this.
*/
abstract class VariableReal extends TVariableReal {
abstract string getNameImpl();
Range() { this = TLocalVariableReal(scope, name, i) }
abstract Location getLocationImpl();
final override string getName() { result = name }
abstract Scope::Range getDeclaringScopeImpl();
final override Location getLocation() { result = i.getLocation() }
final override Scope::Range getDeclaringScope() { result = scope }
final VariableAccess getDefiningAccess() { toGenerated(result) = i }
}
final string toString() { result = this.getNameImpl() }
}
class VariableReal extends Variable, TVariableReal instanceof VariableReal::Range {
final override string getName() { result = VariableReal::Range.super.getName() }
// Convert extensions of `VariableReal` into extensions of `VariableImpl`
private class VariableRealAdapter extends VariableImpl, TVariableReal instanceof VariableReal {
final override string getNameImpl() { result = VariableReal.super.getNameImpl() }
final override Location getLocation() { result = VariableReal::Range.super.getLocation() }
final override Scope getDeclaringScope() {
toGenerated(result) = VariableReal::Range.super.getDeclaringScope()
}
final override Location getLocationImpl() { result = VariableReal.super.getLocationImpl() }
}
class LocalVariableReal extends VariableReal, LocalVariable, TLocalVariableReal instanceof LocalVariable::Range {
final override LocalVariableAccessReal getAnAccess() { result.getVariable() = this }
class LocalVariableReal extends VariableReal, TLocalVariableReal {
private Scope::Range scope;
private string name;
private Ruby::Identifier i;
final override VariableAccess getDefiningAccess() {
result = LocalVariable::Range.super.getDefiningAccess()
}
LocalVariableReal() { this = TLocalVariableReal(scope, name, i) }
final override string getNameImpl() { result = name }
final override Location getLocationImpl() { result = i.getLocation() }
final override Scope::Range getDeclaringScopeImpl() { result = scope }
final VariableAccess getDefiningAccessImpl() { toGenerated(result) = i }
}
class LocalVariableSynth extends LocalVariable, TLocalVariableSynth {
class LocalVariableSynth extends VariableImpl, TLocalVariableSynth {
private AstNode n;
private int i;
LocalVariableSynth() { this = TLocalVariableSynth(n, i) }
final override string getName() {
final override string getNameImpl() {
exists(int level | level = desugarLevel(n) |
if level > 0 then result = "__synth__" + i + "__" + level else result = "__synth__" + i
)
}
final override Location getLocation() { result = n.getLocation() }
final override Scope getDeclaringScope() { none() } // not relevant for synthesized variables
final override Location getLocationImpl() { result = n.getLocation() }
}
module GlobalVariable {
class Range extends VariableReal::Range, TGlobalVariable {
private string name;
class GlobalVariableImpl extends VariableReal, TGlobalVariable {
private string name;
Range() { this = TGlobalVariable(name) }
GlobalVariableImpl() { this = TGlobalVariable(name) }
final override string getName() { result = name }
final override string getNameImpl() { result = name }
final override Location getLocation() { none() }
final override Location getLocationImpl() { none() }
final override Scope::Range getDeclaringScope() { none() }
}
final override Scope::Range getDeclaringScopeImpl() { none() }
}
module InstanceVariable {
class Range extends VariableReal::Range, TInstanceVariable {
private ModuleBase::Range scope;
private boolean instance;
private string name;
private Ruby::AstNode decl;
class InstanceVariableImpl extends VariableReal, TInstanceVariable {
private ModuleBase::Range scope;
private boolean instance;
private string name;
private Ruby::AstNode decl;
Range() { this = TInstanceVariable(scope, name, instance, decl) }
InstanceVariableImpl() { this = TInstanceVariable(scope, name, instance, decl) }
final override string getName() { result = name }
final override string getNameImpl() { result = name }
final predicate isClassInstanceVariable() { instance = false }
final predicate isClassInstanceVariable() { instance = false }
final override Location getLocation() { result = decl.getLocation() }
final override Location getLocationImpl() { result = decl.getLocation() }
final override Scope::Range getDeclaringScope() { result = scope }
}
final override Scope::Range getDeclaringScopeImpl() { result = scope }
}
module ClassVariable {
class Range extends VariableReal::Range, TClassVariable {
private ModuleBase::Range scope;
private string name;
private Ruby::AstNode decl;
class ClassVariableImpl extends VariableReal, TClassVariable {
private ModuleBase::Range scope;
private string name;
private Ruby::AstNode decl;
Range() { this = TClassVariable(scope, name, decl) }
ClassVariableImpl() { this = TClassVariable(scope, name, decl) }
final override string getName() { result = name }
final override string getNameImpl() { result = name }
final override Location getLocation() { result = decl.getLocation() }
final override Location getLocationImpl() { result = decl.getLocation() }
final override Scope::Range getDeclaringScope() { result = scope }
}
final override Scope::Range getDeclaringScopeImpl() { result = scope }
}
abstract class VariableAccessImpl extends VariableAccess {
abstract Variable getVariableImpl();
abstract class VariableAccessImpl extends Expr, TVariableAccess {
abstract VariableImpl getVariableImpl();
}
module LocalVariableAccess {
@@ -505,8 +496,9 @@ class TVariableAccessReal =
TLocalVariableAccessReal or TGlobalVariableAccess or TInstanceVariableAccess or
TClassVariableAccess;
private class LocalVariableAccessReal extends VariableAccessImpl, LocalVariableAccess,
TLocalVariableAccessReal {
abstract class LocalVariableAccessImpl extends VariableAccessImpl, TLocalVariableAccess { }
private class LocalVariableAccessReal extends LocalVariableAccessImpl, TLocalVariableAccessReal {
private Ruby::Identifier g;
private LocalVariable v;
@@ -517,8 +509,7 @@ private class LocalVariableAccessReal extends VariableAccessImpl, LocalVariableA
final override string toString() { result = g.getValue() }
}
private class LocalVariableAccessSynth extends VariableAccessImpl, LocalVariableAccess,
TLocalVariableAccessSynth {
private class LocalVariableAccessSynth extends LocalVariableAccessImpl, TLocalVariableAccessSynth {
private LocalVariable v;
LocalVariableAccessSynth() { this = TLocalVariableAccessSynth(_, _, v) }
@@ -529,11 +520,12 @@ private class LocalVariableAccessSynth extends VariableAccessImpl, LocalVariable
}
module GlobalVariableAccess {
predicate range(Ruby::GlobalVariable n, GlobalVariable v) { n.getValue() = v.getName() }
predicate range(Ruby::GlobalVariable n, GlobalVariableImpl v) { n.getValue() = v.getNameImpl() }
}
private class GlobalVariableAccessReal extends GlobalVariableAccess, VariableAccessImpl,
TGlobalVariableAccessReal {
abstract class GlobalVariableAccessImpl extends VariableAccessImpl, TGlobalVariableAccess { }
private class GlobalVariableAccessReal extends GlobalVariableAccessImpl, TGlobalVariableAccessReal {
private Ruby::GlobalVariable g;
private GlobalVariable v;
@@ -544,8 +536,7 @@ private class GlobalVariableAccessReal extends GlobalVariableAccess, VariableAcc
final override string toString() { result = g.getValue() }
}
private class GlobalVariableAccessSynth extends GlobalVariableAccess, VariableAccessImpl,
TGlobalVariableAccessSynth {
private class GlobalVariableAccessSynth extends GlobalVariableAccessImpl, TGlobalVariableAccessSynth {
private GlobalVariable v;
GlobalVariableAccessSynth() { this = TGlobalVariableAccessSynth(_, _, v) }
@@ -559,7 +550,9 @@ module InstanceVariableAccess {
predicate range(Ruby::InstanceVariable n, InstanceVariable v) { instanceVariableAccess(n, v) }
}
private class InstanceVariableAccessReal extends InstanceVariableAccess, VariableAccessImpl,
abstract class InstanceVariableAccessImpl extends VariableAccessImpl, TInstanceVariableAccess { }
private class InstanceVariableAccessReal extends InstanceVariableAccessImpl,
TInstanceVariableAccessReal {
private Ruby::InstanceVariable g;
private InstanceVariable v;
@@ -571,7 +564,7 @@ private class InstanceVariableAccessReal extends InstanceVariableAccess, Variabl
final override string toString() { result = g.getValue() }
}
private class InstanceVariableAccessSynth extends InstanceVariableAccess, VariableAccessImpl,
private class InstanceVariableAccessSynth extends InstanceVariableAccessImpl,
TInstanceVariableAccessSynth {
private InstanceVariable v;
@@ -586,8 +579,9 @@ module ClassVariableAccess {
predicate range(Ruby::ClassVariable n, ClassVariable v) { classVariableAccess(n, v) }
}
private class ClassVariableAccessReal extends ClassVariableAccess, VariableAccessImpl,
TClassVariableAccessReal {
abstract class ClassVariableAccessRealImpl extends VariableAccessImpl, TClassVariableAccess { }
private class ClassVariableAccessReal extends ClassVariableAccessRealImpl, TClassVariableAccessReal {
private Ruby::ClassVariable g;
private ClassVariable v;
@@ -598,7 +592,7 @@ private class ClassVariableAccessReal extends ClassVariableAccess, VariableAcces
final override string toString() { result = g.getValue() }
}
private class ClassVariableAccessSynth extends ClassVariableAccess, VariableAccessImpl,
private class ClassVariableAccessSynth extends ClassVariableAccessRealImpl,
TClassVariableAccessSynth {
private ClassVariable v;

View File

@@ -2,6 +2,7 @@
private import codeql.ruby.AST
private import codeql.ruby.controlflow.BasicBlocks
private import codeql.ruby.dataflow.SSA
private import ControlFlowGraph
private import internal.ControlFlowGraphImpl
private import internal.Splitting
@@ -98,6 +99,16 @@ class ExprCfgNode extends AstCfgNode {
/** Gets the underlying expression. */
Expr getExpr() { result = e }
private ExprCfgNode getSource() {
exists(Ssa::WriteDefinition def |
def.assigns(result) and
this = def.getARead()
)
}
/** Gets the textual (constant) value of this expression, if any. */
string getValueText() { result = this.getSource().getValueText() }
}
/** A control-flow node that wraps a return-like statement. */
@@ -164,7 +175,19 @@ abstract private class ExprChildMapping extends Expr {
/** Provides classes for control-flow nodes that wrap AST expressions. */
module ExprNodes {
// TODO: Add more classes
private class LiteralChildMapping extends ExprChildMapping, Literal {
override predicate relevantChild(Expr e) { none() }
}
/** A control-flow node that wraps an `ArrayLiteral` AST expression. */
class LiteralCfgNode extends ExprCfgNode {
override LiteralChildMapping e;
override Literal getExpr() { result = super.getExpr() }
override string getValueText() { result = e.getValueText() }
}
private class AssignExprChildMapping extends ExprChildMapping, AssignExpr {
override predicate relevantChild(Expr e) { e = this.getAnOperand() }
}
@@ -209,6 +232,49 @@ module ExprNodes {
/** Gets the right operand of this binary operation. */
final ExprCfgNode getRightOperand() { e.hasCfgChild(bo.getRightOperand(), this, result) }
final override string getValueText() {
exists(string left, string right, string op |
left = this.getLeftOperand().getValueText() and
right = this.getRightOperand().getValueText() and
op = this.getExpr().getOperator()
|
op = "+" and
(
result = (left.toInt() + right.toInt()).toString()
or
not (exists(left.toInt()) and exists(right.toInt())) and
result = (left.toFloat() + right.toFloat()).toString()
or
not (exists(left.toFloat()) and exists(right.toFloat())) and
result = left + right
)
or
op = "-" and
(
result = (left.toInt() - right.toInt()).toString()
or
not (exists(left.toInt()) and exists(right.toInt())) and
result = (left.toFloat() - right.toFloat()).toString()
)
or
op = "*" and
(
result = (left.toInt() * right.toInt()).toString()
or
not (exists(left.toInt()) and exists(right.toInt())) and
result = (left.toFloat() * right.toFloat()).toString()
)
or
op = "/" and
(
result = (left.toInt() / right.toInt()).toString()
or
not (exists(left.toInt()) and exists(right.toInt())) and
result = (left.toFloat() / right.toFloat()).toString()
)
)
}
}
private class BlockArgumentChildMapping extends ExprChildMapping, BlockArgument {

View File

@@ -180,38 +180,42 @@ private module Cached {
cached
CfgScope getTarget(CfgNodes::ExprNodes::CallCfgNode call) {
exists(string method |
exists(Module tp |
instanceMethodCall(call, tp, method) and
result = lookupMethod(tp, method) and
if result.(Method).isPrivate()
then
exists(Self self |
self = call.getReceiver().getExpr() and
pragma[only_bind_out](self.getEnclosingModule().getModule().getSuperClass*()) =
pragma[only_bind_out](result.getEnclosingModule().getModule())
) and
// For now, we restrict the scope of top-level declarations to their file.
// This may remove some plausible targets, but also removes a lot of
// implausible targets
if result.getEnclosingModule() instanceof Toplevel
then result.getFile() = call.getFile()
// Temporarily disable operation resolution (due to bad performance)
not call.getExpr() instanceof Operation and
(
exists(string method |
exists(Module tp |
instanceMethodCall(call, tp, method) and
result = lookupMethod(tp, method) and
if result.(Method).isPrivate()
then
exists(Self self |
self = call.getReceiver().getExpr() and
pragma[only_bind_out](self.getEnclosingModule().getModule().getSuperClass*()) =
pragma[only_bind_out](result.getEnclosingModule().getModule())
) and
// For now, we restrict the scope of top-level declarations to their file.
// This may remove some plausible targets, but also removes a lot of
// implausible targets
if result.getEnclosingModule() instanceof Toplevel
then result.getFile() = call.getFile()
else any()
else any()
else any()
)
or
exists(DataFlow::LocalSourceNode sourceNode |
methodCall(call, sourceNode, method) and
sourceNode = trackSingletonMethod(result, method)
)
)
or
exists(DataFlow::LocalSourceNode sourceNode |
methodCall(call, sourceNode, method) and
sourceNode = trackSingletonMethod(result, method)
exists(Module superClass, string method |
superCall(call, superClass, method) and
result = lookupMethod(superClass, method)
)
or
result = yieldCall(call)
)
or
exists(Module superClass, string method |
superCall(call, superClass, method) and
result = lookupMethod(superClass, method)
)
or
result = yieldCall(call)
}
}
@@ -432,7 +436,7 @@ DataFlowCallable viableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
* Holds if `e` is an `ExprNode` that may be returned by a call to `c`.
*/
predicate exprNodeReturnedFrom(DataFlow::ExprNode e, Callable c) {
exists(ReturnNode r |
exists(ReturningNode r |
r.getEnclosingCallable().asCallable() = c and
(
r.(ExplicitReturnNode).getReturningNode().getReturnedValueNode() = e.asExpr() or

View File

@@ -46,18 +46,19 @@ module LocalFlow {
)
}
/** Gets the SSA definition node corresponding to parameter `p`. */
SsaDefinitionNode getParameterDefNode(NamedParameter p) {
exists(BasicBlock bb, int i |
bb.getNode(i).getNode() = p.getDefiningAccess() and
result.getDefinition().definesAt(_, bb, i)
)
}
/**
* Holds if there is a local flow step from `nodeFrom` to `nodeTo` involving
* SSA definition `def.
* SSA definition `def`.
*/
predicate localSsaFlowStep(Ssa::Definition def, Node nodeFrom, Node nodeTo) {
// Flow from parameter into SSA definition
exists(BasicBlock bb, int i |
bb.getNode(i).getNode() =
nodeFrom.(ParameterNode).getParameter().(NamedParameter).getDefiningAccess() and
nodeTo.(SsaDefinitionNode).getDefinition().definesAt(_, bb, i)
)
or
// Flow from assignment into SSA definition
def.(Ssa::WriteDefinition).assigns(nodeFrom.asExpr()) and
nodeTo.(SsaDefinitionNode).getDefinition() = def
@@ -114,6 +115,12 @@ private module Cached {
newtype TNode =
TExprNode(CfgNodes::ExprCfgNode n) or
TReturningNode(CfgNodes::ReturningCfgNode n) or
TSynthReturnNode(CfgScope scope, ReturnKind kind) {
exists(ReturningNode ret |
ret.(NodeImpl).getCfgScope() = scope and
ret.getKind() = kind
)
} or
TSsaDefinitionNode(Ssa::Definition def) or
TNormalParameterNode(Parameter p) { not p instanceof BlockParameter } or
TSelfParameterNode(MethodBase m) or
@@ -139,21 +146,14 @@ private module Cached {
class TParameterNode =
TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or TSummaryParameterNode;
/**
* This is the local flow predicate that is used as a building block in global
* data flow. It excludes SSA flow through instance fields, as flow through fields
* is handled by the global data-flow library, but includes various other steps
* that are only relevant for global flow.
*/
cached
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
exists(Ssa::Definition def | LocalFlow::localSsaFlowStep(def, nodeFrom, nodeTo))
private predicate defaultValueFlow(NamedParameter p, ExprNode e) {
p.(OptionalParameter).getDefaultValue() = e.getExprNode().getExpr()
or
nodeTo.(ParameterNode).getParameter().(OptionalParameter).getDefaultValue() =
nodeFrom.asExpr().getExpr()
or
nodeTo.(ParameterNode).getParameter().(KeywordParameter).getDefaultValue() =
nodeFrom.asExpr().getExpr()
p.(KeywordParameter).getDefaultValue() = e.getExprNode().getExpr()
}
private predicate localFlowStepCommon(Node nodeFrom, Node nodeTo) {
LocalFlow::localSsaFlowStep(_, nodeFrom, nodeTo)
or
nodeFrom.(SelfParameterNode).getMethod() = nodeTo.asExpr().getExpr().getEnclosingCallable() and
nodeTo.asExpr().getExpr() instanceof Self
@@ -186,12 +186,66 @@ private module Cached {
) and
nodeFrom.asExpr() = for.getValue()
)
}
/**
* This is the local flow predicate that is used as a building block in global
* data flow.
*/
cached
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
localFlowStepCommon(nodeFrom, nodeTo)
or
defaultValueFlow(nodeTo.(ParameterNode).getParameter(), nodeFrom)
or
nodeTo = LocalFlow::getParameterDefNode(nodeFrom.(ParameterNode).getParameter())
or
nodeTo.(SynthReturnNode).getAnInput() = nodeFrom
or
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, true)
}
/** This is the local flow predicate that is exposed. */
cached
predicate isLocalSourceNode(Node n) { not simpleLocalFlowStep+(any(ExprNode e), n) }
predicate localFlowStepImpl(Node nodeFrom, Node nodeTo) {
localFlowStepCommon(nodeFrom, nodeTo)
or
defaultValueFlow(nodeTo.(ParameterNode).getParameter(), nodeFrom)
or
nodeTo = LocalFlow::getParameterDefNode(nodeFrom.(ParameterNode).getParameter())
or
// Simple flow through library code is included in the exposed local
// step relation, even though flow is technically inter-procedural
FlowSummaryImpl::Private::Steps::summaryThroughStep(nodeFrom, nodeTo, true)
}
/** This is the local flow predicate that is used in type tracking. */
cached
predicate localFlowStepTypeTracker(Node nodeFrom, Node nodeTo) {
localFlowStepCommon(nodeFrom, nodeTo)
or
exists(NamedParameter p |
defaultValueFlow(p, nodeFrom) and
nodeTo = LocalFlow::getParameterDefNode(p)
)
}
cached
predicate isLocalSourceNode(Node n) {
n instanceof ParameterNode
or
// This case should not be needed once we have proper use-use flow
// for `self`. At that point, the `self`s returned by `trackInstance`
// in `DataFlowDispatch.qll` should refer to the post-update node,
// and we can remove this case.
n instanceof SelfArgumentNode
or
not localFlowStepTypeTracker+(any(Node e |
e instanceof ExprNode
or
e instanceof ParameterNode
), n)
}
cached
newtype TContent = TTodoContent() // stub
@@ -208,6 +262,8 @@ predicate nodeIsHidden(Node n) {
n instanceof SummaryNode
or
n instanceof SummaryParameterNode
or
n instanceof SynthReturnNode
}
/** An SSA definition, viewed as a node in a data flow graph. */
@@ -234,7 +290,7 @@ class SsaDefinitionNode extends NodeImpl, TSsaDefinitionNode {
* `ControlFlow::Node`s.
*/
class ReturningStatementNode extends NodeImpl, TReturningNode {
private CfgNodes::ReturningCfgNode n;
CfgNodes::ReturningCfgNode n;
ReturningStatementNode() { this = TReturningNode(n) }
@@ -436,6 +492,12 @@ private module ArgumentNodes {
import ArgumentNodes
/** A data-flow node that represents a value syntactically returned by a callable. */
abstract class ReturningNode extends Node {
/** Gets the kind of this return node. */
abstract ReturnKind getKind();
}
/** A data-flow node that represents a value returned by a callable. */
abstract class ReturnNode extends Node {
/** Gets the kind of this return node. */
@@ -463,11 +525,9 @@ private module ReturnNodes {
* A data-flow node that represents an expression returned by a callable,
* either using an explict `return` statement or as the expression of a method body.
*/
class ExplicitReturnNode extends ReturnNode, ReturningStatementNode {
private CfgNodes::ReturningCfgNode n;
class ExplicitReturnNode extends ReturningNode, ReturningStatementNode {
ExplicitReturnNode() {
isValid(this.getReturningNode()) and
isValid(n) and
n.getASuccessor().(CfgNodes::AnnotatedExitNode).isNormal() and
n.getScope() instanceof Callable
}
@@ -479,7 +539,7 @@ private module ReturnNodes {
}
}
class ExprReturnNode extends ReturnNode, ExprNode {
class ExprReturnNode extends ReturningNode, ExprNode {
ExprReturnNode() {
this.getExprNode().getASuccessor().(CfgNodes::AnnotatedExitNode).isNormal() and
this.(NodeImpl).getCfgScope() instanceof Callable
@@ -488,6 +548,34 @@ private module ReturnNodes {
override ReturnKind getKind() { result instanceof NormalReturnKind }
}
/**
* A synthetic data-flow node for joining flow from different syntactic
* returns into a single node.
*
* This node only exists to avoid computing the product of a large fan-in
* with a large fan-out.
*/
class SynthReturnNode extends NodeImpl, ReturnNode, TSynthReturnNode {
private CfgScope scope;
private ReturnKind kind;
SynthReturnNode() { this = TSynthReturnNode(scope, kind) }
/** Gets a syntactic return node that flows into this synthetic node. */
ReturningNode getAnInput() {
result.(NodeImpl).getCfgScope() = scope and
result.getKind() = kind
}
override ReturnKind getKind() { result = kind }
override CfgScope getCfgScope() { result = scope }
override Location getLocationImpl() { result = scope.getLocation() }
override string toStringImpl() { result = "return " + kind + " in " + scope }
}
private class SummaryReturnNode extends SummaryNode, ReturnNode {
private ReturnKind rk;
@@ -631,7 +719,7 @@ private import PostUpdateNodes
/** A node that performs a type cast. */
class CastNode extends Node {
CastNode() { none() }
CastNode() { this instanceof ReturningNode }
}
class DataFlowExpr = CfgNodes::ExprCfgNode;

View File

@@ -120,7 +120,7 @@ predicate hasLocalSource(Node sink, Node source) {
or
exists(Node mid |
hasLocalSource(mid, source) and
simpleLocalFlowStep(mid, sink)
localFlowStepTypeTracker(mid, sink)
)
}
@@ -136,13 +136,7 @@ ParameterNode parameterNode(Parameter p) { result.getParameter() = p }
* Holds if data flows from `nodeFrom` to `nodeTo` in exactly one local
* (intra-procedural) step.
*/
predicate localFlowStep(Node nodeFrom, Node nodeTo) {
simpleLocalFlowStep(nodeFrom, nodeTo)
or
// Simple flow through library code is included in the exposed local
// step relation, even though flow is technically inter-procedural
FlowSummaryImpl::Private::Steps::summaryThroughStep(nodeFrom, nodeTo, true)
}
predicate localFlowStep = localFlowStepImpl/2;
/**
* Holds if data flows from `source` to `sink` in zero or more local

View File

@@ -28,7 +28,7 @@ private class ApplicationControllerAccess extends ConstantReadAccess {
* class FooController < ActionController::Base
* def delete_handler
* uid = params[:id]
* User.delete_all("id = ?", uid)
* User.delete_by("id = ?", uid)
* end
* end
* ```

View File

@@ -68,16 +68,24 @@ private Expr sqlFragmentArgument(MethodCall call) {
(
methodName =
[
"delete_all", "destroy_all", "exists?", "find_by", "find_by_sql", "from", "group",
"having", "joins", "lock", "not", "order", "pluck", "where"
"delete_all", "delete_by", "destroy_all", "destroy_by", "exists?", "find_by", "find_by!",
"find_or_create_by", "find_or_create_by!", "find_or_initialize_by", "find_by_sql", "from",
"group", "having", "joins", "lock", "not", "order", "pluck", "where", "rewhere", "select",
"reselect", "update_all"
] and
result = call.getArgument(0)
or
methodName = "calculate" and result = call.getArgument(1)
or
methodName in ["average", "count", "maximum", "minimum", "sum"] and
result = call.getArgument(0)
or
// This format was supported until Rails 2.3.8
methodName = ["all", "find", "first", "last"] and
result = call.getKeywordArgument("conditions")
or
methodName = "reload" and
result = call.getKeywordArgument("lock")
)
)
}
@@ -118,7 +126,6 @@ class PotentiallyUnsafeSqlExecutingMethodCall extends ActiveRecordModelClassMeth
// The SQL fragment argument itself
private Expr sqlFragmentExpr;
// TODO: `find` with `lock:` option also takes an SQL fragment
PotentiallyUnsafeSqlExecutingMethodCall() {
exists(Expr arg |
arg = sqlFragmentArgument(this) and

View File

@@ -36,13 +36,10 @@ private DataFlow::Node fileInstanceInstantiation() {
result = API::getTopLevelMember("File").getAMethodCall("open")
or
// Calls to `Kernel.open` can yield `File` instances
exists(KernelMethodCall c |
c = result.asExpr().getExpr() and
c.getMethodName() = "open" and
// Assume that calls that don't invoke shell commands will instead open
// a file.
not pathArgSpawnsSubprocess(c.getArgument(0))
)
result.(KernelMethodCall).getMethodName() = "open" and
// Assume that calls that don't invoke shell commands will instead open
// a file.
not pathArgSpawnsSubprocess(result.(KernelMethodCall).getArgument(0).asExpr().getExpr())
}
private DataFlow::Node fileInstance() {

View File

@@ -1,8 +0,0 @@
/**
* Helper file that imports all HTTP clients.
*/
private import codeql.ruby.frameworks.http_clients.NetHTTP
private import codeql.ruby.frameworks.http_clients.Excon
private import codeql.ruby.frameworks.http_clients.Faraday
private import codeql.ruby.frameworks.http_clients.RestClient

View File

@@ -0,0 +1,12 @@
/**
* Helper file that imports all HTTP clients.
*/
private import codeql.ruby.frameworks.http_clients.NetHttp
private import codeql.ruby.frameworks.http_clients.Excon
private import codeql.ruby.frameworks.http_clients.Faraday
private import codeql.ruby.frameworks.http_clients.RestClient
private import codeql.ruby.frameworks.http_clients.Httparty
private import codeql.ruby.frameworks.http_clients.HttpClient
private import codeql.ruby.frameworks.http_clients.OpenURI
private import codeql.ruby.frameworks.http_clients.Typhoeus

View File

@@ -8,20 +8,27 @@ private import codeql.ruby.ApiGraphs
* in every Ruby object. In addition, its module methods can be called by
* providing a specific receiver as in `Kernel.exit`.
*/
class KernelMethodCall extends MethodCall {
class KernelMethodCall extends DataFlow::CallNode {
private MethodCall methodCall;
KernelMethodCall() {
this = API::getTopLevelMember("Kernel").getAMethodCall(_).asExpr().getExpr()
or
// we assume that if there's no obvious target for this method call
// and the method name matches a Kernel method, then it is a Kernel method call.
// TODO: ApiGraphs should ideally handle this case
not exists(this.(Call).getATarget()) and
methodCall = this.asExpr().getExpr() and
(
this.getReceiver() instanceof Self and isPrivateKernelMethod(this.getMethodName())
this = API::getTopLevelMember("Kernel").getAMethodCall(_)
or
isPublicKernelMethod(this.getMethodName())
methodCall instanceof UnknownMethodCall and
(
this.getReceiver().asExpr().getExpr() instanceof Self and
isPrivateKernelMethod(methodCall.getMethodName())
or
isPublicKernelMethod(methodCall.getMethodName())
)
)
}
string getMethodName() { result = methodCall.getMethodName() }
int getNumberOfArguments() { result = methodCall.getNumberOfArguments() }
}
/**
@@ -58,6 +65,45 @@ private predicate isPrivateKernelMethod(string method) {
]
}
/**
* Instance methods on `BasicObject`, which are available to all classes.
*/
class BasicObjectInstanceMethodCall extends UnknownMethodCall {
BasicObjectInstanceMethodCall() {
this.getMethodName() in [
"equal?", "instance_eval", "instance_exec", "method_missing", "singleton_method_added",
"singleton_method_removed", "singleton_method_undefined"
]
}
}
/**
* Instance methods on `Object`, which are available to all classes except `BasicObject`.
*/
class ObjectInstanceMethodCall extends UnknownMethodCall {
ObjectInstanceMethodCall() {
this.getMethodName() in [
"!~", "<=>", "===", "=~", "callable_methods", "define_singleton_method", "display",
"do_until", "do_while", "dup", "enum_for", "eql?", "extend", "f", "freeze", "h", "hash",
"inspect", "instance_of?", "instance_variable_defined?", "instance_variable_get",
"instance_variable_set", "instance_variables", "is_a?", "itself", "kind_of?",
"matching_methods", "method", "method_missing", "methods", "nil?", "object_id",
"private_methods", "protected_methods", "public_method", "public_methods", "public_send",
"remove_instance_variable", "respond_to?", "respond_to_missing?", "send",
"shortest_abbreviation", "singleton_class", "singleton_method", "singleton_methods",
"taint", "tainted?", "to_enum", "to_s", "trust", "untaint", "untrust", "untrusted?"
]
}
}
/**
* Method calls which have no known target.
* These will typically be calls to methods inherited from a superclass.
*/
class UnknownMethodCall extends MethodCall {
UnknownMethodCall() { not exists(this.(Call).getATarget()) }
}
/**
* A system command executed via subshell literal syntax.
* E.g.
@@ -125,19 +171,14 @@ class SubshellHeredocExecution extends SystemCommandExecution::Range {
* ```
* Ruby documentation: https://docs.ruby-lang.org/en/3.0.0/Kernel.html#method-i-system
*/
class KernelSystemCall extends SystemCommandExecution::Range {
KernelMethodCall methodCall;
class KernelSystemCall extends SystemCommandExecution::Range, KernelMethodCall {
KernelSystemCall() { this.getMethodName() = "system" }
KernelSystemCall() {
methodCall.getMethodName() = "system" and
this.asExpr().getExpr() = methodCall
}
override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = methodCall.getAnArgument() }
override DataFlow::Node getAnArgument() { result = this.getArgument(_) }
override predicate isShellInterpreted(DataFlow::Node arg) {
// Kernel.system invokes a subshell if you provide a single string as argument
methodCall.getNumberOfArguments() = 1 and arg.asExpr().getExpr() = methodCall.getAnArgument()
this.getNumberOfArguments() = 1 and arg = getAnArgument()
}
}
@@ -146,19 +187,14 @@ class KernelSystemCall extends SystemCommandExecution::Range {
* `Kernel.exec` takes the same argument forms as `Kernel.system`. See `KernelSystemCall` for details.
* Ruby documentation: https://docs.ruby-lang.org/en/3.0.0/Kernel.html#method-i-exec
*/
class KernelExecCall extends SystemCommandExecution::Range {
KernelMethodCall methodCall;
class KernelExecCall extends SystemCommandExecution::Range, KernelMethodCall {
KernelExecCall() { this.getMethodName() = "exec" }
KernelExecCall() {
methodCall.getMethodName() = "exec" and
this.asExpr().getExpr() = methodCall
}
override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = methodCall.getAnArgument() }
override DataFlow::Node getAnArgument() { result = this.getArgument(_) }
override predicate isShellInterpreted(DataFlow::Node arg) {
// Kernel.exec invokes a subshell if you provide a single string as argument
methodCall.getNumberOfArguments() = 1 and arg.asExpr().getExpr() = methodCall.getAnArgument()
this.getNumberOfArguments() = 1 and arg = getAnArgument()
}
}
@@ -169,22 +205,17 @@ class KernelExecCall extends SystemCommandExecution::Range {
* Ruby documentation: https://docs.ruby-lang.org/en/3.0.0/Kernel.html#method-i-spawn
* TODO: document and handle the env and option arguments.
* ```
* spawn([env,] command... [,options]) pid
* spawn([env,] command... [,options]) -> pid
* ```
*/
class KernelSpawnCall extends SystemCommandExecution::Range {
KernelMethodCall methodCall;
class KernelSpawnCall extends SystemCommandExecution::Range, KernelMethodCall {
KernelSpawnCall() { this.getMethodName() = "spawn" }
KernelSpawnCall() {
methodCall.getMethodName() = "spawn" and
this.asExpr().getExpr() = methodCall
}
override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = methodCall.getAnArgument() }
override DataFlow::Node getAnArgument() { result = this.getArgument(_) }
override predicate isShellInterpreted(DataFlow::Node arg) {
// Kernel.spawn invokes a subshell if you provide a single string as argument
methodCall.getNumberOfArguments() = 1 and arg.asExpr().getExpr() = methodCall.getAnArgument()
this.getNumberOfArguments() = 1 and arg = getAnArgument()
}
}
@@ -241,37 +272,62 @@ class Open3PipelineCall extends SystemCommandExecution::Range {
}
/**
* A call to `Kernel.eval`, which executes its argument as Ruby code.
* A call to `Kernel.eval`, which executes its first argument as Ruby code.
* ```ruby
* a = 1
* Kernel.eval("a = 2")
* a # => 2
* ```
*/
class EvalCallCodeExecution extends CodeExecution::Range {
KernelMethodCall methodCall;
class EvalCallCodeExecution extends CodeExecution::Range, KernelMethodCall {
EvalCallCodeExecution() { this.getMethodName() = "eval" }
EvalCallCodeExecution() {
this.asExpr().getExpr() = methodCall and methodCall.getMethodName() = "eval"
}
override DataFlow::Node getCode() { result.asExpr().getExpr() = methodCall.getAnArgument() }
override DataFlow::Node getCode() { result = this.getArgument(0) }
}
/**
* A call to `Kernel#send`, which executes its arguments as a Ruby method call.
* A call to `Kernel#send`, which executes its first argument as a Ruby method call.
* ```ruby
* arr = []
* arr.send("push", 1)
* arr # => [1]
* ```
*/
class SendCallCodeExecution extends CodeExecution::Range {
KernelMethodCall methodCall;
class SendCallCodeExecution extends CodeExecution::Range, KernelMethodCall {
SendCallCodeExecution() { this.getMethodName() = "send" }
SendCallCodeExecution() {
this.asExpr().getExpr() = methodCall and methodCall.getMethodName() = "send"
override DataFlow::Node getCode() { result = this.getArgument(0) }
}
/**
* A call to `BasicObject#instance_eval`, which executes its first argument as Ruby code.
*/
class InstanceEvalCallCodeExecution extends CodeExecution::Range, DataFlow::CallNode {
InstanceEvalCallCodeExecution() {
this.asExpr().getExpr().(UnknownMethodCall).getMethodName() = "instance_eval"
}
override DataFlow::Node getCode() { result.asExpr().getExpr() = methodCall.getAnArgument() }
override DataFlow::Node getCode() { result = this.getArgument(0) }
}
/**
* A call to `Module#class_eval`, which executes its first argument as Ruby code.
*/
class ClassEvalCallCodeExecution extends CodeExecution::Range, DataFlow::CallNode {
ClassEvalCallCodeExecution() {
this.asExpr().getExpr().(UnknownMethodCall).getMethodName() = "class_eval"
}
override DataFlow::Node getCode() { result = this.getArgument(0) }
}
/**
* A call to `Module#module_eval`, which executes its first argument as Ruby code.
*/
class ModuleEvalCallCodeExecution extends CodeExecution::Range, DataFlow::CallNode {
ModuleEvalCallCodeExecution() {
this.asExpr().getExpr().(UnknownMethodCall).getMethodName() = "module_eval"
}
override DataFlow::Node getCode() { result = this.getArgument(0) }
}

View File

@@ -0,0 +1,182 @@
private import codeql.ruby.Concepts
private import codeql.ruby.AST
private import codeql.ruby.DataFlow
private import codeql.ruby.typetracking.TypeTracker
private import codeql.ruby.ApiGraphs
private import codeql.ruby.controlflow.CfgNodes as CfgNodes
private class NokogiriXmlParserCall extends XmlParserCall::Range, DataFlow::CallNode {
NokogiriXmlParserCall() {
this =
[
API::getTopLevelMember("Nokogiri").getMember("XML"),
API::getTopLevelMember("Nokogiri").getMember("XML").getMember("Document"),
API::getTopLevelMember("Nokogiri")
.getMember("XML")
.getMember("SAX")
.getMember("Parser")
.getInstance()
].getAMethodCall("parse")
}
override DataFlow::Node getInput() { result = this.getArgument(0) }
override predicate externalEntitiesEnabled() {
this.getArgument(3) =
[trackEnableFeature(TNOENT()), trackEnableFeature(TDTDLOAD()), trackDisableFeature(TNONET())]
or
// calls to methods that enable/disable features in a block argument passed to this parser call.
// For example:
// ```ruby
// doc.parse(...) { |options| options.nononet; options.noent }
// ```
this.asExpr()
.getExpr()
.(MethodCall)
.getBlock()
.getAStmt()
.getAChild*()
.(MethodCall)
.getMethodName() = ["noent", "dtdload", "nononet"]
}
}
private class LibXmlRubyXmlParserCall extends XmlParserCall::Range, DataFlow::CallNode {
LibXmlRubyXmlParserCall() {
this =
[API::getTopLevelMember("LibXML").getMember("XML"), API::getTopLevelMember("XML")]
.getMember(["Document", "Parser"])
.getAMethodCall(["file", "io", "string"])
}
override DataFlow::Node getInput() { result = this.getArgument(0) }
override predicate externalEntitiesEnabled() {
exists(Pair pair |
pair = this.getArgument(1).asExpr().getExpr().(HashLiteral).getAKeyValuePair() and
pair.getKey().(Literal).getValueText() = "options" and
pair.getValue() =
[
trackEnableFeature(TNOENT()), trackEnableFeature(TDTDLOAD()),
trackDisableFeature(TNONET())
].asExpr().getExpr()
)
}
}
private newtype TFeature =
TNOENT() or
TNONET() or
TDTDLOAD()
class Feature extends TFeature {
abstract int getValue();
string toString() { result = getConstantName() }
abstract string getConstantName();
}
private class FeatureNOENT extends Feature, TNOENT {
override int getValue() { result = 2 }
override string getConstantName() { result = "NOENT" }
}
private class FeatureNONET extends Feature, TNONET {
override int getValue() { result = 2048 }
override string getConstantName() { result = "NONET" }
}
private class FeatureDTDLOAD extends Feature, TDTDLOAD {
override int getValue() { result = 4 }
override string getConstantName() { result = "DTDLOAD" }
}
private API::Node parseOptionsModule() {
result = API::getTopLevelMember("Nokogiri").getMember("XML").getMember("ParseOptions")
or
result =
API::getTopLevelMember("LibXML").getMember("XML").getMember("Parser").getMember("Options")
or
result = API::getTopLevelMember("XML").getMember("Parser").getMember("Options")
}
private predicate bitWiseAndOr(CfgNodes::ExprNodes::OperationCfgNode operation) {
operation.getExpr() instanceof BitwiseAndExpr or
operation.getExpr() instanceof AssignBitwiseAndExpr or
operation.getExpr() instanceof BitwiseOrExpr or
operation.getExpr() instanceof AssignBitwiseOrExpr
}
private DataFlow::LocalSourceNode trackFeature(Feature f, boolean enable, TypeTracker t) {
t.start() and
(
// An integer literal with the feature-bit enabled/disabled
exists(int bitValue |
bitValue = result.asExpr().getExpr().(IntegerLiteral).getValue().bitAnd(f.getValue())
|
if bitValue = 0 then enable = false else enable = true
)
or
// Use of a constant f
enable = true and
result = parseOptionsModule().getMember(f.getConstantName()).getAUse()
or
// Treat `&`, `&=`, `|` and `|=` operators as if they preserve the on/off states
// of their operands. This is an overapproximation but likely to work well in practice
// because it makes little sense to explicitly set a feature to both `on` and `off` in the
// same code.
exists(CfgNodes::ExprNodes::OperationCfgNode operation |
bitWiseAndOr(operation) and
operation = result.asExpr().(CfgNodes::ExprNodes::OperationCfgNode) and
operation.getAnOperand() = trackFeature(f, enable).asExpr()
)
or
// The complement operator toggles a feature from enabled to disabled and vice-versa
result.asExpr().getExpr() instanceof ComplementExpr and
result.asExpr().(CfgNodes::ExprNodes::OperationCfgNode).getAnOperand() =
trackFeature(f, enable.booleanNot()).asExpr()
or
// Nokogiri has a ParseOptions class that is a wrapper around the bit-fields and
// provides methods for querying and updating the fields.
result =
API::getTopLevelMember("Nokogiri")
.getMember("XML")
.getMember("ParseOptions")
.getAnInstantiation() and
result.asExpr().(CfgNodes::ExprNodes::CallCfgNode).getArgument(0) =
trackFeature(f, enable).asExpr()
or
// The Nokogiri ParseOptions class has methods for setting/unsetting features.
// The method names are the lowercase variants of the constant names, with a "no"
// prefix for unsetting a feature.
exists(CfgNodes::ExprNodes::CallCfgNode call |
enable = true and
call.getExpr().(MethodCall).getMethodName() = f.getConstantName().toLowerCase()
or
enable = false and
call.getExpr().(MethodCall).getMethodName() = "no" + f.getConstantName().toLowerCase()
|
(
// these methods update the receiver
result.flowsTo(any(DataFlow::Node n | n.asExpr() = call.getReceiver()))
or
// in addition they return the (updated) receiver to allow chaining calls.
result.asExpr() = call
)
)
)
or
exists(TypeTracker t2 | result = trackFeature(f, enable, t2).track(t2, t))
}
private DataFlow::Node trackFeature(Feature f, boolean enable) {
trackFeature(f, enable, TypeTracker::end()).flowsTo(result)
}
private DataFlow::Node trackEnableFeature(Feature f) { result = trackFeature(f, true) }
private DataFlow::Node trackDisableFeature(Feature f) { result = trackFeature(f, false) }

View File

@@ -17,11 +17,11 @@ private import codeql.ruby.ApiGraphs
* TODO: pipelining, streaming responses
* https://github.com/excon/excon/blob/master/README.md
*/
class ExconHTTPRequest extends HTTP::Client::Request::Range {
class ExconHttpRequest extends HTTP::Client::Request::Range {
DataFlow::Node request;
DataFlow::CallNode responseBody;
ExconHTTPRequest() {
ExconHttpRequest() {
exists(API::Node requestNode | request = requestNode.getAnImmediateUse() |
requestNode =
[

View File

@@ -13,11 +13,11 @@ private import codeql.ruby.ApiGraphs
* connection.get("/").body
* ```
*/
class FaradayHTTPRequest extends HTTP::Client::Request::Range {
class FaradayHttpRequest extends HTTP::Client::Request::Range {
DataFlow::Node request;
DataFlow::CallNode responseBody;
FaradayHTTPRequest() {
FaradayHttpRequest() {
exists(API::Node requestNode |
requestNode =
[

View File

@@ -0,0 +1,40 @@
private import ruby
private import codeql.ruby.Concepts
private import codeql.ruby.ApiGraphs
/**
* A call that makes an HTTP request using `HTTPClient`.
* ```ruby
* HTTPClient.get("http://example.com").body
* HTTPClient.get_content("http://example.com")
* ```
*/
class HttpClientRequest extends HTTP::Client::Request::Range {
DataFlow::Node request;
DataFlow::CallNode responseBody;
HttpClientRequest() {
exists(API::Node requestNode, string method |
request = requestNode.getAnImmediateUse() and
method in [
"get", "head", "delete", "options", "post", "put", "trace", "get_content", "post_content"
]
|
requestNode = API::getTopLevelMember("HTTPClient").getReturn(method) and
(
// The `get_content` and `post_content` methods return the response body as a string.
// The other methods return a `HTTPClient::Message` object which has various methods
// that return the response body.
method in ["get_content", "post_content"] and responseBody = request
or
not method in ["get_content", "put_content"] and
responseBody = requestNode.getAMethodCall(["body", "http_body", "content", "dump"])
) and
this = request.asExpr().getExpr()
)
}
override DataFlow::Node getResponseBody() { result = responseBody }
override string getFramework() { result = "HTTPClient" }
}

View File

@@ -0,0 +1,46 @@
private import ruby
private import codeql.ruby.Concepts
private import codeql.ruby.ApiGraphs
/**
* A call that makes an HTTP request using `HTTParty`.
* ```ruby
* # one-off request - returns the response body
* HTTParty.get("http://example.com")
*
* # TODO: module inclusion
* class MyClass
* include HTTParty
* end
*
* MyClass.new("http://example.com")
* ```
*/
class HttpartyRequest extends HTTP::Client::Request::Range {
DataFlow::Node request;
DataFlow::CallNode responseBody;
HttpartyRequest() {
exists(API::Node requestNode | request = requestNode.getAnImmediateUse() |
requestNode =
API::getTopLevelMember("HTTParty")
.getReturn(["get", "head", "delete", "options", "post", "put", "patch"]) and
(
// If HTTParty can recognise the response type, it will parse and return it
// directly from the request call. Otherwise, it will return a `HTTParty::Response`
// object that has a `#body` method.
// So if there's a call to `#body` on the response, treat that as the response body.
exists(DataFlow::Node r | r = requestNode.getAMethodCall("body") | responseBody = r)
or
// Otherwise, treat the response as the response body.
not exists(DataFlow::Node r | r = requestNode.getAMethodCall("body")) and
responseBody = request
) and
this = request.asExpr().getExpr()
)
}
override DataFlow::Node getResponseBody() { result = responseBody }
override string getFramework() { result = "HTTParty" }
}

View File

@@ -13,11 +13,11 @@ private import codeql.ruby.dataflow.internal.DataFlowPublic
* response = req.get("/")
* ```
*/
class NetHTTPRequest extends HTTP::Client::Request::Range {
class NetHttpRequest extends HTTP::Client::Request::Range {
private DataFlow::CallNode request;
private DataFlow::Node responseBody;
NetHTTPRequest() {
NetHttpRequest() {
exists(API::Node requestNode, string method |
request = requestNode.getAnImmediateUse() and
this = request.asExpr().getExpr()

View File

@@ -0,0 +1,39 @@
private import ruby
private import codeql.ruby.Concepts
private import codeql.ruby.ApiGraphs
private import codeql.ruby.frameworks.StandardLibrary
/**
* A call that makes an HTTP request using `OpenURI`.
* ```ruby
* Kernel.open("http://example.com").read
* URI.open("http://example.com").readlines
* URI.parse("http://example.com").open.read
* ```
*/
class OpenURIRequest extends HTTP::Client::Request::Range {
DataFlow::Node request;
DataFlow::CallNode responseBody;
OpenURIRequest() {
exists(API::Node requestNode | request = requestNode.getAnImmediateUse() |
requestNode =
[API::getTopLevelMember("URI"), API::getTopLevelMember("URI").getReturn("parse")]
.getReturn("open") and
responseBody = requestNode.getAMethodCall(["read", "readlines"]) and
this = request.asExpr().getExpr()
)
or
// Kernel.open("http://example.com").read
// open("http://example.com").read
request instanceof KernelMethodCall and
this.getMethodName() = "open" and
request.asExpr().getExpr() = this and
responseBody.asExpr().getExpr().(MethodCall).getMethodName() in ["read", "readlines"] and
request.(DataFlow::LocalSourceNode).flowsTo(responseBody.getReceiver())
}
override DataFlow::Node getResponseBody() { result = responseBody }
override string getFramework() { result = "OpenURI" }
}

View File

@@ -8,11 +8,11 @@ private import codeql.ruby.ApiGraphs
* RestClient.get("http://example.com").body
* ```
*/
class RestClientHTTPRequest extends HTTP::Client::Request::Range {
class RestClientHttpRequest extends HTTP::Client::Request::Range {
DataFlow::Node request;
DataFlow::CallNode responseBody;
RestClientHTTPRequest() {
RestClientHttpRequest() {
exists(API::Node requestNode |
requestNode =
API::getTopLevelMember("RestClient")

View File

@@ -0,0 +1,28 @@
private import ruby
private import codeql.ruby.Concepts
private import codeql.ruby.ApiGraphs
/**
* A call that makes an HTTP request using `Typhoeus`.
* ```ruby
* Typhoeus.get("http://example.com").body
* ```
*/
class TyphoeusHttpRequest extends HTTP::Client::Request::Range {
DataFlow::Node request;
DataFlow::CallNode responseBody;
TyphoeusHttpRequest() {
exists(API::Node requestNode | request = requestNode.getAnImmediateUse() |
requestNode =
API::getTopLevelMember("Typhoeus")
.getReturn(["get", "head", "delete", "options", "post", "put", "patch"]) and
responseBody = requestNode.getAMethodCall("body") and
this = request.asExpr().getExpr()
)
}
override DataFlow::Node getResponseBody() { result = responseBody }
override string getFramework() { result = "Typhoeus" }
}

View File

@@ -85,7 +85,7 @@ class PrintAstNode extends TPrintNode {
* Holds if this node is at the specified location. The location spans column
* `startcolumn` of line `startline` to column `endcolumn` of line `endline`
* in file `filepath`. For more information, see
* [LGTM locations](https://lgtm.com/help/ql/locations).
* [LGTM locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn

View File

@@ -11,7 +11,7 @@ class Node = DataFlowPublic::Node;
class TypeTrackingNode = DataFlowPublic::LocalSourceNode;
predicate simpleLocalFlowStep = DataFlowPrivate::simpleLocalFlowStep/2;
predicate simpleLocalFlowStep = DataFlowPrivate::localFlowStepTypeTracker/2;
predicate jumpStep = DataFlowPrivate::jumpStep/2;
@@ -30,12 +30,21 @@ string getPossibleContentName() { result = getSetterCallAttributeName(_) }
* recursion (or, at best, terrible performance), since identifying calls to library
* methods is done using API graphs (which uses type tracking).
*/
predicate callStep(DataFlowPrivate::ArgumentNode nodeFrom, DataFlowPublic::ParameterNode nodeTo) {
predicate callStep(Node nodeFrom, Node nodeTo) {
exists(ExprNodes::CallCfgNode call, CFG::CfgScope callable, int i |
DataFlowDispatch::getTarget(call) = callable and
nodeFrom.sourceArgumentOf(call, i) and
nodeFrom.(DataFlowPrivate::ArgumentNode).sourceArgumentOf(call, i) and
nodeTo.(DataFlowPrivate::ParameterNodeImpl).isSourceParameterOf(callable, i)
)
or
// In normal data-flow, this will be a local flow step. But for type tracking
// we model it as a call step, in order to avoid computing a potential
// self-cross product of all calls to a function that returns one of its parameters
// (only to later filter that flow out using `TypeTracker::append`).
nodeTo =
DataFlowPrivate::LocalFlow::getParameterDefNode(nodeFrom
.(DataFlowPublic::ParameterNode)
.getParameter())
}
/**
@@ -45,11 +54,18 @@ predicate callStep(DataFlowPrivate::ArgumentNode nodeFrom, DataFlowPublic::Param
* recursion (or, at best, terrible performance), since identifying calls to library
* methods is done using API graphs (which uses type tracking).
*/
predicate returnStep(DataFlowPrivate::ReturnNode nodeFrom, Node nodeTo) {
predicate returnStep(Node nodeFrom, Node nodeTo) {
exists(ExprNodes::CallCfgNode call |
nodeFrom instanceof DataFlowPrivate::ReturnNode and
nodeFrom.(DataFlowPrivate::NodeImpl).getCfgScope() = DataFlowDispatch::getTarget(call) and
nodeTo.asExpr().getNode() = call.getNode()
)
or
// In normal data-flow, this will be a local flow step. But for type tracking
// we model it as a returning flow step, in order to avoid computing a potential
// self-cross product of all calls to a function that returns one of its parameters
// (only to later filter that flow out using `TypeTracker::append`).
nodeTo.(DataFlowPrivate::SynthReturnNode).getAnInput() = nodeFrom
}
/**
@@ -105,7 +121,6 @@ predicate basicStoreStep(Node nodeFrom, DataFlowPublic::LocalSourceNode nodeTo,
*/
private string getSetterCallAttributeName(AST::SetterMethodCall call) {
// TODO: this should be exposed in `SetterMethodCall`
not call instanceof AST::ElementReference and
exists(string setterName |
setterName = call.getMethodName() and result = setterName.prefix(setterName.length() - 1)
)

View File

@@ -1205,8 +1205,6 @@ ruby_yield_def(
ruby_tokeninfo(
unique int id: @ruby_token,
int kind: int ref,
int file: @file ref,
int idx: int ref,
string value: string ref,
int loc: @location ref
);
@@ -1293,8 +1291,6 @@ erb_template_def(
erb_tokeninfo(
unique int id: @erb_token,
int kind: int ref,
int file: @file ref,
int idx: int ref,
string value: string ref,
int loc: @location ref
);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
description: Removed unused columns from the `*_tokeninfo` relations
compatibility: full
ruby_tokeninfo.rel: reorder ruby_tokeninfo.rel (int id, int kind, int file, int idx, string value, int loc) id kind value loc
erb_tokeninfo.rel: reorder erb_tokeninfo.rel (int id, int kind, int file, int idx, string value, int loc) id kind value loc

View File

@@ -0,0 +1,46 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>If <code>Kernel.open</code> is given a file name that starts with a <code>|</code>
character, it will execute the remaining string as a shell command. If a
malicious user can control the file name, they can execute arbitrary code.
The same vulnerability applies to <code>IO.read</code>.
</p>
</overview>
<recommendation>
<p>Use <code>File.open</code> instead of <code>Kernel.open</code>, as the former
does not have this vulnerability. Similarly, use <code>File.read</code> instead
of <code>IO.read</code>.</p>
</recommendation>
<example>
<p>
The following example shows code that calls <code>Kernel.open</code> on a
user-supplied file path.
</p>
<sample src="examples/kernel_open.rb" />
<p>Instead, <code>File.open</code> should be used, as in the following example.</p>
<sample src="examples/file_open.rb" />
</example>
<references>
<li>
OWASP:
<a href="https://www.owasp.org/index.php/Command_Injection">Command Injection</a>.
</li>
<li>
Example CVE: <a href="https://www.ruby-lang.org/en/news/2021/05/02/os-command-injection-in-rdoc/">Command Injection in RDoc</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,76 @@
/**
* @name Use of `Kernel.open` or `IO.read`
* @description Using `Kernel.open` or `IO.read` may allow a malicious
* user to execute arbitrary system commands.
* @kind path-problem
* @problem.severity error
* @security-severity 9.8
* @precision high
* @id rb/kernel-open
* @tags correctness
* security
* external/cwe/cwe-078
* external/cwe/cwe-088
* external/cwe/cwe-073
*/
import ruby
import codeql.ruby.ApiGraphs
import codeql.ruby.frameworks.StandardLibrary
import codeql.ruby.TaintTracking
import codeql.ruby.dataflow.BarrierGuards
import codeql.ruby.dataflow.RemoteFlowSources
import DataFlow::PathGraph
/**
* Method calls that have a suggested replacement.
*/
abstract class Replacement extends DataFlow::CallNode {
abstract string getFrom();
abstract string getTo();
}
class KernelOpenCall extends KernelMethodCall, Replacement {
KernelOpenCall() { this.getMethodName() = "open" }
override string getFrom() { result = "Kernel.open" }
override string getTo() { result = "File.open" }
}
class IOReadCall extends DataFlow::CallNode, Replacement {
IOReadCall() { this = API::getTopLevelMember("IO").getAMethodCall("read") }
override string getFrom() { result = "IO.read" }
override string getTo() { result = "File.read" }
}
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "KernelOpen" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) {
exists(KernelOpenCall c | c.getArgument(0) = sink)
or
exists(IOReadCall c | c.getArgument(0) = sink)
}
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
guard instanceof StringConstCompare or
guard instanceof StringConstArrayInclusionCall
}
}
from
Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink,
DataFlow::Node sourceNode, DataFlow::CallNode call
where
config.hasFlowPath(source, sink) and
sourceNode = source.getNode() and
call.asExpr().getExpr().(MethodCall).getArgument(0) = sink.getNode().asExpr().getExpr()
select sink.getNode(), source, sink,
"This call to " + call.(Replacement).getFrom() +
" depends on a user-provided value. Replace it with " + call.(Replacement).getTo() + "."

View File

@@ -0,0 +1,6 @@
class UsersController < ActionController::Base
def create
filename = params[:filename]
File.open(filename)
end
end

View File

@@ -0,0 +1,6 @@
class UsersController < ActionController::Base
def create
filename = params[:filename]
open(filename) # BAD
end
end

View File

@@ -0,0 +1,55 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>
Parsing untrusted XML files with a weakly configured XML parser may lead to an
XML External Entity (XXE) attack. This type of attack uses external entity references
to access arbitrary files on a system, carry out denial-of-service (DoS) attacks, or server-side
request forgery. Even when the result of parsing is not returned to the user, DoS attacks are still possible
and out-of-band data retrieval techniques may allow attackers to steal sensitive data.
</p>
</overview>
<recommendation>
<p>
The easiest way to prevent XXE attacks is to disable external entity handling when
parsing untrusted data. How this is done depends on the library being used. Note that some
libraries, such as <code>rexml</code>, <code>nokogiri</code> and <code>libxml-ruby</code>,
disable entity expansion by default, so unless you have explicitly enabled entity expansion,
no further action needs to be taken.
</p>
</recommendation>
<example>
<p>
The following example uses the <code>nokogiri</code> XML parser to parse a string <code>xmlSrc</code>.
If that string is from an untrusted source, this code may be vulnerable to an XXE attack, since
the parser is invoked with the <code>noent</code> option set:
</p>
<sample src="examples/Xxe.rb"/>
<p>
To guard against XXE attacks, the <code>noent</code> option should be omitted or cleared
(e.g. using <code>nonoent</code>). This means that no entity expansion is undertaken at all,
not even for standard internal entities such as <code>&amp;amp;</code> or <code>&amp;gt;</code>.
If desired, these entities can be expanded in a separate step using utility functions.
</p>
<sample src="examples/XxeGood.rb"/>
</example>
<references>
<li>
OWASP:
<a href="https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing">XML External Entity (XXE) Processing</a>.
</li>
<li>
Timothy Morgen:
<a href="https://research.nccgroup.com/2014/05/19/xml-schema-dtd-and-entity-attacks-a-compendium-of-known-techniques/">XML Schema, DTD, and Entity Attacks</a>.
</li>
<li>
Timur Yunusov, Alexey Osipov:
<a href="https://www.slideshare.net/qqlan/bh-ready-v4">XML Out-Of-Band Data Retrieval</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,43 @@
/**
* @name XML external entity expansion
* @description Parsing user input as an XML document with external
* entity expansion is vulnerable to XXE attacks.
* @kind path-problem
* @problem.severity error
* @security-severity 9.1
* @precision high
* @id rb/xxe
* @tags security
* external/cwe/cwe-611
* external/cwe/cwe-776
* external/cwe/cwe-827
*/
import ruby
import codeql.ruby.dataflow.RemoteFlowSources
import codeql.ruby.TaintTracking
import codeql.ruby.Concepts
import codeql.ruby.DataFlow
import DataFlow::PathGraph
class UnsafeXxeSink extends DataFlow::ExprNode {
UnsafeXxeSink() {
exists(XmlParserCall parse |
parse.getInput() = this and
parse.externalEntitiesEnabled()
)
}
}
class XxeConfig extends TaintTracking::Configuration {
XxeConfig() { this = "XXE.ql::XxeConfig" }
override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof UnsafeXxeSink }
}
from DataFlow::PathNode source, DataFlow::PathNode sink, XxeConfig conf
where conf.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Unsafe parsing of XML file from $@.", source.getNode(),
"user input"

View File

@@ -0,0 +1,12 @@
require "nokogiri"
def process_data1
xmlSrc = request.body
doc = Nokogiri::XML.parse(xmlSrc, nil, nil, Nokogiri::XML::ParseOptions::NOENT) # BAD
end
def process_data2
xmlSrc = request.body
doc = Nokogiri::XML.parse(xmlSrc) { |config| config.noent } # BAD
end

View File

@@ -0,0 +1,12 @@
require "nokogiri"
def process_data1
xmlSrc = request.body
doc = Nokogiri::XML.parse(xmlSrc) # GOOD
end
def process_data2
xmlSrc = request.body
doc = Nokogiri::XML.parse(xmlSrc) { |config| config.nonoent } # GOOD
end

View File

@@ -0,0 +1,558 @@
| calls/calls.rb:11:1:11:3 | 123 | 123 |
| calls/calls.rb:14:5:14:5 | 0 | 0 |
| calls/calls.rb:14:8:14:8 | 1 | 1 |
| calls/calls.rb:14:11:14:11 | 2 | 2 |
| calls/calls.rb:17:15:17:15 | 1 | 1 |
| calls/calls.rb:21:7:21:7 | 1 | 1 |
| calls/calls.rb:25:1:25:3 | 123 | 123 |
| calls/calls.rb:25:9:25:13 | "foo" | foo |
| calls/calls.rb:26:7:26:7 | 1 | 1 |
| calls/calls.rb:36:9:36:11 | 100 | 100 |
| calls/calls.rb:36:14:36:16 | 200 | 200 |
| calls/calls.rb:278:5:278:8 | :blah | blah |
| calls/calls.rb:279:5:279:8 | :blah | blah |
| calls/calls.rb:288:11:288:16 | "blah" | blah |
| calls/calls.rb:289:11:289:11 | 1 | 1 |
| calls/calls.rb:289:14:289:14 | 2 | 2 |
| calls/calls.rb:289:17:289:17 | 3 | 3 |
| calls/calls.rb:290:21:290:21 | 1 | 1 |
| calls/calls.rb:291:22:291:22 | 2 | 2 |
| calls/calls.rb:292:11:292:11 | 4 | 4 |
| calls/calls.rb:292:14:292:14 | 5 | 5 |
| calls/calls.rb:292:26:292:28 | 100 | 100 |
| calls/calls.rb:293:11:293:11 | 6 | 6 |
| calls/calls.rb:293:14:293:14 | 7 | 7 |
| calls/calls.rb:293:27:293:29 | 200 | 200 |
| calls/calls.rb:311:6:311:6 | 1 | 1 |
| calls/calls.rb:314:1:314:8 | __synth__0 | 10 |
| calls/calls.rb:314:12:314:13 | 10 | 10 |
| calls/calls.rb:315:1:315:6 | __synth__0 | 10 |
| calls/calls.rb:315:5:315:5 | 0 | 0 |
| calls/calls.rb:315:10:315:11 | 10 | 10 |
| calls/calls.rb:316:1:316:8 | 0 | 0 |
| calls/calls.rb:316:12:316:19 | 1 | 1 |
| calls/calls.rb:316:12:316:19 | -2 | -2 |
| calls/calls.rb:316:22:316:27 | -1 | -1 |
| calls/calls.rb:316:26:316:26 | 4 | 4 |
| calls/calls.rb:316:32:316:32 | 1 | 1 |
| calls/calls.rb:316:35:316:35 | 2 | 2 |
| calls/calls.rb:316:38:316:38 | 3 | 3 |
| calls/calls.rb:316:41:316:41 | 4 | 4 |
| calls/calls.rb:317:1:317:1 | 0 | 0 |
| calls/calls.rb:317:5:317:10 | 1 | 1 |
| calls/calls.rb:317:5:317:10 | -1 | -1 |
| calls/calls.rb:317:9:317:9 | 5 | 5 |
| calls/calls.rb:317:15:317:15 | 1 | 1 |
| calls/calls.rb:317:18:317:18 | 2 | 2 |
| calls/calls.rb:317:21:317:21 | 3 | 3 |
| calls/calls.rb:318:15:318:15 | 1 | 1 |
| calls/calls.rb:319:5:319:5 | 0 | 0 |
| calls/calls.rb:319:5:319:5 | __synth__1 | 0 |
| calls/calls.rb:319:5:319:5 | __synth__1 | 0 |
| calls/calls.rb:319:11:319:11 | 1 | 1 |
| calls/calls.rb:320:9:320:9 | 0 | 0 |
| calls/calls.rb:320:9:320:9 | __synth__1 | 0 |
| calls/calls.rb:320:9:320:9 | __synth__1 | 0 |
| calls/calls.rb:320:31:320:31 | 1 | 1 |
| calls/calls.rb:320:37:320:37 | 2 | 2 |
| constants/constants.rb:3:19:3:27 | "const_a" | const_a |
| constants/constants.rb:6:15:6:23 | "const_b" | const_b |
| constants/constants.rb:17:12:17:18 | "Hello" | Hello |
| constants/constants.rb:17:22:17:45 | CONST_A | const_a |
| constants/constants.rb:17:49:17:64 | CONST_B | const_b |
| constants/constants.rb:20:14:20:19 | "Vera" | Vera |
| constants/constants.rb:20:22:20:28 | "Chuck" | Chuck |
| constants/constants.rb:20:31:20:36 | "Dave" | Dave |
| constants/constants.rb:28:11:28:15 | "foo" | foo |
| constants/constants.rb:37:30:37:33 | 1024 | 1024 |
| constants/constants.rb:39:6:39:31 | MAX_SIZE | 1024 |
| control/cases.rb:2:5:2:5 | 0 | 0 |
| control/cases.rb:3:5:3:5 | 0 | 0 |
| control/cases.rb:4:5:4:5 | 0 | 0 |
| control/cases.rb:5:5:5:5 | 0 | 0 |
| control/cases.rb:8:6:8:6 | a | 0 |
| control/cases.rb:9:6:9:6 | b | 0 |
| control/cases.rb:10:5:10:7 | 100 | 100 |
| control/cases.rb:11:6:11:6 | c | 0 |
| control/cases.rb:11:9:11:9 | d | 0 |
| control/cases.rb:12:5:12:7 | 200 | 200 |
| control/cases.rb:14:5:14:7 | 300 | 300 |
| control/cases.rb:19:6:19:6 | a | 0 |
| control/cases.rb:19:10:19:10 | b | 0 |
| control/cases.rb:19:18:19:19 | 10 | 10 |
| control/cases.rb:20:6:20:6 | a | 0 |
| control/cases.rb:20:11:20:11 | b | 0 |
| control/cases.rb:20:18:20:19 | 20 | 20 |
| control/cases.rb:21:6:21:6 | a | 0 |
| control/cases.rb:21:10:21:10 | b | 0 |
| control/cases.rb:21:18:21:19 | 30 | 30 |
| control/conditionals.rb:2:5:2:5 | 0 | 0 |
| control/conditionals.rb:3:5:3:5 | 0 | 0 |
| control/conditionals.rb:4:5:4:5 | 0 | 0 |
| control/conditionals.rb:5:5:5:5 | 0 | 0 |
| control/conditionals.rb:6:5:6:5 | 0 | 0 |
| control/conditionals.rb:7:5:7:5 | 0 | 0 |
| control/conditionals.rb:10:4:10:4 | a | 0 |
| control/conditionals.rb:10:8:10:8 | b | 0 |
| control/conditionals.rb:11:5:11:5 | c | 0 |
| control/conditionals.rb:15:4:15:4 | a | 0 |
| control/conditionals.rb:15:9:15:9 | b | 0 |
| control/conditionals.rb:16:5:16:5 | c | 0 |
| control/conditionals.rb:18:5:18:5 | d | 0 |
| control/conditionals.rb:22:4:22:4 | a | 0 |
| control/conditionals.rb:22:9:22:9 | 0 | 0 |
| control/conditionals.rb:23:5:23:5 | c | 0 |
| control/conditionals.rb:24:7:24:7 | a | 0 |
| control/conditionals.rb:24:12:24:12 | 1 | 1 |
| control/conditionals.rb:25:5:25:5 | d | 0 |
| control/conditionals.rb:26:7:26:7 | a | 0 |
| control/conditionals.rb:26:12:26:12 | 2 | 2 |
| control/conditionals.rb:27:5:27:5 | e | 0 |
| control/conditionals.rb:29:5:29:5 | f | 0 |
| control/conditionals.rb:33:4:33:4 | a | 0 |
| control/conditionals.rb:33:9:33:9 | 0 | 0 |
| control/conditionals.rb:34:5:34:5 | b | 0 |
| control/conditionals.rb:35:7:35:7 | a | 0 |
| control/conditionals.rb:35:12:35:12 | 1 | 1 |
| control/conditionals.rb:36:5:36:5 | c | 0 |
| control/conditionals.rb:40:8:40:8 | a | 0 |
| control/conditionals.rb:40:12:40:12 | b | 0 |
| control/conditionals.rb:41:5:41:5 | c | 0 |
| control/conditionals.rb:45:8:45:8 | a | 0 |
| control/conditionals.rb:45:13:45:13 | b | 0 |
| control/conditionals.rb:46:5:46:5 | c | 0 |
| control/conditionals.rb:48:5:48:5 | d | 0 |
| control/conditionals.rb:52:5:52:5 | b | 0 |
| control/conditionals.rb:52:10:52:10 | c | 0 |
| control/conditionals.rb:52:14:52:14 | d | 0 |
| control/conditionals.rb:55:5:55:5 | b | 0 |
| control/conditionals.rb:55:14:55:14 | c | 0 |
| control/conditionals.rb:55:18:55:18 | d | 0 |
| control/conditionals.rb:58:5:58:5 | b | 0 |
| control/conditionals.rb:58:9:58:9 | c | 0 |
| control/conditionals.rb:58:13:58:13 | d | 0 |
| control/conditionals.rb:58:13:58:17 | ... + ... | 1 |
| control/conditionals.rb:58:17:58:17 | 1 | 1 |
| control/conditionals.rb:58:21:58:21 | e | 0 |
| control/conditionals.rb:58:21:58:25 | ... - ... | -2 |
| control/conditionals.rb:58:25:58:25 | 2 | 2 |
| control/conditionals.rb:61:8:61:8 | b | 0 |
| control/conditionals.rb:62:5:62:5 | c | 0 |
| control/conditionals.rb:67:8:67:8 | b | 0 |
| control/conditionals.rb:69:5:69:5 | c | 0 |
| control/loops.rb:2:7:2:7 | 0 | 0 |
| control/loops.rb:3:7:3:7 | 0 | 0 |
| control/loops.rb:4:5:4:5 | 0 | 0 |
| control/loops.rb:5:5:5:5 | 0 | 0 |
| control/loops.rb:6:5:6:5 | 0 | 0 |
| control/loops.rb:9:10:9:10 | 1 | 1 |
| control/loops.rb:9:13:9:14 | 10 | 10 |
| control/loops.rb:16:10:16:10 | 1 | 1 |
| control/loops.rb:16:13:16:14 | 10 | 10 |
| control/loops.rb:22:20:22:22 | :foo | foo |
| control/loops.rb:22:25:22:25 | 0 | 0 |
| control/loops.rb:22:28:22:30 | :bar | bar |
| control/loops.rb:22:33:22:33 | 1 | 1 |
| control/loops.rb:28:22:28:24 | :foo | foo |
| control/loops.rb:28:27:28:27 | 0 | 0 |
| control/loops.rb:28:30:28:32 | :bar | bar |
| control/loops.rb:28:35:28:35 | 1 | 1 |
| control/loops.rb:35:11:35:11 | y | 0 |
| control/loops.rb:36:8:36:8 | 1 | 1 |
| control/loops.rb:37:8:37:8 | 1 | 1 |
| control/loops.rb:42:11:42:11 | y | 0 |
| control/loops.rb:43:8:43:8 | 1 | 1 |
| control/loops.rb:44:8:44:8 | 2 | 2 |
| control/loops.rb:48:6:48:6 | 1 | 1 |
| control/loops.rb:48:14:48:14 | y | 0 |
| control/loops.rb:51:12:51:12 | y | 0 |
| control/loops.rb:52:8:52:8 | 1 | 1 |
| control/loops.rb:53:8:53:8 | 1 | 1 |
| control/loops.rb:57:11:57:11 | y | 0 |
| control/loops.rb:58:8:58:8 | 1 | 1 |
| control/loops.rb:59:8:59:8 | 4 | 4 |
| control/loops.rb:63:6:63:6 | 1 | 1 |
| control/loops.rb:63:19:63:19 | 0 | 0 |
| control/loops.rb:66:11:66:11 | y | 0 |
| erb/template.html.erb:19:5:19:17 | "hello world" | hello world |
| erb/template.html.erb:25:9:25:10 | "" | |
| erb/template.html.erb:27:16:27:20 | "foo" | foo |
| erb/template.html.erb:27:23:27:27 | "bar" | bar |
| erb/template.html.erb:27:30:27:34 | "baz" | baz |
| gems/Gemfile:1:8:1:29 | "https://rubygems.org" | https://rubygems.org |
| gems/Gemfile:3:5:3:13 | "foo_gem" | foo_gem |
| gems/Gemfile:3:16:3:23 | "~> 2.0" | ~> 2.0 |
| gems/Gemfile:5:8:5:33 | "https://gems.example.com" | https://gems.example.com |
| gems/Gemfile:6:7:6:14 | "my_gem" | my_gem |
| gems/Gemfile:6:17:6:21 | "1.0" | 1.0 |
| gems/Gemfile:7:7:7:19 | "another_gem" | another_gem |
| gems/Gemfile:7:22:7:28 | "3.1.4" | 3.1.4 |
| gems/lib/test.rb:3:10:3:16 | "Hello" | Hello |
| gems/test.gemspec:2:3:2:8 | __synth__0 | test |
| gems/test.gemspec:2:19:2:24 | "test" | test |
| gems/test.gemspec:3:3:3:11 | __synth__0 | 0.0.0 |
| gems/test.gemspec:3:19:3:25 | "0.0.0" | 0.0.0 |
| gems/test.gemspec:4:3:4:11 | __synth__0 | foo! |
| gems/test.gemspec:4:19:4:24 | "foo!" | foo! |
| gems/test.gemspec:5:3:5:15 | __synth__0 | A test |
| gems/test.gemspec:5:19:5:26 | "A test" | A test |
| gems/test.gemspec:6:20:6:30 | "Mona Lisa" | Mona Lisa |
| gems/test.gemspec:7:3:7:9 | __synth__0 | mona@example.com |
| gems/test.gemspec:7:19:7:36 | "mona@example.com" | mona@example.com |
| gems/test.gemspec:8:20:8:32 | "lib/test.rb" | lib/test.rb |
| gems/test.gemspec:9:3:9:12 | __synth__0 | https://github.com/github/codeql-ruby |
| gems/test.gemspec:9:19:9:57 | "https://github.com/github/cod..." | https://github.com/github/codeql-ruby |
| literals/literals.rb:2:1:2:3 | nil | nil |
| literals/literals.rb:3:1:3:3 | NIL | NIL |
| literals/literals.rb:4:1:4:5 | false | false |
| literals/literals.rb:5:1:5:5 | FALSE | FALSE |
| literals/literals.rb:6:1:6:4 | true | true |
| literals/literals.rb:7:1:7:4 | TRUE | TRUE |
| literals/literals.rb:10:1:10:4 | 1234 | 1234 |
| literals/literals.rb:11:1:11:5 | 5_678 | 5_678 |
| literals/literals.rb:12:1:12:1 | 0 | 0 |
| literals/literals.rb:13:1:13:5 | 0d900 | 0d900 |
| literals/literals.rb:16:1:16:6 | 0x1234 | 0x1234 |
| literals/literals.rb:17:1:17:10 | 0xdeadbeef | 0xdeadbeef |
| literals/literals.rb:18:1:18:11 | 0xF00D_face | 0xF00D_face |
| literals/literals.rb:21:1:21:4 | 0123 | 0123 |
| literals/literals.rb:22:1:22:5 | 0o234 | 0o234 |
| literals/literals.rb:23:1:23:6 | 0O45_6 | 0O45_6 |
| literals/literals.rb:26:1:26:10 | 0b10010100 | 0b10010100 |
| literals/literals.rb:27:1:27:11 | 0B011_01101 | 0B011_01101 |
| literals/literals.rb:30:1:30:5 | 12.34 | 12.34 |
| literals/literals.rb:31:1:31:7 | 1234e-2 | 1234e-2 |
| literals/literals.rb:32:1:32:7 | 1.234E1 | 1.234E1 |
| literals/literals.rb:35:1:35:3 | 23r | 23r |
| literals/literals.rb:36:1:36:5 | 9.85r | 9.85r |
| literals/literals.rb:39:1:39:2 | 2i | 2i |
| literals/literals.rb:46:1:46:2 | "" | |
| literals/literals.rb:47:1:47:2 | "" | |
| literals/literals.rb:48:1:48:7 | "hello" | hello |
| literals/literals.rb:49:1:49:9 | "goodbye" | goodbye |
| literals/literals.rb:50:1:50:30 | "string with escaped \\" quote" | string with escaped \\" quote |
| literals/literals.rb:51:1:51:21 | "string with " quote" | string with " quote |
| literals/literals.rb:52:1:52:14 | "foo bar baz" | foo bar baz |
| literals/literals.rb:53:1:53:15 | "foo bar baz" | foo bar baz |
| literals/literals.rb:54:1:54:20 | "foo ' bar " baz'" | foo ' bar " baz' |
| literals/literals.rb:55:1:55:20 | "FOO ' BAR " BAZ'" | FOO ' BAR " BAZ' |
| literals/literals.rb:56:1:56:12 | "foo\\ bar" | foo\\ bar |
| literals/literals.rb:57:1:57:12 | "foo\\ bar" | foo\\ bar |
| literals/literals.rb:58:13:58:13 | 2 | 2 |
| literals/literals.rb:58:13:58:17 | ... + ... | 4 |
| literals/literals.rb:58:17:58:17 | 2 | 2 |
| literals/literals.rb:59:15:59:15 | 3 | 3 |
| literals/literals.rb:59:15:59:19 | ... + ... | 7 |
| literals/literals.rb:59:19:59:19 | 4 | 4 |
| literals/literals.rb:60:1:60:20 | "2 + 2 = #{ 2 + 2 }" | 2 + 2 = #{ 2 + 2 } |
| literals/literals.rb:61:1:61:22 | "3 + 4 = #{ 3 + 4 }" | 3 + 4 = #{ 3 + 4 } |
| literals/literals.rb:62:1:62:5 | "foo" | foo |
| literals/literals.rb:62:7:62:11 | "bar" | bar |
| literals/literals.rb:62:13:62:17 | "baz" | baz |
| literals/literals.rb:63:1:63:7 | "foo" | foo |
| literals/literals.rb:63:9:63:13 | "bar" | bar |
| literals/literals.rb:63:15:63:19 | "baz" | baz |
| literals/literals.rb:64:1:64:5 | "foo" | foo |
| literals/literals.rb:64:14:64:14 | 1 | 1 |
| literals/literals.rb:64:14:64:18 | ... * ... | 1 |
| literals/literals.rb:64:18:64:18 | 1 | 1 |
| literals/literals.rb:64:23:64:27 | "baz" | baz |
| literals/literals.rb:65:17:65:17 | 2 | 2 |
| literals/literals.rb:65:17:65:21 | ... + ... | 5 |
| literals/literals.rb:65:21:65:21 | 3 | 3 |
| literals/literals.rb:66:17:66:17 | 1 | 1 |
| literals/literals.rb:66:17:66:19 | ... + ... | 10 |
| literals/literals.rb:66:19:66:19 | 9 | 9 |
| literals/literals.rb:69:1:69:2 | ?x | ?x |
| literals/literals.rb:70:1:70:3 | ?\\n | ?\\n |
| literals/literals.rb:71:1:71:3 | ?\\s | ?\\s |
| literals/literals.rb:72:1:72:3 | ?\\\\ | ?\\\\ |
| literals/literals.rb:73:1:73:7 | ?\\u{58} | ?\\u{58} |
| literals/literals.rb:74:1:74:5 | ?\\C-a | ?\\C-a |
| literals/literals.rb:75:1:75:5 | ?\\M-a | ?\\M-a |
| literals/literals.rb:76:1:76:8 | ?\\M-\\C-a | ?\\M-\\C-a |
| literals/literals.rb:77:1:77:8 | ?\\C-\\M-a | ?\\C-\\M-a |
| literals/literals.rb:80:1:80:3 | :"" | |
| literals/literals.rb:81:1:81:6 | :hello | hello |
| literals/literals.rb:82:1:82:10 | :"foo bar" | foo bar |
| literals/literals.rb:83:1:83:10 | :"bar baz" | bar baz |
| literals/literals.rb:84:3:84:5 | :foo | foo |
| literals/literals.rb:84:8:84:12 | "bar" | bar |
| literals/literals.rb:85:1:85:10 | :"wibble" | wibble |
| literals/literals.rb:86:1:86:17 | :"wibble wobble" | wibble wobble |
| literals/literals.rb:87:10:87:10 | 2 | 2 |
| literals/literals.rb:87:10:87:14 | ... + ... | 4 |
| literals/literals.rb:87:14:87:14 | 2 | 2 |
| literals/literals.rb:88:1:88:17 | :"foo_#{ 1 + 1 }" | foo_#{ 1 + 1 } |
| literals/literals.rb:89:1:89:18 | :"foo_#{ 3 - 2 }" | foo_#{ 3 - 2 } |
| literals/literals.rb:93:2:93:2 | 1 | 1 |
| literals/literals.rb:93:5:93:5 | 2 | 2 |
| literals/literals.rb:93:8:93:8 | 3 | 3 |
| literals/literals.rb:94:2:94:2 | 4 | 4 |
| literals/literals.rb:94:5:94:5 | 5 | 5 |
| literals/literals.rb:94:8:94:9 | 12 | 12 |
| literals/literals.rb:94:8:94:13 | ... / ... | 6 |
| literals/literals.rb:94:13:94:13 | 2 | 2 |
| literals/literals.rb:95:2:95:2 | 7 | 7 |
| literals/literals.rb:95:6:95:6 | 8 | 8 |
| literals/literals.rb:95:9:95:9 | 9 | 9 |
| literals/literals.rb:99:4:99:6 | "foo" | foo |
| literals/literals.rb:99:8:99:10 | "bar" | bar |
| literals/literals.rb:99:12:99:14 | "baz" | baz |
| literals/literals.rb:100:4:100:6 | "foo" | foo |
| literals/literals.rb:100:8:100:10 | "bar" | bar |
| literals/literals.rb:100:12:100:14 | "baz" | baz |
| literals/literals.rb:101:4:101:6 | "foo" | foo |
| literals/literals.rb:101:13:101:13 | 1 | 1 |
| literals/literals.rb:101:13:101:15 | ... + ... | 2 |
| literals/literals.rb:101:15:101:15 | 1 | 1 |
| literals/literals.rb:101:18:101:20 | "baz" | baz |
| literals/literals.rb:102:4:102:6 | "foo" | foo |
| literals/literals.rb:102:8:102:16 | "bar#{1+1}" | bar#{1+1} |
| literals/literals.rb:102:18:102:20 | "baz" | baz |
| literals/literals.rb:106:4:106:6 | :"foo" | foo |
| literals/literals.rb:106:8:106:10 | :"bar" | bar |
| literals/literals.rb:106:12:106:14 | :"baz" | baz |
| literals/literals.rb:107:4:107:6 | :"foo" | foo |
| literals/literals.rb:107:8:107:10 | :"bar" | bar |
| literals/literals.rb:107:12:107:14 | :"baz" | baz |
| literals/literals.rb:108:4:108:6 | :"foo" | foo |
| literals/literals.rb:108:14:108:14 | 2 | 2 |
| literals/literals.rb:108:14:108:18 | ... + ... | 6 |
| literals/literals.rb:108:18:108:18 | 4 | 4 |
| literals/literals.rb:108:22:108:24 | :"baz" | baz |
| literals/literals.rb:109:4:109:6 | :"foo" | foo |
| literals/literals.rb:109:8:109:12 | :"bar#{" | bar#{ |
| literals/literals.rb:109:14:109:14 | :"2" | 2 |
| literals/literals.rb:109:16:109:16 | :"+" | + |
| literals/literals.rb:109:18:109:18 | :"4" | 4 |
| literals/literals.rb:109:20:109:20 | :"}" | } |
| literals/literals.rb:109:22:109:24 | :"baz" | baz |
| literals/literals.rb:113:3:113:5 | :foo | foo |
| literals/literals.rb:113:8:113:8 | 1 | 1 |
| literals/literals.rb:113:11:113:14 | :bar | bar |
| literals/literals.rb:113:19:113:19 | 2 | 2 |
| literals/literals.rb:113:22:113:26 | "baz" | baz |
| literals/literals.rb:113:31:113:31 | 3 | 3 |
| literals/literals.rb:114:3:114:5 | :foo | foo |
| literals/literals.rb:114:8:114:8 | 7 | 7 |
| literals/literals.rb:117:2:117:2 | 1 | 1 |
| literals/literals.rb:117:5:117:6 | 10 | 10 |
| literals/literals.rb:118:2:118:2 | 1 | 1 |
| literals/literals.rb:118:6:118:7 | 10 | 10 |
| literals/literals.rb:119:2:119:2 | 1 | 1 |
| literals/literals.rb:119:7:119:7 | 0 | 0 |
| literals/literals.rb:120:9:120:9 | 2 | 2 |
| literals/literals.rb:120:9:120:11 | ... + ... | 5 |
| literals/literals.rb:120:11:120:11 | 3 | 3 |
| literals/literals.rb:121:2:121:2 | 1 | 1 |
| literals/literals.rb:122:4:122:4 | 1 | 1 |
| literals/literals.rb:123:2:123:2 | 0 | 0 |
| literals/literals.rb:123:6:123:6 | 1 | 1 |
| literals/literals.rb:126:1:126:7 | `ls -l` | ls -l |
| literals/literals.rb:127:1:127:9 | `ls -l` | ls -l |
| literals/literals.rb:128:11:128:11 | 1 | 1 |
| literals/literals.rb:128:11:128:15 | ... + ... | 2 |
| literals/literals.rb:128:15:128:15 | 1 | 1 |
| literals/literals.rb:129:13:129:13 | 5 | 5 |
| literals/literals.rb:129:13:129:17 | ... - ... | 1 |
| literals/literals.rb:129:17:129:17 | 4 | 4 |
| literals/literals.rb:132:1:132:2 | // | |
| literals/literals.rb:133:1:133:5 | /foo/ | foo |
| literals/literals.rb:134:1:134:6 | /foo/ | foo |
| literals/literals.rb:135:1:135:13 | /foo+\\sbar\\S/ | foo+\\sbar\\S |
| literals/literals.rb:136:8:136:8 | 1 | 1 |
| literals/literals.rb:136:8:136:12 | ... + ... | 2 |
| literals/literals.rb:136:12:136:12 | 1 | 1 |
| literals/literals.rb:137:1:137:8 | /foo/ | foo |
| literals/literals.rb:138:1:138:4 | // | |
| literals/literals.rb:139:1:139:7 | /foo/ | foo |
| literals/literals.rb:140:1:140:8 | /foo/ | foo |
| literals/literals.rb:141:1:141:15 | /foo+\\sbar\\S/ | foo+\\sbar\\S |
| literals/literals.rb:142:10:142:10 | 1 | 1 |
| literals/literals.rb:142:10:142:14 | ... + ... | 2 |
| literals/literals.rb:142:14:142:14 | 1 | 1 |
| literals/literals.rb:143:1:143:10 | /foo/ | foo |
| literals/literals.rb:146:1:146:34 | "abcdefghijklmnopqrstuvwxyzabcdef" | abcdefghijklmnopqrstuvwxyzabcdef |
| literals/literals.rb:147:1:147:35 | "foobarfoobarfoobarfoobarfooba..." | foobarfoobarfoobarfoobarfoobarfoo |
| literals/literals.rb:148:1:148:40 | "foobar\\\\foobar\\\\foobar\\\\fooba..." | foobar\\\\foobar\\\\foobar\\\\foobar\\\\foobar |
| literals/literals.rb:151:9:151:13 | <<SQL | \nselect * from table\n |
| literals/literals.rb:158:11:158:16 | <<-BLA | \nsome text\\nand some more\n |
| literals/literals.rb:163:9:163:19 | <<~SQUIGGLY | \n indented stuff\n |
| literals/literals.rb:176:10:176:19 | <<`SCRIPT` | \n cat file.txt\n |
| misc/misc.erb:2:15:2:37 | "main_include_admin.js" | main_include_admin.js |
| misc/misc.rb:1:7:1:11 | "bar" | bar |
| misc/misc.rb:3:7:3:9 | foo | foo |
| misc/misc.rb:3:12:3:15 | :foo | foo |
| misc/misc.rb:3:18:3:21 | foo= | foo= |
| misc/misc.rb:3:24:3:25 | [] | [] |
| misc/misc.rb:3:28:3:30 | []= | []= |
| misc/misc.rb:4:15:4:17 | bar | bar |
| misc/misc.rb:5:7:5:9 | nil | nil |
| misc/misc.rb:5:12:5:15 | true | true |
| misc/misc.rb:5:18:5:22 | false | false |
| misc/misc.rb:5:25:5:29 | super | super |
| misc/misc.rb:5:32:5:35 | self | self |
| misc/misc.rb:7:7:7:9 | new | new |
| misc/misc.rb:7:11:7:14 | :old | old |
| misc/misc.rb:8:7:8:10 | foo= | foo= |
| misc/misc.rb:8:12:8:14 | []= | []= |
| misc/misc.rb:9:7:9:11 | super | super |
| misc/misc.rb:9:13:9:16 | self | self |
| misc/misc.rb:10:13:10:15 | bar | bar |
| misc/misc.rb:10:19:10:24 | :"foo" | foo |
| modules/classes.rb:11:28:11:31 | :baz | baz |
| modules/classes.rb:22:10:22:12 | "a" | a |
| modules/classes.rb:26:10:26:12 | "b" | b |
| modules/classes.rb:30:17:30:19 | 123 | 123 |
| modules/classes.rb:40:5:40:11 | "hello" | hello |
| modules/classes.rb:41:10:41:10 | x | hello |
| modules/classes.rb:43:5:43:7 | 100 | 100 |
| modules/classes.rb:47:10:47:17 | "wibble" | wibble |
| modules/classes.rb:51:18:51:20 | 456 | 456 |
| modules/modules.rb:12:10:12:26 | "module Foo::Bar" | module Foo::Bar |
| modules/modules.rb:13:19:13:19 | 0 | 0 |
| modules/modules.rb:22:8:22:19 | "module Foo" | module Foo |
| modules/modules.rb:23:17:23:17 | 1 | 1 |
| modules/modules.rb:33:8:33:25 | "module Foo again" | module Foo again |
| modules/modules.rb:34:17:34:17 | 2 | 2 |
| modules/modules.rb:44:8:44:19 | "module Bar" | module Bar |
| modules/modules.rb:45:17:45:17 | 3 | 3 |
| modules/modules.rb:55:8:55:30 | "module Foo::Bar again" | module Foo::Bar again |
| modules/modules.rb:56:17:56:17 | 4 | 4 |
| modules/toplevel.rb:1:6:1:12 | "world" | world |
| modules/toplevel.rb:3:12:3:16 | "!!!" | !!! |
| modules/toplevel.rb:5:14:5:20 | "hello" | hello |
| operations/operations.rb:3:5:3:5 | 0 | 0 |
| operations/operations.rb:4:5:4:5 | 0 | 0 |
| operations/operations.rb:5:7:5:7 | 0 | 0 |
| operations/operations.rb:6:8:6:8 | 0 | 0 |
| operations/operations.rb:7:7:7:7 | 0 | 0 |
| operations/operations.rb:8:7:8:7 | 0 | 0 |
| operations/operations.rb:9:10:9:10 | 0 | 0 |
| operations/operations.rb:10:5:10:5 | 0 | 0 |
| operations/operations.rb:11:8:11:8 | 0 | 0 |
| operations/operations.rb:12:5:12:5 | 0 | 0 |
| operations/operations.rb:13:8:13:8 | 0 | 0 |
| operations/operations.rb:14:7:14:7 | 0 | 0 |
| operations/operations.rb:15:9:15:9 | 0 | 0 |
| operations/operations.rb:16:7:16:7 | 0 | 0 |
| operations/operations.rb:17:5:17:5 | 0 | 0 |
| operations/operations.rb:18:5:18:5 | 0 | 0 |
| operations/operations.rb:19:5:19:5 | 0 | 0 |
| operations/operations.rb:20:5:20:5 | 0 | 0 |
| operations/operations.rb:23:2:23:2 | a | 0 |
| operations/operations.rb:24:5:24:5 | b | 0 |
| operations/operations.rb:25:2:25:3 | 14 | 14 |
| operations/operations.rb:26:2:26:2 | 7 | 7 |
| operations/operations.rb:27:2:27:2 | x | 0 |
| operations/operations.rb:28:10:28:12 | foo | 0 |
| operations/operations.rb:29:17:29:17 | 1 | 1 |
| operations/operations.rb:29:22:29:22 | 2 | 2 |
| operations/operations.rb:29:26:29:26 | :a | a |
| operations/operations.rb:29:28:29:28 | 3 | 3 |
| operations/operations.rb:29:34:29:34 | :b | b |
| operations/operations.rb:29:36:29:36 | 4 | 4 |
| operations/operations.rb:29:39:29:39 | :c | c |
| operations/operations.rb:29:41:29:41 | 5 | 5 |
| operations/operations.rb:32:1:32:1 | w | 0 |
| operations/operations.rb:32:1:32:7 | ... + ... | 234 |
| operations/operations.rb:32:5:32:7 | 234 | 234 |
| operations/operations.rb:33:1:33:1 | x | 0 |
| operations/operations.rb:33:1:33:6 | ... - ... | -17 |
| operations/operations.rb:33:5:33:6 | 17 | 17 |
| operations/operations.rb:34:1:34:1 | y | 0 |
| operations/operations.rb:34:1:34:6 | ... * ... | 0 |
| operations/operations.rb:34:5:34:6 | 10 | 10 |
| operations/operations.rb:35:1:35:1 | z | 0 |
| operations/operations.rb:35:1:35:5 | ... / ... | 0 |
| operations/operations.rb:35:5:35:5 | 2 | 2 |
| operations/operations.rb:36:1:36:3 | num | 0 |
| operations/operations.rb:36:7:36:7 | 2 | 2 |
| operations/operations.rb:37:1:37:4 | base | 0 |
| operations/operations.rb:37:9:37:13 | power | 0 |
| operations/operations.rb:40:1:40:3 | foo | 0 |
| operations/operations.rb:40:8:40:10 | bar | 0 |
| operations/operations.rb:41:1:41:3 | baz | 0 |
| operations/operations.rb:41:9:41:11 | qux | 0 |
| operations/operations.rb:42:1:42:1 | a | 0 |
| operations/operations.rb:42:6:42:6 | b | 0 |
| operations/operations.rb:43:1:43:1 | x | 0 |
| operations/operations.rb:43:6:43:6 | y | 0 |
| operations/operations.rb:46:1:46:1 | x | 0 |
| operations/operations.rb:46:6:46:6 | 3 | 3 |
| operations/operations.rb:47:1:47:1 | y | 0 |
| operations/operations.rb:47:6:47:7 | 16 | 16 |
| operations/operations.rb:48:1:48:3 | foo | 0 |
| operations/operations.rb:48:7:48:10 | 0xff | 0xff |
| operations/operations.rb:49:1:49:3 | bar | 0 |
| operations/operations.rb:49:7:49:10 | 0x02 | 0x02 |
| operations/operations.rb:50:1:50:3 | baz | 0 |
| operations/operations.rb:50:7:50:9 | qux | 0 |
| operations/operations.rb:53:1:53:1 | x | 0 |
| operations/operations.rb:53:6:53:6 | y | 0 |
| operations/operations.rb:54:1:54:1 | a | 0 |
| operations/operations.rb:54:6:54:8 | 123 | 123 |
| operations/operations.rb:55:1:55:1 | m | 0 |
| operations/operations.rb:55:7:55:7 | n | 0 |
| operations/operations.rb:58:1:58:1 | x | 0 |
| operations/operations.rb:58:5:58:5 | 0 | 0 |
| operations/operations.rb:59:1:59:1 | y | 0 |
| operations/operations.rb:59:6:59:8 | 100 | 100 |
| operations/operations.rb:60:1:60:1 | a | 0 |
| operations/operations.rb:60:5:60:5 | b | 0 |
| operations/operations.rb:61:1:61:1 | 7 | 7 |
| operations/operations.rb:61:6:61:8 | foo | 0 |
| operations/operations.rb:64:1:64:1 | a | 0 |
| operations/operations.rb:64:7:64:7 | b | 0 |
| operations/operations.rb:65:1:65:4 | name | 0 |
| operations/operations.rb:65:9:65:15 | /foo.*/ | foo.* |
| operations/operations.rb:66:1:66:6 | handle | 0 |
| operations/operations.rb:66:11:66:17 | /.*bar/ | .*bar |
| operations/operations.rb:69:1:69:1 | x | 0 |
| operations/operations.rb:69:3:69:4 | ... + ... | 128 |
| operations/operations.rb:69:6:69:8 | 128 | 128 |
| operations/operations.rb:70:1:70:1 | y | 0 |
| operations/operations.rb:70:3:70:4 | ... - ... | -32 |
| operations/operations.rb:70:6:70:7 | 32 | 32 |
| operations/operations.rb:71:1:71:1 | a | 0 |
| operations/operations.rb:71:3:71:4 | ... * ... | 0 |
| operations/operations.rb:71:6:71:7 | 12 | 12 |
| operations/operations.rb:72:1:72:1 | b | 0 |
| operations/operations.rb:72:3:72:4 | ... / ... | 0 |
| operations/operations.rb:72:6:72:6 | 4 | 4 |
| operations/operations.rb:73:1:73:1 | z | 0 |
| operations/operations.rb:73:6:73:6 | 2 | 2 |
| operations/operations.rb:74:1:74:3 | foo | 0 |
| operations/operations.rb:74:9:74:11 | bar | 0 |
| operations/operations.rb:77:2:77:2 | x | 128 |
| operations/operations.rb:77:8:77:8 | y | -32 |
| operations/operations.rb:78:2:78:2 | a | 0 |
| operations/operations.rb:78:8:78:8 | b | 0 |
| operations/operations.rb:81:8:81:8 | 2 | 2 |
| operations/operations.rb:82:2:82:2 | y | -32 |
| operations/operations.rb:82:8:82:8 | 3 | 3 |
| operations/operations.rb:83:9:83:12 | mask | 0 |
| operations/operations.rb:84:2:84:4 | bar | 0 |
| operations/operations.rb:84:9:84:12 | 0x01 | 0x01 |
| operations/operations.rb:85:2:85:4 | baz | 0 |
| operations/operations.rb:85:9:85:11 | qux | 0 |
| operations/operations.rb:88:8:88:8 | 1 | 1 |
| operations/operations.rb:89:9:89:9 | 2 | 2 |
| operations/operations.rb:91:9:91:9 | 3 | 3 |
| operations/operations.rb:92:10:92:10 | 4 | 4 |
| operations/operations.rb:95:15:95:15 | 5 | 5 |
| operations/operations.rb:96:16:96:16 | 6 | 6 |
| params/params.rb:41:46:41:46 | 7 | 7 |
| params/params.rb:47:19:47:21 | :bar | bar |
| params/params.rb:47:24:47:24 | 2 | 2 |
| params/params.rb:47:27:47:29 | :foo | foo |
| params/params.rb:47:32:47:32 | 3 | 3 |
| params/params.rb:49:37:49:39 | 100 | 100 |
| params/params.rb:53:44:53:44 | 3 | 3 |
| params/params.rb:58:46:58:46 | 0 | 0 |
| params/params.rb:58:56:58:58 | 100 | 100 |
| params/params.rb:63:14:63:19 | "Zeus" | Zeus |
| params/params.rb:65:41:65:42 | 99 | 99 |
| params/params.rb:70:42:70:45 | 1000 | 1000 |
| params/params.rb:70:52:70:53 | 20 | 20 |

View File

@@ -0,0 +1,4 @@
import ruby
from Expr e
select e, e.getValueText()

View File

@@ -41,8 +41,8 @@ break_ensure.rb:
#-----| -> do ...
# 3| ... > ...
#-----| true -> break
#-----| raise -> for ... in ...
#-----| true -> break
#-----| false -> if ...
# 3| element
@@ -580,12 +580,12 @@ cfg.html.erb:
# 12| self
#-----| -> call to a
# 12| :id
#-----| -> "a"
# 12| Pair
#-----| -> call to link_to
# 12| :id
#-----| -> "a"
# 12| "a"
#-----| -> Pair
@@ -1493,12 +1493,12 @@ cfg.rb:
# 97| "d"
#-----| -> Pair
# 97| :e
#-----| -> "f"
# 97| Pair
#-----| -> {...}
# 97| :e
#-----| -> "f"
# 97| "f"
#-----| -> Pair
@@ -1826,6 +1826,9 @@ cfg.rb:
# 134| EmptyModule
#-----| -> ... rescue ...
# 136| ... rescue ...
#-----| -> 1
# 136| ... / ...
#-----| raise -> self
#-----| -> __synth__0
@@ -1833,9 +1836,6 @@ cfg.rb:
# 136| 1
#-----| -> 0
# 136| ... rescue ...
#-----| -> 1
# 136| 0
#-----| -> ... / ...

View File

@@ -1,4 +1,4 @@
import ruby
import codeql.ruby.dataflow.internal.DataFlowPrivate
select any(ReturnNode node)
select any(ReturningNode node)

View File

@@ -13,8 +13,8 @@ actionControllerActionMethods
| app/controllers/foo/bars_controller.rb:15:3:19:5 | show |
paramsCalls
| ActiveRecordInjection.rb:35:30:35:35 | call to params |
| ActiveRecordInjection.rb:39:30:39:35 | call to params |
| ActiveRecordInjection.rb:43:32:43:37 | call to params |
| ActiveRecordInjection.rb:39:29:39:34 | call to params |
| ActiveRecordInjection.rb:43:31:43:36 | call to params |
| ActiveRecordInjection.rb:48:21:48:26 | call to params |
| ActiveRecordInjection.rb:54:34:54:39 | call to params |
| ActiveRecordInjection.rb:56:23:56:28 | call to params |
@@ -24,7 +24,7 @@ paramsCalls
| ActiveRecordInjection.rb:77:12:77:17 | call to params |
| ActiveRecordInjection.rb:83:12:83:17 | call to params |
| ActiveRecordInjection.rb:88:15:88:20 | call to params |
| ActiveRecordInjection.rb:94:22:94:27 | call to params |
| ActiveRecordInjection.rb:94:21:94:26 | call to params |
| app/controllers/foo/bars_controller.rb:8:21:8:26 | call to params |
| app/controllers/foo/bars_controller.rb:9:10:9:15 | call to params |
| app/controllers/foo/bars_controller.rb:16:21:16:26 | call to params |
@@ -32,8 +32,8 @@ paramsCalls
| app/views/foo/bars/show.html.erb:5:9:5:14 | call to params |
paramsSources
| ActiveRecordInjection.rb:35:30:35:35 | call to params |
| ActiveRecordInjection.rb:39:30:39:35 | call to params |
| ActiveRecordInjection.rb:43:32:43:37 | call to params |
| ActiveRecordInjection.rb:39:29:39:34 | call to params |
| ActiveRecordInjection.rb:43:31:43:36 | call to params |
| ActiveRecordInjection.rb:48:21:48:26 | call to params |
| ActiveRecordInjection.rb:54:34:54:39 | call to params |
| ActiveRecordInjection.rb:56:23:56:28 | call to params |
@@ -43,7 +43,7 @@ paramsSources
| ActiveRecordInjection.rb:77:12:77:17 | call to params |
| ActiveRecordInjection.rb:83:12:83:17 | call to params |
| ActiveRecordInjection.rb:88:15:88:20 | call to params |
| ActiveRecordInjection.rb:94:22:94:27 | call to params |
| ActiveRecordInjection.rb:94:21:94:26 | call to params |
| app/controllers/foo/bars_controller.rb:8:21:8:26 | call to params |
| app/controllers/foo/bars_controller.rb:9:10:9:15 | call to params |
| app/controllers/foo/bars_controller.rb:16:21:16:26 | call to params |

View File

@@ -4,13 +4,13 @@ activeRecordModelClasses
| ActiveRecordInjection.rb:19:1:25:3 | Admin |
activeRecordSqlExecutionRanges
| ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" |
| ActiveRecordInjection.rb:23:17:23:25 | condition |
| ActiveRecordInjection.rb:23:16:23:24 | condition |
| ActiveRecordInjection.rb:35:30:35:44 | ...[...] |
| ActiveRecordInjection.rb:39:21:39:43 | "id = '#{...}'" |
| ActiveRecordInjection.rb:43:23:43:45 | "id = '#{...}'" |
| ActiveRecordInjection.rb:39:20:39:42 | "id = '#{...}'" |
| ActiveRecordInjection.rb:43:22:43:44 | "id = '#{...}'" |
| ActiveRecordInjection.rb:47:16:47:21 | <<-SQL |
| ActiveRecordInjection.rb:54:20:54:47 | "user.id = '#{...}'" |
| ActiveRecordInjection.rb:68:21:68:33 | ... + ... |
| ActiveRecordInjection.rb:68:20:68:32 | ... + ... |
| ActiveRecordInjection.rb:75:16:75:28 | "name #{...}" |
| ActiveRecordInjection.rb:80:20:80:39 | "username = #{...}" |
activeRecordModelClassMethodCalls
@@ -19,28 +19,28 @@ activeRecordModelClassMethodCalls
| ActiveRecordInjection.rb:10:5:10:68 | call to find |
| ActiveRecordInjection.rb:15:5:15:40 | call to find_by |
| ActiveRecordInjection.rb:15:5:15:46 | call to users |
| ActiveRecordInjection.rb:23:5:23:26 | call to destroy_all |
| ActiveRecordInjection.rb:23:5:23:25 | call to destroy_by |
| ActiveRecordInjection.rb:35:5:35:45 | call to calculate |
| ActiveRecordInjection.rb:39:5:39:44 | call to delete_all |
| ActiveRecordInjection.rb:43:5:43:47 | call to destroy_all |
| ActiveRecordInjection.rb:39:5:39:43 | call to delete_by |
| ActiveRecordInjection.rb:43:5:43:46 | call to destroy_by |
| ActiveRecordInjection.rb:47:5:47:35 | call to where |
| ActiveRecordInjection.rb:54:5:54:14 | call to where |
| ActiveRecordInjection.rb:54:5:54:48 | call to not |
| ActiveRecordInjection.rb:56:5:56:51 | call to authenticate |
| ActiveRecordInjection.rb:68:5:68:34 | call to delete_all |
| ActiveRecordInjection.rb:68:5:68:33 | call to delete_by |
| ActiveRecordInjection.rb:75:5:75:29 | call to order |
| ActiveRecordInjection.rb:80:7:80:40 | call to find_by |
| ActiveRecordInjection.rb:85:5:85:33 | call to find_by |
| ActiveRecordInjection.rb:88:5:88:34 | call to find |
| ActiveRecordInjection.rb:94:5:94:46 | call to delete_all |
| ActiveRecordInjection.rb:94:5:94:45 | call to delete_by |
potentiallyUnsafeSqlExecutingMethodCall
| ActiveRecordInjection.rb:10:5:10:68 | call to find |
| ActiveRecordInjection.rb:23:5:23:26 | call to destroy_all |
| ActiveRecordInjection.rb:23:5:23:25 | call to destroy_by |
| ActiveRecordInjection.rb:35:5:35:45 | call to calculate |
| ActiveRecordInjection.rb:39:5:39:44 | call to delete_all |
| ActiveRecordInjection.rb:43:5:43:47 | call to destroy_all |
| ActiveRecordInjection.rb:39:5:39:43 | call to delete_by |
| ActiveRecordInjection.rb:43:5:43:46 | call to destroy_by |
| ActiveRecordInjection.rb:47:5:47:35 | call to where |
| ActiveRecordInjection.rb:54:5:54:48 | call to not |
| ActiveRecordInjection.rb:68:5:68:34 | call to delete_all |
| ActiveRecordInjection.rb:68:5:68:33 | call to delete_by |
| ActiveRecordInjection.rb:75:5:75:29 | call to order |
| ActiveRecordInjection.rb:80:7:80:40 | call to find_by |

View File

@@ -17,10 +17,10 @@ class User < ApplicationRecord
end
class Admin < User
def self.delete_all(condition = nil)
# BAD: `delete_all` overrides an ActiveRecord method, but doesn't perform
def self.delete_by(condition = nil)
# BAD: `delete_by` overrides an ActiveRecord method, but doesn't perform
# any validation before passing its arguments on to another ActiveRecord method
destroy_all(condition)
destroy_by(condition)
end
end
@@ -36,11 +36,11 @@ class FooController < ActionController::Base
# BAD: executes `DELETE FROM "users" WHERE (id = '#{params[:id]}')`
# where `params[:id]` is unsanitized
User.delete_all("id = '#{params[:id]}'")
User.delete_by("id = '#{params[:id]}'")
# BAD: executes `SELECT "users".* FROM "users" WHERE (id = '#{params[:id]}')`
# where `params[:id]` is unsanitized
User.destroy_all(["id = '#{params[:id]}'"])
User.destroy_by(["id = '#{params[:id]}'"])
# BAD: executes `SELECT "users".* FROM "users" WHERE id BETWEEN '#{params[:min_id]}' AND 100000`
# where `params[:min_id]` is unsanitized
@@ -65,7 +65,7 @@ class BarController < ApplicationController
# BAD: executes `DELETE FROM "users" WHERE (id = #{uid})`
# where `uid` is unsantized
User.delete_all("id " + uidEq)
User.delete_by("id " + uidEq)
end
def safe_paths
@@ -91,6 +91,6 @@ end
class BazController < BarController
def yet_another_handler
Admin.delete_all(params[:admin_condition])
Admin.delete_by(params[:admin_condition])
end
end

View File

@@ -1,10 +1,10 @@
# Uses of eval and send
eval("raise \"error\"")
eval("raise \"error\"", binding, "file", 1)
send("raise", "error")
a = []
a.send("raise", "error")
a.send("push", "1")
class Foo
def eval(x)
@@ -21,3 +21,6 @@ class Foo
end
Foo.new.send("exit", 1)
Foo.new.instance_eval("self.class", "file.rb", 3)
Foo.class_eval("def foo; 1; end", "file.rb", 1)
Foo.module_eval("def bar; 1; end", "other_file.rb", 2)

View File

@@ -59,7 +59,13 @@ open3PipelineCallExecutions
| CommandExecution.rb:64:1:64:44 | call to pipeline_start |
| CommandExecution.rb:65:1:65:38 | call to pipeline |
evalCallCodeExecutions
| Eval.rb:3:1:3:23 | call to eval |
| Eval.rb:3:1:3:43 | call to eval | Eval.rb:3:6:3:22 | "raise \\"error\\"" |
sendCallCodeExecutions
| Eval.rb:4:1:4:22 | call to send |
| Eval.rb:7:1:7:24 | call to send |
| Eval.rb:4:1:4:22 | call to send | Eval.rb:4:6:4:12 | "raise" |
| Eval.rb:7:1:7:19 | call to send | Eval.rb:7:8:7:13 | "push" |
instanceEvalCallCodeExecutions
| Eval.rb:24:1:24:49 | call to instance_eval | Eval.rb:24:23:24:34 | "self.class" |
classEvalCallCodeExecutions
| Eval.rb:25:1:25:47 | call to class_eval | Eval.rb:25:16:25:32 | "def foo; 1; end" |
moduleEvalCallCodeExecutions
| Eval.rb:26:1:26:54 | call to module_eval | Eval.rb:26:17:26:33 | "def bar; 1; end" |

View File

@@ -1,4 +1,5 @@
import codeql.ruby.frameworks.StandardLibrary
import codeql.ruby.DataFlow
query predicate subshellLiteralExecutions(SubshellLiteralExecution e) { any() }
@@ -14,6 +15,18 @@ query predicate open3CallExecutions(Open3Call c) { any() }
query predicate open3PipelineCallExecutions(Open3PipelineCall c) { any() }
query predicate evalCallCodeExecutions(EvalCallCodeExecution e) { any() }
query DataFlow::Node evalCallCodeExecutions(EvalCallCodeExecution e) { result = e.getCode() }
query predicate sendCallCodeExecutions(SendCallCodeExecution e) { any() }
query DataFlow::Node sendCallCodeExecutions(SendCallCodeExecution e) { result = e.getCode() }
query DataFlow::Node instanceEvalCallCodeExecutions(InstanceEvalCallCodeExecution e) {
result = e.getCode()
}
query DataFlow::Node classEvalCallCodeExecutions(ClassEvalCallCodeExecution e) {
result = e.getCode()
}
query DataFlow::Node moduleEvalCallCodeExecutions(ModuleEvalCallCodeExecution e) {
result = e.getCode()
}

View File

@@ -1,4 +1,4 @@
import codeql.ruby.frameworks.http_clients.Excon
import codeql.ruby.DataFlow
query DataFlow::Node exconHTTPRequests(ExconHTTPRequest e) { result = e.getResponseBody() }
query DataFlow::Node exconHttpRequests(ExconHttpRequest e) { result = e.getResponseBody() }

View File

@@ -1,4 +1,4 @@
import codeql.ruby.frameworks.http_clients.Faraday
import codeql.ruby.DataFlow
query DataFlow::Node faradayHTTPRequests(FaradayHTTPRequest e) { result = e.getResponseBody() }
query DataFlow::Node faradayHttpRequests(FaradayHttpRequest e) { result = e.getResponseBody() }

View File

@@ -0,0 +1,9 @@
| HttpClient.rb:3:9:3:45 | call to get | HttpClient.rb:4:1:4:10 | call to body |
| HttpClient.rb:6:9:6:65 | call to post | HttpClient.rb:7:1:7:13 | call to content |
| HttpClient.rb:9:9:9:64 | call to put | HttpClient.rb:10:1:10:15 | call to http_body |
| HttpClient.rb:12:9:12:48 | call to delete | HttpClient.rb:13:1:13:10 | call to dump |
| HttpClient.rb:15:9:15:46 | call to head | HttpClient.rb:16:1:16:10 | call to body |
| HttpClient.rb:18:9:18:49 | call to options | HttpClient.rb:19:1:19:13 | call to content |
| HttpClient.rb:21:9:21:47 | call to trace | HttpClient.rb:22:1:22:15 | call to http_body |
| HttpClient.rb:24:9:24:53 | call to get_content | HttpClient.rb:24:9:24:53 | call to get_content |
| HttpClient.rb:26:10:26:74 | call to post_content | HttpClient.rb:26:10:26:74 | call to post_content |

View File

@@ -0,0 +1,4 @@
import codeql.ruby.frameworks.http_clients.HttpClient
import codeql.ruby.DataFlow
query DataFlow::Node httpClientRequests(HttpClientRequest e) { result = e.getResponseBody() }

View File

@@ -0,0 +1,26 @@
require "httpclient"
resp1 = HTTPClient.get("http://example.com/")
resp1.body
resp2 = HTTPClient.post("http://example.com/", body: "some_data")
resp2.content
resp3 = HTTPClient.put("http://example.com/", body: "some_data")
resp3.http_body
resp5 = HTTPClient.delete("http://example.com/")
resp5.dump
resp6 = HTTPClient.head("http://example.com/")
resp6.body
resp7 = HTTPClient.options("http://example.com/")
resp7.content
resp8 = HTTPClient.trace("http://example.com/")
resp8.http_body
resp9 = HTTPClient.get_content("http://example.com/")
resp10 = HTTPClient.post_content("http://example.com/", body: "some_data")

View File

@@ -0,0 +1,7 @@
| Httparty.rb:5:1:5:35 | call to get | Httparty.rb:5:1:5:35 | call to get |
| Httparty.rb:7:1:7:55 | call to post | Httparty.rb:7:1:7:55 | call to post |
| Httparty.rb:9:1:9:54 | call to put | Httparty.rb:9:1:9:54 | call to put |
| Httparty.rb:11:1:11:56 | call to patch | Httparty.rb:11:1:11:56 | call to patch |
| Httparty.rb:15:9:15:46 | call to delete | Httparty.rb:16:1:16:10 | call to body |
| Httparty.rb:18:9:18:44 | call to head | Httparty.rb:19:1:19:10 | call to body |
| Httparty.rb:21:9:21:47 | call to options | Httparty.rb:22:1:22:10 | call to body |

View File

@@ -0,0 +1,4 @@
import codeql.ruby.frameworks.http_clients.Httparty
import codeql.ruby.DataFlow
query DataFlow::Node httpartyRequests(HttpartyRequest e) { result = e.getResponseBody() }

View File

@@ -0,0 +1,31 @@
require "httparty"
# If the response body is not nil or an empty string, it will be parsed and returned directly.
HTTParty.get("http://example.com/")
HTTParty.post("http://example.com/", body: "some_data")
HTTParty.put("http://example.com/", body: "some_data")
HTTParty.patch("http://example.com/", body: "some_data")
# Otherwise, `HTTParty::Response` will be returned, which has a `#body` method.
resp5 = HTTParty.delete("http://example.com/")
resp5.body
resp6 = HTTParty.head("http://example.com/")
resp6.body
resp7 = HTTParty.options("http://example.com/")
resp7.body
# HTTParty methods can also be included in other classes.
# This is not yet modelled.
class MyClient
inlcude HTTParty
end
MyClient.get("http://example.com")

View File

@@ -1,8 +0,0 @@
| NetHTTP.rb:4:1:4:18 | call to get | NetHTTP.rb:4:1:4:18 | call to get |
| NetHTTP.rb:6:8:6:50 | call to post | NetHTTP.rb:7:1:7:9 | call to body |
| NetHTTP.rb:6:8:6:50 | call to post | NetHTTP.rb:8:1:8:14 | call to read_body |
| NetHTTP.rb:6:8:6:50 | call to post | NetHTTP.rb:9:1:9:11 | call to entity |
| NetHTTP.rb:13:6:13:17 | call to get | NetHTTP.rb:18:1:18:7 | call to body |
| NetHTTP.rb:14:6:14:18 | call to post | NetHTTP.rb:19:1:19:12 | call to read_body |
| NetHTTP.rb:15:6:15:17 | call to put | NetHTTP.rb:20:1:20:9 | call to entity |
| NetHTTP.rb:24:3:24:33 | call to get | NetHTTP.rb:27:1:27:28 | call to body |

View File

@@ -1,4 +0,0 @@
import codeql.ruby.frameworks.http_clients.NetHTTP
import codeql.ruby.DataFlow
query DataFlow::Node netHTTPRequests(NetHTTPRequest e) { result = e.getResponseBody() }

View File

@@ -0,0 +1,8 @@
| NetHttp.rb:4:1:4:18 | call to get | NetHttp.rb:4:1:4:18 | call to get |
| NetHttp.rb:6:8:6:50 | call to post | NetHttp.rb:7:1:7:9 | call to body |
| NetHttp.rb:6:8:6:50 | call to post | NetHttp.rb:8:1:8:14 | call to read_body |
| NetHttp.rb:6:8:6:50 | call to post | NetHttp.rb:9:1:9:11 | call to entity |
| NetHttp.rb:13:6:13:17 | call to get | NetHttp.rb:18:1:18:7 | call to body |
| NetHttp.rb:14:6:14:18 | call to post | NetHttp.rb:19:1:19:12 | call to read_body |
| NetHttp.rb:15:6:15:17 | call to put | NetHttp.rb:20:1:20:9 | call to entity |
| NetHttp.rb:24:3:24:33 | call to get | NetHttp.rb:27:1:27:28 | call to body |

View File

@@ -0,0 +1,4 @@
import codeql.ruby.frameworks.http_clients.NetHttp
import codeql.ruby.DataFlow
query DataFlow::Node netHttpRequests(NetHttpRequest e) { result = e.getResponseBody() }

View File

@@ -0,0 +1,4 @@
| OpenURI.rb:3:9:3:41 | call to open | OpenURI.rb:4:1:4:10 | call to read |
| OpenURI.rb:6:9:6:34 | call to open | OpenURI.rb:7:1:7:15 | call to readlines |
| OpenURI.rb:9:9:9:38 | call to open | OpenURI.rb:10:1:10:10 | call to read |
| OpenURI.rb:12:9:12:45 | call to open | OpenURI.rb:13:1:13:10 | call to read |

View File

@@ -0,0 +1,4 @@
import codeql.ruby.frameworks.http_clients.OpenURI
import codeql.ruby.DataFlow
query DataFlow::Node openURIRequests(OpenURIRequest e) { result = e.getResponseBody() }

View File

@@ -0,0 +1,13 @@
require "open-uri"
resp1 = Kernel.open("http://example.com")
resp1.read
resp2 = open("http://example.com")
resp2.readlines
resp3 = URI.open("http://example.com")
resp3.read
resp4 = URI.parse("https://example.com").open
resp4.read

View File

@@ -1,6 +1,6 @@
import codeql.ruby.frameworks.http_clients.RestClient
import codeql.ruby.DataFlow
query DataFlow::Node restClientHTTPRequests(RestClientHTTPRequest e) {
query DataFlow::Node restClientHttpRequests(RestClientHttpRequest e) {
result = e.getResponseBody()
}

View File

@@ -0,0 +1,7 @@
| Typhoeus.rb:3:9:3:43 | call to get | Typhoeus.rb:4:1:4:10 | call to body |
| Typhoeus.rb:6:9:6:63 | call to post | Typhoeus.rb:7:1:7:10 | call to body |
| Typhoeus.rb:9:9:9:62 | call to put | Typhoeus.rb:10:1:10:10 | call to body |
| Typhoeus.rb:12:9:12:64 | call to patch | Typhoeus.rb:13:1:13:10 | call to body |
| Typhoeus.rb:15:9:15:46 | call to delete | Typhoeus.rb:16:1:16:10 | call to body |
| Typhoeus.rb:18:9:18:44 | call to head | Typhoeus.rb:19:1:19:10 | call to body |
| Typhoeus.rb:21:9:21:47 | call to options | Typhoeus.rb:22:1:22:10 | call to body |

View File

@@ -0,0 +1,4 @@
import codeql.ruby.frameworks.http_clients.Typhoeus
import codeql.ruby.DataFlow
query DataFlow::Node typhoeusHttpRequests(TyphoeusHttpRequest e) { result = e.getResponseBody() }

View File

@@ -0,0 +1,22 @@
require "typhoeus"
resp1 = Typhoeus.get("http://example.com/")
resp1.body
resp2 = Typhoeus.post("http://example.com/", body: "some_data")
resp2.body
resp3 = Typhoeus.put("http://example.com/", body: "some_data")
resp3.body
resp4 = Typhoeus.patch("http://example.com/", body: "some_data")
resp4.body
resp5 = Typhoeus.delete("http://example.com/")
resp5.body
resp6 = Typhoeus.head("http://example.com/")
resp6.body
resp7 = Typhoeus.options("http://example.com/")
resp7.body

View File

@@ -0,0 +1,11 @@
edges
| KernelOpen.rb:3:12:3:17 | call to params : | KernelOpen.rb:4:10:4:13 | file |
| KernelOpen.rb:3:12:3:17 | call to params : | KernelOpen.rb:5:13:5:16 | file |
nodes
| KernelOpen.rb:3:12:3:17 | call to params : | semmle.label | call to params : |
| KernelOpen.rb:4:10:4:13 | file | semmle.label | file |
| KernelOpen.rb:5:13:5:16 | file | semmle.label | file |
subpaths
#select
| KernelOpen.rb:4:10:4:13 | file | KernelOpen.rb:3:12:3:17 | call to params : | KernelOpen.rb:4:10:4:13 | file | This call to Kernel.open depends on a user-provided value. Replace it with File.open. |
| KernelOpen.rb:5:13:5:16 | file | KernelOpen.rb:3:12:3:17 | call to params : | KernelOpen.rb:5:13:5:16 | file | This call to IO.read depends on a user-provided value. Replace it with File.read. |

View File

@@ -0,0 +1 @@
queries/security/cwe-078/KernelOpen.ql

View File

@@ -0,0 +1,17 @@
class UsersController < ActionController::Base
def create
file = params[:file]
open(file) # BAD
IO.read(file) # BAD
File.open(file).read # GOOD
if file == "some/const/path.txt"
open(file) # GOOD - file path is sanitised by guard
end
if %w(some/const/1.txt some/const/2.txt).include? file
IO.read(file) # GOOD - file path is sanitised by guard
end
end
end

View File

@@ -17,10 +17,10 @@ class User < ApplicationRecord
end
class Admin < User
def self.delete_all(condition = nil)
# BAD: `delete_all` overrides an ActiveRecord method, but doesn't perform
def self.delete_by(condition = nil)
# BAD: `delete_by overrides an ActiveRecord method, but doesn't perform
# any validation before passing its arguments on to another ActiveRecord method
destroy_all(condition)
destroy_by(condition)
end
end
@@ -34,12 +34,26 @@ class FooController < ActionController::Base
# where `params[:column]` is unsanitized
User.calculate(:average, params[:column])
# BAD: executes `SELECT MAX(#{params[:column]}) FROM "users"`
# where `params[:column]` is unsanitized
User.maximum(params[:column])
# BAD: executes `DELETE FROM "users" WHERE (id = '#{params[:id]}')`
# where `params[:id]` is unsanitized
User.delete_by("id = '#{params[:id]}'")
# BAD: executes `DELETE FROM "users" WHERE (id = '#{params[:id]}')`
# where `params[:id]` is unsanitized
# (in Rails < 4.0)
User.delete_all("id = '#{params[:id]}'")
# BAD: executes `SELECT "users".* FROM "users" WHERE (id = '#{params[:id]}')`
# where `params[:id]` is unsanitized
User.destroy_by(["id = '#{params[:id]}'"])
# BAD: executes `SELECT "users".* FROM "users" WHERE (id = '#{params[:id]}')`
# where `params[:id]` is unsanitized
# (in Rails < 4.0)
User.destroy_all(["id = '#{params[:id]}'"])
# BAD: executes `SELECT "users".* FROM "users" WHERE id BETWEEN '#{params[:min_id]}' AND 100000`
@@ -54,6 +68,28 @@ class FooController < ActionController::Base
User.where.not("user.id = '#{params[:id]}'")
User.authenticate(params[:name], params[:pass])
# BAD: executes `SELECT "users".* FROM "users" WHERE (id = '#{params[:id]}')` LIMIT 1
# where `params[:id]` is unsanitized
User.find_or_initialize_by("id = '#{params[:id]}'")
user = User.first
# BAD: executes `SELECT "users".* FROM "users" WHERE id = 1 LIMIT 1 #{params[:lock]}`
# where `params[:lock]` is unsanitized
user.reload(lock: params[:lock])
# BAD: executes `SELECT #{params[:column]} FROM "users"`
# where `params[:column]` is unsanitized
User.select(params[:column])
User.reselect(params[:column])
# BAD: executes `SELECT "users".* FROM "users" WHERE (#{params[:condition]})`
# where `params[:condition]` is unsanitized
User.rewhere(params[:condition])
# BAD: executes `UPDATE "users" SET #{params[:fields]}`
# where `params[:fields]` is unsanitized
User.update_all(params[:fields])
end
end
@@ -65,7 +101,7 @@ class BarController < ApplicationController
# BAD: executes `DELETE FROM "users" WHERE (id = #{uid})`
# where `uid` is unsantized
User.delete_all("id " + uidEq)
User.delete_by("id " + uidEq)
end
def safe_paths
@@ -98,6 +134,6 @@ end
class BazController < BarController
def yet_another_handler
Admin.delete_all(params[:admin_condition])
Admin.delete_by(params[:admin_condition])
end
end

View File

@@ -1,51 +1,83 @@
edges
| ActiveRecordInjection.rb:8:25:8:28 | name : | ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" |
| ActiveRecordInjection.rb:8:31:8:34 | pass : | ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" |
| ActiveRecordInjection.rb:20:23:20:31 | condition : | ActiveRecordInjection.rb:23:17:23:25 | condition |
| ActiveRecordInjection.rb:20:22:20:30 | condition : | ActiveRecordInjection.rb:23:16:23:24 | condition |
| ActiveRecordInjection.rb:35:30:35:35 | call to params : | ActiveRecordInjection.rb:35:30:35:44 | ...[...] |
| ActiveRecordInjection.rb:39:30:39:35 | call to params : | ActiveRecordInjection.rb:39:21:39:43 | "id = '#{...}'" |
| ActiveRecordInjection.rb:43:32:43:37 | call to params : | ActiveRecordInjection.rb:43:23:43:45 | "id = '#{...}'" |
| ActiveRecordInjection.rb:48:21:48:26 | call to params : | ActiveRecordInjection.rb:47:16:47:21 | <<-SQL |
| ActiveRecordInjection.rb:54:34:54:39 | call to params : | ActiveRecordInjection.rb:54:20:54:47 | "user.id = '#{...}'" |
| ActiveRecordInjection.rb:56:23:56:28 | call to params : | ActiveRecordInjection.rb:56:23:56:35 | ...[...] : |
| ActiveRecordInjection.rb:56:23:56:35 | ...[...] : | ActiveRecordInjection.rb:8:25:8:28 | name : |
| ActiveRecordInjection.rb:56:38:56:43 | call to params : | ActiveRecordInjection.rb:56:38:56:50 | ...[...] : |
| ActiveRecordInjection.rb:56:38:56:50 | ...[...] : | ActiveRecordInjection.rb:8:31:8:34 | pass : |
| ActiveRecordInjection.rb:62:10:62:15 | call to params : | ActiveRecordInjection.rb:68:21:68:33 | ... + ... |
| ActiveRecordInjection.rb:101:22:101:27 | call to params : | ActiveRecordInjection.rb:101:22:101:45 | ...[...] : |
| ActiveRecordInjection.rb:101:22:101:45 | ...[...] : | ActiveRecordInjection.rb:20:23:20:31 | condition : |
| ActiveRecordInjection.rb:39:18:39:23 | call to params : | ActiveRecordInjection.rb:39:18:39:32 | ...[...] |
| ActiveRecordInjection.rb:43:29:43:34 | call to params : | ActiveRecordInjection.rb:43:20:43:42 | "id = '#{...}'" |
| ActiveRecordInjection.rb:48:30:48:35 | call to params : | ActiveRecordInjection.rb:48:21:48:43 | "id = '#{...}'" |
| ActiveRecordInjection.rb:52:31:52:36 | call to params : | ActiveRecordInjection.rb:52:22:52:44 | "id = '#{...}'" |
| ActiveRecordInjection.rb:57:32:57:37 | call to params : | ActiveRecordInjection.rb:57:23:57:45 | "id = '#{...}'" |
| ActiveRecordInjection.rb:62:21:62:26 | call to params : | ActiveRecordInjection.rb:61:16:61:21 | <<-SQL |
| ActiveRecordInjection.rb:68:34:68:39 | call to params : | ActiveRecordInjection.rb:68:20:68:47 | "user.id = '#{...}'" |
| ActiveRecordInjection.rb:70:23:70:28 | call to params : | ActiveRecordInjection.rb:70:23:70:35 | ...[...] : |
| ActiveRecordInjection.rb:70:23:70:35 | ...[...] : | ActiveRecordInjection.rb:8:25:8:28 | name : |
| ActiveRecordInjection.rb:70:38:70:43 | call to params : | ActiveRecordInjection.rb:70:38:70:50 | ...[...] : |
| ActiveRecordInjection.rb:70:38:70:50 | ...[...] : | ActiveRecordInjection.rb:8:31:8:34 | pass : |
| ActiveRecordInjection.rb:74:41:74:46 | call to params : | ActiveRecordInjection.rb:74:32:74:54 | "id = '#{...}'" |
| ActiveRecordInjection.rb:83:17:83:22 | call to params : | ActiveRecordInjection.rb:83:17:83:31 | ...[...] |
| ActiveRecordInjection.rb:84:19:84:24 | call to params : | ActiveRecordInjection.rb:84:19:84:33 | ...[...] |
| ActiveRecordInjection.rb:88:18:88:23 | call to params : | ActiveRecordInjection.rb:88:18:88:35 | ...[...] |
| ActiveRecordInjection.rb:92:21:92:26 | call to params : | ActiveRecordInjection.rb:92:21:92:35 | ...[...] |
| ActiveRecordInjection.rb:98:10:98:15 | call to params : | ActiveRecordInjection.rb:104:20:104:32 | ... + ... |
| ActiveRecordInjection.rb:137:21:137:26 | call to params : | ActiveRecordInjection.rb:137:21:137:44 | ...[...] : |
| ActiveRecordInjection.rb:137:21:137:44 | ...[...] : | ActiveRecordInjection.rb:20:22:20:30 | condition : |
nodes
| ActiveRecordInjection.rb:8:25:8:28 | name : | semmle.label | name : |
| ActiveRecordInjection.rb:8:31:8:34 | pass : | semmle.label | pass : |
| ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | semmle.label | "name='#{...}' and pass='#{...}'" |
| ActiveRecordInjection.rb:20:23:20:31 | condition : | semmle.label | condition : |
| ActiveRecordInjection.rb:23:17:23:25 | condition | semmle.label | condition |
| ActiveRecordInjection.rb:20:22:20:30 | condition : | semmle.label | condition : |
| ActiveRecordInjection.rb:23:16:23:24 | condition | semmle.label | condition |
| ActiveRecordInjection.rb:35:30:35:35 | call to params : | semmle.label | call to params : |
| ActiveRecordInjection.rb:35:30:35:44 | ...[...] | semmle.label | ...[...] |
| ActiveRecordInjection.rb:39:21:39:43 | "id = '#{...}'" | semmle.label | "id = '#{...}'" |
| ActiveRecordInjection.rb:39:30:39:35 | call to params : | semmle.label | call to params : |
| ActiveRecordInjection.rb:43:23:43:45 | "id = '#{...}'" | semmle.label | "id = '#{...}'" |
| ActiveRecordInjection.rb:43:32:43:37 | call to params : | semmle.label | call to params : |
| ActiveRecordInjection.rb:47:16:47:21 | <<-SQL | semmle.label | <<-SQL |
| ActiveRecordInjection.rb:48:21:48:26 | call to params : | semmle.label | call to params : |
| ActiveRecordInjection.rb:54:20:54:47 | "user.id = '#{...}'" | semmle.label | "user.id = '#{...}'" |
| ActiveRecordInjection.rb:54:34:54:39 | call to params : | semmle.label | call to params : |
| ActiveRecordInjection.rb:56:23:56:28 | call to params : | semmle.label | call to params : |
| ActiveRecordInjection.rb:56:23:56:35 | ...[...] : | semmle.label | ...[...] : |
| ActiveRecordInjection.rb:56:38:56:43 | call to params : | semmle.label | call to params : |
| ActiveRecordInjection.rb:56:38:56:50 | ...[...] : | semmle.label | ...[...] : |
| ActiveRecordInjection.rb:62:10:62:15 | call to params : | semmle.label | call to params : |
| ActiveRecordInjection.rb:68:21:68:33 | ... + ... | semmle.label | ... + ... |
| ActiveRecordInjection.rb:101:22:101:27 | call to params : | semmle.label | call to params : |
| ActiveRecordInjection.rb:101:22:101:45 | ...[...] : | semmle.label | ...[...] : |
| ActiveRecordInjection.rb:39:18:39:23 | call to params : | semmle.label | call to params : |
| ActiveRecordInjection.rb:39:18:39:32 | ...[...] | semmle.label | ...[...] |
| ActiveRecordInjection.rb:43:20:43:42 | "id = '#{...}'" | semmle.label | "id = '#{...}'" |
| ActiveRecordInjection.rb:43:29:43:34 | call to params : | semmle.label | call to params : |
| ActiveRecordInjection.rb:48:21:48:43 | "id = '#{...}'" | semmle.label | "id = '#{...}'" |
| ActiveRecordInjection.rb:48:30:48:35 | call to params : | semmle.label | call to params : |
| ActiveRecordInjection.rb:52:22:52:44 | "id = '#{...}'" | semmle.label | "id = '#{...}'" |
| ActiveRecordInjection.rb:52:31:52:36 | call to params : | semmle.label | call to params : |
| ActiveRecordInjection.rb:57:23:57:45 | "id = '#{...}'" | semmle.label | "id = '#{...}'" |
| ActiveRecordInjection.rb:57:32:57:37 | call to params : | semmle.label | call to params : |
| ActiveRecordInjection.rb:61:16:61:21 | <<-SQL | semmle.label | <<-SQL |
| ActiveRecordInjection.rb:62:21:62:26 | call to params : | semmle.label | call to params : |
| ActiveRecordInjection.rb:68:20:68:47 | "user.id = '#{...}'" | semmle.label | "user.id = '#{...}'" |
| ActiveRecordInjection.rb:68:34:68:39 | call to params : | semmle.label | call to params : |
| ActiveRecordInjection.rb:70:23:70:28 | call to params : | semmle.label | call to params : |
| ActiveRecordInjection.rb:70:23:70:35 | ...[...] : | semmle.label | ...[...] : |
| ActiveRecordInjection.rb:70:38:70:43 | call to params : | semmle.label | call to params : |
| ActiveRecordInjection.rb:70:38:70:50 | ...[...] : | semmle.label | ...[...] : |
| ActiveRecordInjection.rb:74:32:74:54 | "id = '#{...}'" | semmle.label | "id = '#{...}'" |
| ActiveRecordInjection.rb:74:41:74:46 | call to params : | semmle.label | call to params : |
| ActiveRecordInjection.rb:83:17:83:22 | call to params : | semmle.label | call to params : |
| ActiveRecordInjection.rb:83:17:83:31 | ...[...] | semmle.label | ...[...] |
| ActiveRecordInjection.rb:84:19:84:24 | call to params : | semmle.label | call to params : |
| ActiveRecordInjection.rb:84:19:84:33 | ...[...] | semmle.label | ...[...] |
| ActiveRecordInjection.rb:88:18:88:23 | call to params : | semmle.label | call to params : |
| ActiveRecordInjection.rb:88:18:88:35 | ...[...] | semmle.label | ...[...] |
| ActiveRecordInjection.rb:92:21:92:26 | call to params : | semmle.label | call to params : |
| ActiveRecordInjection.rb:92:21:92:35 | ...[...] | semmle.label | ...[...] |
| ActiveRecordInjection.rb:98:10:98:15 | call to params : | semmle.label | call to params : |
| ActiveRecordInjection.rb:104:20:104:32 | ... + ... | semmle.label | ... + ... |
| ActiveRecordInjection.rb:137:21:137:26 | call to params : | semmle.label | call to params : |
| ActiveRecordInjection.rb:137:21:137:44 | ...[...] : | semmle.label | ...[...] : |
subpaths
#select
| ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | ActiveRecordInjection.rb:56:23:56:28 | call to params : | ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:56:23:56:28 | call to params | a user-provided value |
| ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | ActiveRecordInjection.rb:56:38:56:43 | call to params : | ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:56:38:56:43 | call to params | a user-provided value |
| ActiveRecordInjection.rb:23:17:23:25 | condition | ActiveRecordInjection.rb:101:22:101:27 | call to params : | ActiveRecordInjection.rb:23:17:23:25 | condition | This SQL query depends on $@. | ActiveRecordInjection.rb:101:22:101:27 | call to params | a user-provided value |
| ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | ActiveRecordInjection.rb:70:23:70:28 | call to params : | ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:70:23:70:28 | call to params | a user-provided value |
| ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | ActiveRecordInjection.rb:70:38:70:43 | call to params : | ActiveRecordInjection.rb:10:33:10:67 | "name='#{...}' and pass='#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:70:38:70:43 | call to params | a user-provided value |
| ActiveRecordInjection.rb:23:16:23:24 | condition | ActiveRecordInjection.rb:137:21:137:26 | call to params : | ActiveRecordInjection.rb:23:16:23:24 | condition | This SQL query depends on $@. | ActiveRecordInjection.rb:137:21:137:26 | call to params | a user-provided value |
| ActiveRecordInjection.rb:35:30:35:44 | ...[...] | ActiveRecordInjection.rb:35:30:35:35 | call to params : | ActiveRecordInjection.rb:35:30:35:44 | ...[...] | This SQL query depends on $@. | ActiveRecordInjection.rb:35:30:35:35 | call to params | a user-provided value |
| ActiveRecordInjection.rb:39:21:39:43 | "id = '#{...}'" | ActiveRecordInjection.rb:39:30:39:35 | call to params : | ActiveRecordInjection.rb:39:21:39:43 | "id = '#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:39:30:39:35 | call to params | a user-provided value |
| ActiveRecordInjection.rb:43:23:43:45 | "id = '#{...}'" | ActiveRecordInjection.rb:43:32:43:37 | call to params : | ActiveRecordInjection.rb:43:23:43:45 | "id = '#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:43:32:43:37 | call to params | a user-provided value |
| ActiveRecordInjection.rb:47:16:47:21 | <<-SQL | ActiveRecordInjection.rb:48:21:48:26 | call to params : | ActiveRecordInjection.rb:47:16:47:21 | <<-SQL | This SQL query depends on $@. | ActiveRecordInjection.rb:48:21:48:26 | call to params | a user-provided value |
| ActiveRecordInjection.rb:54:20:54:47 | "user.id = '#{...}'" | ActiveRecordInjection.rb:54:34:54:39 | call to params : | ActiveRecordInjection.rb:54:20:54:47 | "user.id = '#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:54:34:54:39 | call to params | a user-provided value |
| ActiveRecordInjection.rb:68:21:68:33 | ... + ... | ActiveRecordInjection.rb:62:10:62:15 | call to params : | ActiveRecordInjection.rb:68:21:68:33 | ... + ... | This SQL query depends on $@. | ActiveRecordInjection.rb:62:10:62:15 | call to params | a user-provided value |
| ActiveRecordInjection.rb:39:18:39:32 | ...[...] | ActiveRecordInjection.rb:39:18:39:23 | call to params : | ActiveRecordInjection.rb:39:18:39:32 | ...[...] | This SQL query depends on $@. | ActiveRecordInjection.rb:39:18:39:23 | call to params | a user-provided value |
| ActiveRecordInjection.rb:43:20:43:42 | "id = '#{...}'" | ActiveRecordInjection.rb:43:29:43:34 | call to params : | ActiveRecordInjection.rb:43:20:43:42 | "id = '#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:43:29:43:34 | call to params | a user-provided value |
| ActiveRecordInjection.rb:48:21:48:43 | "id = '#{...}'" | ActiveRecordInjection.rb:48:30:48:35 | call to params : | ActiveRecordInjection.rb:48:21:48:43 | "id = '#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:48:30:48:35 | call to params | a user-provided value |
| ActiveRecordInjection.rb:52:22:52:44 | "id = '#{...}'" | ActiveRecordInjection.rb:52:31:52:36 | call to params : | ActiveRecordInjection.rb:52:22:52:44 | "id = '#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:52:31:52:36 | call to params | a user-provided value |
| ActiveRecordInjection.rb:57:23:57:45 | "id = '#{...}'" | ActiveRecordInjection.rb:57:32:57:37 | call to params : | ActiveRecordInjection.rb:57:23:57:45 | "id = '#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:57:32:57:37 | call to params | a user-provided value |
| ActiveRecordInjection.rb:61:16:61:21 | <<-SQL | ActiveRecordInjection.rb:62:21:62:26 | call to params : | ActiveRecordInjection.rb:61:16:61:21 | <<-SQL | This SQL query depends on $@. | ActiveRecordInjection.rb:62:21:62:26 | call to params | a user-provided value |
| ActiveRecordInjection.rb:68:20:68:47 | "user.id = '#{...}'" | ActiveRecordInjection.rb:68:34:68:39 | call to params : | ActiveRecordInjection.rb:68:20:68:47 | "user.id = '#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:68:34:68:39 | call to params | a user-provided value |
| ActiveRecordInjection.rb:74:32:74:54 | "id = '#{...}'" | ActiveRecordInjection.rb:74:41:74:46 | call to params : | ActiveRecordInjection.rb:74:32:74:54 | "id = '#{...}'" | This SQL query depends on $@. | ActiveRecordInjection.rb:74:41:74:46 | call to params | a user-provided value |
| ActiveRecordInjection.rb:83:17:83:31 | ...[...] | ActiveRecordInjection.rb:83:17:83:22 | call to params : | ActiveRecordInjection.rb:83:17:83:31 | ...[...] | This SQL query depends on $@. | ActiveRecordInjection.rb:83:17:83:22 | call to params | a user-provided value |
| ActiveRecordInjection.rb:84:19:84:33 | ...[...] | ActiveRecordInjection.rb:84:19:84:24 | call to params : | ActiveRecordInjection.rb:84:19:84:33 | ...[...] | This SQL query depends on $@. | ActiveRecordInjection.rb:84:19:84:24 | call to params | a user-provided value |
| ActiveRecordInjection.rb:88:18:88:35 | ...[...] | ActiveRecordInjection.rb:88:18:88:23 | call to params : | ActiveRecordInjection.rb:88:18:88:35 | ...[...] | This SQL query depends on $@. | ActiveRecordInjection.rb:88:18:88:23 | call to params | a user-provided value |
| ActiveRecordInjection.rb:92:21:92:35 | ...[...] | ActiveRecordInjection.rb:92:21:92:26 | call to params : | ActiveRecordInjection.rb:92:21:92:35 | ...[...] | This SQL query depends on $@. | ActiveRecordInjection.rb:92:21:92:26 | call to params | a user-provided value |
| ActiveRecordInjection.rb:104:20:104:32 | ... + ... | ActiveRecordInjection.rb:98:10:98:15 | call to params : | ActiveRecordInjection.rb:104:20:104:32 | ... + ... | This SQL query depends on $@. | ActiveRecordInjection.rb:98:10:98:15 | call to params | a user-provided value |

View File

@@ -1,10 +1,16 @@
edges
| CodeInjection.rb:3:12:3:17 | call to params : | CodeInjection.rb:6:10:6:13 | code |
| CodeInjection.rb:3:12:3:17 | call to params : | CodeInjection.rb:18:20:18:23 | code |
| CodeInjection.rb:3:12:3:17 | call to params : | CodeInjection.rb:21:21:21:24 | code |
nodes
| CodeInjection.rb:3:12:3:17 | call to params : | semmle.label | call to params : |
| CodeInjection.rb:6:10:6:13 | code | semmle.label | code |
| CodeInjection.rb:9:10:9:15 | call to params | semmle.label | call to params |
| CodeInjection.rb:18:20:18:23 | code | semmle.label | code |
| CodeInjection.rb:21:21:21:24 | code | semmle.label | code |
subpaths
#select
| CodeInjection.rb:6:10:6:13 | code | CodeInjection.rb:3:12:3:17 | call to params : | CodeInjection.rb:6:10:6:13 | code | This code execution depends on $@. | CodeInjection.rb:3:12:3:17 | call to params | a user-provided value |
| CodeInjection.rb:9:10:9:15 | call to params | CodeInjection.rb:9:10:9:15 | call to params | CodeInjection.rb:9:10:9:15 | call to params | This code execution depends on $@. | CodeInjection.rb:9:10:9:15 | call to params | a user-provided value |
| CodeInjection.rb:18:20:18:23 | code | CodeInjection.rb:3:12:3:17 | call to params : | CodeInjection.rb:18:20:18:23 | code | This code execution depends on $@. | CodeInjection.rb:3:12:3:17 | call to params | a user-provided value |
| CodeInjection.rb:21:21:21:24 | code | CodeInjection.rb:3:12:3:17 | call to params : | CodeInjection.rb:21:21:21:24 | code | This code execution depends on $@. | CodeInjection.rb:3:12:3:17 | call to params | a user-provided value |

View File

@@ -8,14 +8,32 @@ class UsersController < ActionController::Base
# BAD
eval(params)
# GOOD - user input is in second argument, which is not evaluated as Ruby code
send(:sanitize, params[:code])
# GOOD
Foo.new.bar(code)
# BAD
Foo.class_eval(code)
# BAD
Foo.module_eval(code)
# GOOD
Bar.class_eval(code)
end
def update
# GOOD
eval("foo")
end
private
def sanitize(code)
true
end
end
class Foo
@@ -27,3 +45,9 @@ class Foo
eval(x)
end
end
class Bar
def self.class_eval(x)
true
end
end

View File

@@ -3,9 +3,7 @@ edges
| UrlRedirect.rb:14:17:14:22 | call to params : | UrlRedirect.rb:14:17:14:43 | call to fetch |
| UrlRedirect.rb:19:17:19:22 | call to params : | UrlRedirect.rb:19:17:19:37 | call to to_unsafe_hash |
| UrlRedirect.rb:24:31:24:36 | call to params : | UrlRedirect.rb:24:17:24:37 | call to filter_params |
| UrlRedirect.rb:24:31:24:36 | call to params : | UrlRedirect.rb:56:21:56:32 | input_params : |
| UrlRedirect.rb:34:20:34:25 | call to params : | UrlRedirect.rb:34:17:34:37 | "#{...}/foo" |
| UrlRedirect.rb:56:21:56:32 | input_params : | UrlRedirect.rb:57:5:57:29 | call to permit : |
nodes
| UrlRedirect.rb:4:17:4:22 | call to params | semmle.label | call to params |
| UrlRedirect.rb:9:17:9:22 | call to params : | semmle.label | call to params : |
@@ -18,10 +16,7 @@ nodes
| UrlRedirect.rb:24:31:24:36 | call to params : | semmle.label | call to params : |
| UrlRedirect.rb:34:17:34:37 | "#{...}/foo" | semmle.label | "#{...}/foo" |
| UrlRedirect.rb:34:20:34:25 | call to params : | semmle.label | call to params : |
| UrlRedirect.rb:56:21:56:32 | input_params : | semmle.label | input_params : |
| UrlRedirect.rb:57:5:57:29 | call to permit : | semmle.label | call to permit : |
subpaths
| UrlRedirect.rb:24:31:24:36 | call to params : | UrlRedirect.rb:56:21:56:32 | input_params : | UrlRedirect.rb:57:5:57:29 | call to permit : | UrlRedirect.rb:24:17:24:37 | call to filter_params : |
#select
| UrlRedirect.rb:4:17:4:22 | call to params | UrlRedirect.rb:4:17:4:22 | call to params | UrlRedirect.rb:4:17:4:22 | call to params | Untrusted URL redirection due to $@. | UrlRedirect.rb:4:17:4:22 | call to params | a user-provided value |
| UrlRedirect.rb:9:17:9:28 | ...[...] | UrlRedirect.rb:9:17:9:22 | call to params : | UrlRedirect.rb:9:17:9:28 | ...[...] | Untrusted URL redirection due to $@. | UrlRedirect.rb:9:17:9:22 | call to params | a user-provided value |

View File

@@ -0,0 +1,16 @@
class LibXmlRubyXXE < ApplicationController
content = params[:xml]
LibXML::XML::Document.string(content, { options: 2 | 2048, encoding: 'utf-8' })
LibXML::XML::Document.file(content, { options: LibXML::XML::Parser::Options::NOENT | 2048})
LibXML::XML::Document.io(content, { options: XML::Parser::Options::NOENT | 2048 })
LibXML::XML::Parser.string(content, { options: 2 | 2048 })
LibXML::XML::Parser.file(content, { options: 3 | 2048 })
LibXML::XML::Parser.io(content, { options: 2 | 2048})
XML::Document.string(content, { options: 2 | 2048 })
XML::Parser.string(content, { options: 2 | 2048 })
LibXML::XML::Parser.file(content, { options: 2048 }) # OK
end

View File

@@ -0,0 +1,30 @@
class NokogiriXXE < ApplicationController
content = params[:xml]
Nokogiri::XML::parse(content, nil, nil, 2)
Nokogiri::XML::parse(content, nil, nil, 1 | 2)
Nokogiri::XML::parse(content, nil, nil, 1 & ~Nokogiri::XML::ParseOptions::NONET)
Nokogiri::XML::parse(content, nil, nil, Nokogiri::XML::ParseOptions::NOENT)
Nokogiri::XML::parse(content, nil, nil, Nokogiri::XML::ParseOptions::DTDLOAD)
Nokogiri::XML::parse(content, nil, nil, ~Nokogiri::XML::ParseOptions::NOENT) #OK
Nokogiri::XML::parse(content, nil, nil, ~Nokogiri::XML::ParseOptions::NONET)
Nokogiri::XML::parse(content, nil, nil, Nokogiri::XML::ParseOptions.new 2)
options = Nokogiri::XML::ParseOptions.new 2048
options.noent
Nokogiri::XML::parse(content, nil, nil, options)
Nokogiri::XML::parse(content, nil, nil, (Nokogiri::XML::ParseOptions.new 0).noent)
Nokogiri::XML::parse(content) { |x| x.noent }
Nokogiri::XML::parse(content) { |x| x.nononet } #FAIL
Nokogiri::XML::parse(content) { |x| x.nodtdload } # OK
Nokogiri::XML::parse(content) { |x| x.nonet.noent.nodtdload }
Nokogiri::XML::parse(content, nil, nil, 2048) # OK
Nokogiri::XML::parse(content, nil, nil, 3)
Nokogiri::XML::parse(content) { |x| x.nonet.nodtdload } # OK
Nokogiri::XML::parse(content, nil, nil, Nokogiri::XML::ParseOptions::NOENT & ~Nokogiri::XML::ParseOptions::NOBLANKS)
Nokogiri::XML::parse(content, nil, nil, ~Nokogiri::XML::ParseOptions::NONET | Nokogiri::XML::ParseOptions::NOBLANKS)
end

View File

@@ -0,0 +1,75 @@
edges
| LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:4:34:4:40 | content |
| LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:5:32:5:38 | content |
| LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:6:30:6:36 | content |
| LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:7:32:7:38 | content |
| LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:8:30:8:36 | content |
| LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:9:28:9:34 | content |
| LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:11:26:11:32 | content |
| LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:12:24:12:30 | content |
| Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:5:26:5:32 | content |
| Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:6:26:6:32 | content |
| Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:7:26:7:32 | content |
| Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:8:26:8:32 | content |
| Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:9:26:9:32 | content |
| Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:11:26:11:32 | content |
| Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:12:26:12:32 | content |
| Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:15:26:15:32 | content |
| Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:16:26:16:32 | content |
| Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:18:26:18:32 | content |
| Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:19:26:19:32 | content |
| Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:22:26:22:32 | content |
| Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:25:26:25:32 | content |
| Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:27:26:27:32 | content |
| Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:28:26:28:32 | content |
nodes
| LibXmlRuby.rb:3:15:3:20 | call to params : | semmle.label | call to params : |
| LibXmlRuby.rb:4:34:4:40 | content | semmle.label | content |
| LibXmlRuby.rb:5:32:5:38 | content | semmle.label | content |
| LibXmlRuby.rb:6:30:6:36 | content | semmle.label | content |
| LibXmlRuby.rb:7:32:7:38 | content | semmle.label | content |
| LibXmlRuby.rb:8:30:8:36 | content | semmle.label | content |
| LibXmlRuby.rb:9:28:9:34 | content | semmle.label | content |
| LibXmlRuby.rb:11:26:11:32 | content | semmle.label | content |
| LibXmlRuby.rb:12:24:12:30 | content | semmle.label | content |
| Nokogiri.rb:3:15:3:20 | call to params : | semmle.label | call to params : |
| Nokogiri.rb:5:26:5:32 | content | semmle.label | content |
| Nokogiri.rb:6:26:6:32 | content | semmle.label | content |
| Nokogiri.rb:7:26:7:32 | content | semmle.label | content |
| Nokogiri.rb:8:26:8:32 | content | semmle.label | content |
| Nokogiri.rb:9:26:9:32 | content | semmle.label | content |
| Nokogiri.rb:11:26:11:32 | content | semmle.label | content |
| Nokogiri.rb:12:26:12:32 | content | semmle.label | content |
| Nokogiri.rb:15:26:15:32 | content | semmle.label | content |
| Nokogiri.rb:16:26:16:32 | content | semmle.label | content |
| Nokogiri.rb:18:26:18:32 | content | semmle.label | content |
| Nokogiri.rb:19:26:19:32 | content | semmle.label | content |
| Nokogiri.rb:22:26:22:32 | content | semmle.label | content |
| Nokogiri.rb:25:26:25:32 | content | semmle.label | content |
| Nokogiri.rb:27:26:27:32 | content | semmle.label | content |
| Nokogiri.rb:28:26:28:32 | content | semmle.label | content |
subpaths
#select
| LibXmlRuby.rb:4:34:4:40 | content | LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:4:34:4:40 | content | Unsafe parsing of XML file from $@. | LibXmlRuby.rb:3:15:3:20 | call to params | user input |
| LibXmlRuby.rb:5:32:5:38 | content | LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:5:32:5:38 | content | Unsafe parsing of XML file from $@. | LibXmlRuby.rb:3:15:3:20 | call to params | user input |
| LibXmlRuby.rb:6:30:6:36 | content | LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:6:30:6:36 | content | Unsafe parsing of XML file from $@. | LibXmlRuby.rb:3:15:3:20 | call to params | user input |
| LibXmlRuby.rb:7:32:7:38 | content | LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:7:32:7:38 | content | Unsafe parsing of XML file from $@. | LibXmlRuby.rb:3:15:3:20 | call to params | user input |
| LibXmlRuby.rb:8:30:8:36 | content | LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:8:30:8:36 | content | Unsafe parsing of XML file from $@. | LibXmlRuby.rb:3:15:3:20 | call to params | user input |
| LibXmlRuby.rb:9:28:9:34 | content | LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:9:28:9:34 | content | Unsafe parsing of XML file from $@. | LibXmlRuby.rb:3:15:3:20 | call to params | user input |
| LibXmlRuby.rb:11:26:11:32 | content | LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:11:26:11:32 | content | Unsafe parsing of XML file from $@. | LibXmlRuby.rb:3:15:3:20 | call to params | user input |
| LibXmlRuby.rb:12:24:12:30 | content | LibXmlRuby.rb:3:15:3:20 | call to params : | LibXmlRuby.rb:12:24:12:30 | content | Unsafe parsing of XML file from $@. | LibXmlRuby.rb:3:15:3:20 | call to params | user input |
| Nokogiri.rb:5:26:5:32 | content | Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:5:26:5:32 | content | Unsafe parsing of XML file from $@. | Nokogiri.rb:3:15:3:20 | call to params | user input |
| Nokogiri.rb:6:26:6:32 | content | Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:6:26:6:32 | content | Unsafe parsing of XML file from $@. | Nokogiri.rb:3:15:3:20 | call to params | user input |
| Nokogiri.rb:7:26:7:32 | content | Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:7:26:7:32 | content | Unsafe parsing of XML file from $@. | Nokogiri.rb:3:15:3:20 | call to params | user input |
| Nokogiri.rb:8:26:8:32 | content | Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:8:26:8:32 | content | Unsafe parsing of XML file from $@. | Nokogiri.rb:3:15:3:20 | call to params | user input |
| Nokogiri.rb:9:26:9:32 | content | Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:9:26:9:32 | content | Unsafe parsing of XML file from $@. | Nokogiri.rb:3:15:3:20 | call to params | user input |
| Nokogiri.rb:11:26:11:32 | content | Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:11:26:11:32 | content | Unsafe parsing of XML file from $@. | Nokogiri.rb:3:15:3:20 | call to params | user input |
| Nokogiri.rb:12:26:12:32 | content | Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:12:26:12:32 | content | Unsafe parsing of XML file from $@. | Nokogiri.rb:3:15:3:20 | call to params | user input |
| Nokogiri.rb:15:26:15:32 | content | Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:15:26:15:32 | content | Unsafe parsing of XML file from $@. | Nokogiri.rb:3:15:3:20 | call to params | user input |
| Nokogiri.rb:16:26:16:32 | content | Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:16:26:16:32 | content | Unsafe parsing of XML file from $@. | Nokogiri.rb:3:15:3:20 | call to params | user input |
| Nokogiri.rb:18:26:18:32 | content | Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:18:26:18:32 | content | Unsafe parsing of XML file from $@. | Nokogiri.rb:3:15:3:20 | call to params | user input |
| Nokogiri.rb:19:26:19:32 | content | Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:19:26:19:32 | content | Unsafe parsing of XML file from $@. | Nokogiri.rb:3:15:3:20 | call to params | user input |
| Nokogiri.rb:22:26:22:32 | content | Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:22:26:22:32 | content | Unsafe parsing of XML file from $@. | Nokogiri.rb:3:15:3:20 | call to params | user input |
| Nokogiri.rb:25:26:25:32 | content | Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:25:26:25:32 | content | Unsafe parsing of XML file from $@. | Nokogiri.rb:3:15:3:20 | call to params | user input |
| Nokogiri.rb:27:26:27:32 | content | Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:27:26:27:32 | content | Unsafe parsing of XML file from $@. | Nokogiri.rb:3:15:3:20 | call to params | user input |
| Nokogiri.rb:28:26:28:32 | content | Nokogiri.rb:3:15:3:20 | call to params : | Nokogiri.rb:28:26:28:32 | content | Unsafe parsing of XML file from $@. | Nokogiri.rb:3:15:3:20 | call to params | user input |

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