Merge branch 'main' into refacReDoS

This commit is contained in:
Erik Krogh Kristensen
2022-08-09 16:18:46 +02:00
1452 changed files with 75078 additions and 71169 deletions

BIN
ruby/Cargo.lock generated

Binary file not shown.

View File

@@ -19,6 +19,7 @@ fn main() -> std::io::Result<()> {
.arg("--include-extension=.erb")
.arg("--include-extension=.gemspec")
.arg("--include=**/Gemfile")
.arg("--exclude=**/.git")
.arg("--size-limit=5m")
.arg("--language=ruby")
.arg("--working-dir=.")
@@ -29,9 +30,9 @@ fn main() -> std::io::Result<()> {
.split('\n')
{
if let Some(stripped) = line.strip_prefix("include:") {
cmd.arg("--include").arg(stripped);
cmd.arg("--also-match=".to_owned() + stripped);
} else if let Some(stripped) = line.strip_prefix("exclude:") {
cmd.arg("--exclude").arg(stripped);
cmd.arg("--exclude=".to_owned() + stripped);
}
}
let exit = &cmd.spawn()?.wait()?;

View File

@@ -11,10 +11,12 @@ flate2 = "1.0"
node-types = { path = "../node-types" }
tree-sitter = "0.19"
tree-sitter-embedded-template = { git = "https://github.com/tree-sitter/tree-sitter-embedded-template.git", rev = "1a538da253d73f896b9f6c0c7d79cda58791ac5c" }
tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "6334d6ab3d04a5672da695d3b155ca3301511f8d" }
tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "e75d04404c9dd71ad68850d5c672b226d5e694f3" }
clap = "3.0"
tracing = "0.1"
tracing-subscriber = { version = "0.3.3", features = ["env-filter"] }
rayon = "1.5.0"
num_cpus = "1.13.0"
regex = "1.5.5"
encoding = "0.2"
lazy_static = "1.4.0"

View File

@@ -1,161 +1,112 @@
use crate::trap;
use node_types::{EntryKind, Field, NodeTypeMap, Storage, TypeName};
use std::borrow::Cow;
use std::collections::BTreeMap as Map;
use std::collections::BTreeSet as Set;
use std::fmt;
use std::io::Write;
use std::path::Path;
use tracing::{error, info, span, Level};
use tree_sitter::{Language, Node, Parser, Range, Tree};
pub struct TrapWriter {
/// The accumulated trap entries
trap_output: Vec<TrapEntry>,
/// A counter for generating fresh labels
counter: u32,
/// cache of global keys
global_keys: std::collections::HashMap<String, Label>,
pub fn populate_file(writer: &mut trap::Writer, absolute_path: &Path) -> trap::Label {
let (file_label, fresh) =
writer.global_id(&trap::full_id_for_file(&normalize_path(absolute_path)));
if fresh {
writer.add_tuple(
"files",
vec![
trap::Arg::Label(file_label),
trap::Arg::String(normalize_path(absolute_path)),
],
);
populate_parent_folders(writer, file_label, absolute_path.parent());
}
file_label
}
pub fn new_trap_writer() -> TrapWriter {
TrapWriter {
counter: 0,
trap_output: Vec::new(),
global_keys: std::collections::HashMap::new(),
fn populate_empty_file(writer: &mut trap::Writer) -> trap::Label {
let (file_label, fresh) = writer.global_id("empty;sourcefile");
if fresh {
writer.add_tuple(
"files",
vec![
trap::Arg::Label(file_label),
trap::Arg::String("".to_string()),
],
);
}
file_label
}
impl TrapWriter {
/// Gets a label that will hold the unique ID of the passed string at import time.
/// This can be used for incrementally importable TRAP files -- use globally unique
/// strings to compute a unique ID for table tuples.
///
/// Note: You probably want to make sure that the key strings that you use are disjoint
/// for disjoint column types; the standard way of doing this is to prefix (or append)
/// the column type name to the ID. Thus, you might identify methods in Java by the
/// full ID "methods_com.method.package.DeclaringClass.method(argumentList)".
pub fn populate_empty_location(writer: &mut trap::Writer) {
let file_label = populate_empty_file(writer);
location(writer, file_label, 0, 0, 0, 0);
}
fn fresh_id(&mut self) -> Label {
let label = Label(self.counter);
self.counter += 1;
self.trap_output.push(TrapEntry::FreshId(label));
label
}
fn global_id(&mut self, key: &str) -> (Label, bool) {
if let Some(label) = self.global_keys.get(key) {
return (*label, false);
}
let label = Label(self.counter);
self.counter += 1;
self.global_keys.insert(key.to_owned(), label);
self.trap_output
.push(TrapEntry::MapLabelToKey(label, key.to_owned()));
(label, true)
}
fn add_tuple(&mut self, table_name: &str, args: Vec<Arg>) {
self.trap_output
.push(TrapEntry::GenericTuple(table_name.to_owned(), args))
}
fn populate_file(&mut self, absolute_path: &Path) -> Label {
let (file_label, fresh) = self.global_id(&full_id_for_file(absolute_path));
if fresh {
self.add_tuple(
"files",
vec![
Arg::Label(file_label),
Arg::String(normalize_path(absolute_path)),
],
);
self.populate_parent_folders(file_label, absolute_path.parent());
}
file_label
}
fn populate_empty_file(&mut self) -> Label {
let (file_label, fresh) = self.global_id("empty;sourcefile");
if fresh {
self.add_tuple(
"files",
vec![Arg::Label(file_label), Arg::String("".to_string())],
);
}
file_label
}
pub fn populate_empty_location(&mut self) {
let file_label = self.populate_empty_file();
self.location(file_label, 0, 0, 0, 0);
}
fn populate_parent_folders(&mut self, child_label: Label, path: Option<&Path>) {
let mut path = path;
let mut child_label = child_label;
loop {
match path {
None => break,
Some(folder) => {
let (folder_label, fresh) = self.global_id(&full_id_for_folder(folder));
self.add_tuple(
"containerparent",
vec![Arg::Label(folder_label), Arg::Label(child_label)],
pub fn populate_parent_folders(
writer: &mut trap::Writer,
child_label: trap::Label,
path: Option<&Path>,
) {
let mut path = path;
let mut child_label = child_label;
loop {
match path {
None => break,
Some(folder) => {
let (folder_label, fresh) =
writer.global_id(&trap::full_id_for_folder(&normalize_path(folder)));
writer.add_tuple(
"containerparent",
vec![
trap::Arg::Label(folder_label),
trap::Arg::Label(child_label),
],
);
if fresh {
writer.add_tuple(
"folders",
vec![
trap::Arg::Label(folder_label),
trap::Arg::String(normalize_path(folder)),
],
);
if fresh {
self.add_tuple(
"folders",
vec![
Arg::Label(folder_label),
Arg::String(normalize_path(folder)),
],
);
path = folder.parent();
child_label = folder_label;
} else {
break;
}
path = folder.parent();
child_label = folder_label;
} else {
break;
}
}
}
}
}
fn location(
&mut self,
file_label: Label,
start_line: usize,
start_column: usize,
end_line: usize,
end_column: usize,
) -> Label {
let (loc_label, fresh) = self.global_id(&format!(
"loc,{{{}}},{},{},{},{}",
file_label, start_line, start_column, end_line, end_column
));
if fresh {
self.add_tuple(
"locations_default",
vec![
Arg::Label(loc_label),
Arg::Label(file_label),
Arg::Int(start_line),
Arg::Int(start_column),
Arg::Int(end_line),
Arg::Int(end_column),
],
);
}
loc_label
}
fn comment(&mut self, text: String) {
self.trap_output.push(TrapEntry::Comment(text));
}
pub fn output(self, writer: &mut dyn Write) -> std::io::Result<()> {
write!(writer, "{}", Program(self.trap_output))
fn location(
writer: &mut trap::Writer,
file_label: trap::Label,
start_line: usize,
start_column: usize,
end_line: usize,
end_column: usize,
) -> trap::Label {
let (loc_label, fresh) = writer.global_id(&format!(
"loc,{{{}}},{},{},{},{}",
file_label, start_line, start_column, end_line, end_column
));
if fresh {
writer.add_tuple(
"locations_default",
vec![
trap::Arg::Label(loc_label),
trap::Arg::Label(file_label),
trap::Arg::Int(start_line),
trap::Arg::Int(start_column),
trap::Arg::Int(end_line),
trap::Arg::Int(end_column),
],
);
}
loc_label
}
/// Extracts the source file at `path`, which is assumed to be canonicalized.
@@ -163,71 +114,43 @@ pub fn extract(
language: Language,
language_prefix: &str,
schema: &NodeTypeMap,
trap_writer: &mut TrapWriter,
trap_writer: &mut trap::Writer,
path: &Path,
source: &[u8],
ranges: &[Range],
) -> std::io::Result<()> {
let path_str = format!("{}", path.display());
let span = span!(
Level::TRACE,
"extract",
file = %path.display()
file = %path_str
);
let _enter = span.enter();
info!("extracting: {}", path.display());
info!("extracting: {}", path_str);
let mut parser = Parser::new();
parser.set_language(language).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 {
trap_writer.comment(format!("Auto-generated TRAP file for {}", path_str));
let file_label = populate_file(trap_writer, path);
let mut visitor = Visitor::new(
source,
trap_writer,
// TODO: should we handle path strings that are not valid UTF8 better?
path: format!("{}", path.display()),
file_label: *file_label,
toplevel_child_counter: 0,
stack: Vec::new(),
&path_str,
file_label,
language_prefix,
schema,
};
);
traverse(&tree, &mut visitor);
parser.reset();
Ok(())
}
/// Escapes a string for use in a TRAP key, by replacing special characters with
/// HTML entities.
fn escape_key<'a, S: Into<Cow<'a, str>>>(key: S) -> Cow<'a, str> {
fn needs_escaping(c: char) -> bool {
matches!(c, '&' | '{' | '}' | '"' | '@' | '#')
}
let key = key.into();
if key.contains(needs_escaping) {
let mut escaped = String::with_capacity(2 * key.len());
for c in key.chars() {
match c {
'&' => escaped.push_str("&amp;"),
'{' => escaped.push_str("&lbrace;"),
'}' => escaped.push_str("&rbrace;"),
'"' => escaped.push_str("&quot;"),
'@' => escaped.push_str("&commat;"),
'#' => escaped.push_str("&num;"),
_ => escaped.push(c),
}
}
Cow::Owned(escaped)
} else {
key
}
}
/// Normalizes the path according the common CodeQL specification. Assumes that
/// `path` has already been canonicalized using `std::fs::canonicalize`.
fn normalize_path(path: &Path) -> String {
@@ -267,34 +190,28 @@ fn normalize_path(path: &Path) -> String {
}
}
fn full_id_for_file(path: &Path) -> String {
format!("{};sourcefile", escape_key(&normalize_path(path)))
}
fn full_id_for_folder(path: &Path) -> String {
format!("{};folder", escape_key(&normalize_path(path)))
}
struct ChildNode {
field_name: Option<&'static str>,
label: Label,
label: trap::Label,
type_name: TypeName,
}
struct Visitor<'a> {
/// The file path of the source code (as string)
path: String,
path: &'a str,
/// The label to use whenever we need to refer to the `@file` entity of this
/// source file.
file_label: Label,
file_label: trap::Label,
/// The source code as a UTF-8 byte array
source: &'a [u8],
/// A TrapWriter to accumulate trap entries
trap_writer: &'a mut TrapWriter,
/// A trap::Writer to accumulate trap entries
trap_writer: &'a mut trap::Writer,
/// A counter for top-level child nodes
toplevel_child_counter: usize,
/// Language prefix
language_prefix: &'a str,
/// Language-specific name of the AST info table
ast_node_info_table_name: String,
/// Language-specific name of the tokeninfo table
tokeninfo_table_name: String,
/// A lookup table from type name to node types
schema: &'a NodeTypeMap,
/// A stack for gathering information from child nodes. Whenever a node is
@@ -303,27 +220,48 @@ struct Visitor<'a> {
/// node the list containing the child data is popped from the stack and
/// matched against the dbscheme for the node. If the expectations are met
/// the corresponding row definitions are added to the trap_output.
stack: Vec<(Label, usize, Vec<ChildNode>)>,
stack: Vec<(trap::Label, usize, Vec<ChildNode>)>,
}
impl Visitor<'_> {
impl<'a> Visitor<'a> {
fn new(
source: &'a [u8],
trap_writer: &'a mut trap::Writer,
path: &'a str,
file_label: trap::Label,
language_prefix: &str,
schema: &'a NodeTypeMap,
) -> Visitor<'a> {
Visitor {
path,
file_label,
source,
trap_writer,
toplevel_child_counter: 0,
ast_node_info_table_name: format!("{}_ast_node_info", language_prefix),
tokeninfo_table_name: format!("{}_tokeninfo", language_prefix),
schema,
stack: Vec::new(),
}
}
fn record_parse_error(
&mut self,
error_message: String,
full_error_message: String,
loc: Label,
loc: trap::Label,
) {
error!("{}", full_error_message);
let id = self.trap_writer.fresh_id();
self.trap_writer.add_tuple(
"diagnostics",
vec![
Arg::Label(id),
Arg::Int(40), // severity 40 = error
Arg::String("parse_error".to_string()),
Arg::String(error_message),
Arg::String(full_error_message),
Arg::Label(loc),
trap::Arg::Label(id),
trap::Arg::Int(40), // severity 40 = error
trap::Arg::String("parse_error".to_string()),
trap::Arg::String(error_message),
trap::Arg::String(full_error_message),
trap::Arg::Label(loc),
],
);
}
@@ -335,7 +273,8 @@ impl Visitor<'_> {
node: Node,
) {
let (start_line, start_column, end_line, end_column) = location_for(self.source, node);
let loc = self.trap_writer.location(
let loc = location(
self.trap_writer,
self.file_label,
start_line,
start_column,
@@ -374,7 +313,8 @@ impl Visitor<'_> {
}
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 loc = self.trap_writer.location(
let loc = location(
self.trap_writer,
self.file_label,
start_line,
start_column,
@@ -402,19 +342,19 @@ impl Visitor<'_> {
match &table.kind {
EntryKind::Token { kind_id, .. } => {
self.trap_writer.add_tuple(
&format!("{}_ast_node_info", self.language_prefix),
&self.ast_node_info_table_name,
vec![
Arg::Label(id),
Arg::Label(parent_id),
Arg::Int(parent_index),
Arg::Label(loc),
trap::Arg::Label(id),
trap::Arg::Label(parent_id),
trap::Arg::Int(parent_index),
trap::Arg::Label(loc),
],
);
self.trap_writer.add_tuple(
&format!("{}_tokeninfo", self.language_prefix),
&self.tokeninfo_table_name,
vec![
Arg::Label(id),
Arg::Int(*kind_id),
trap::Arg::Label(id),
trap::Arg::Int(*kind_id),
sliced_source_arg(self.source, node),
],
);
@@ -425,15 +365,15 @@ impl Visitor<'_> {
} => {
if let Some(args) = self.complex_node(&node, fields, &child_nodes, id) {
self.trap_writer.add_tuple(
&format!("{}_ast_node_info", self.language_prefix),
&self.ast_node_info_table_name,
vec![
Arg::Label(id),
Arg::Label(parent_id),
Arg::Int(parent_index),
Arg::Label(loc),
trap::Arg::Label(id),
trap::Arg::Label(parent_id),
trap::Arg::Int(parent_index),
trap::Arg::Label(loc),
],
);
let mut all_args = vec![Arg::Label(id)];
let mut all_args = vec![trap::Arg::Label(id)];
all_args.extend(args);
self.trap_writer.add_tuple(table_name, all_args);
}
@@ -472,9 +412,9 @@ impl Visitor<'_> {
node: &Node,
fields: &[Field],
child_nodes: &[ChildNode],
parent_id: Label,
) -> Option<Vec<Arg>> {
let mut map: Map<&Option<String>, (&Field, Vec<Arg>)> = Map::new();
parent_id: trap::Label,
) -> Option<Vec<trap::Arg>> {
let mut map: Map<&Option<String>, (&Field, Vec<trap::Arg>)> = Map::new();
for field in fields {
map.insert(&field.name, (field, Vec::new()));
}
@@ -488,9 +428,9 @@ impl Visitor<'_> {
{
// We can safely unwrap because type_matches checks the key is in the map.
let (int_value, _) = int_mapping.get(&child_node.type_name.kind).unwrap();
values.push(Arg::Int(*int_value));
values.push(trap::Arg::Int(*int_value));
} else {
values.push(Arg::Label(child_node.label));
values.push(trap::Arg::Label(child_node.label));
}
} else if field.name.is_some() {
let error_message = format!(
@@ -569,9 +509,9 @@ impl Visitor<'_> {
);
break;
}
let mut args = vec![Arg::Label(parent_id)];
let mut args = vec![trap::Arg::Label(parent_id)];
if *has_index {
args.push(Arg::Int(index))
args.push(trap::Arg::Int(index))
}
args.push(child_value.clone());
self.trap_writer.add_tuple(table_name, args);
@@ -625,9 +565,9 @@ impl Visitor<'_> {
}
// Emit a slice of a source file as an Arg.
fn sliced_source_arg(source: &[u8], n: Node) -> Arg {
fn sliced_source_arg(source: &[u8], n: Node) -> trap::Arg {
let range = n.byte_range();
Arg::String(String::from_utf8_lossy(&source[range.start..range.end]).into_owned())
trap::Arg::String(String::from_utf8_lossy(&source[range.start..range.end]).into_owned())
}
// Emit a pair of `TrapEntry`s for the provided node, appropriately calibrated.
@@ -699,59 +639,6 @@ fn traverse(tree: &Tree, visitor: &mut Visitor) {
}
}
pub struct Program(Vec<TrapEntry>);
impl fmt::Display for Program {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut text = String::new();
for trap_entry in &self.0 {
text.push_str(&format!("{}\n", trap_entry));
}
write!(f, "{}", text)
}
}
enum TrapEntry {
/// Maps the label to a fresh id, e.g. `#123=*`.
FreshId(Label),
/// Maps the label to a key, e.g. `#7=@"foo"`.
MapLabelToKey(Label, String),
/// foo_bar(arg*)
GenericTuple(String, Vec<Arg>),
Comment(String),
}
impl fmt::Display for TrapEntry {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
TrapEntry::FreshId(label) => write!(f, "{}=*", label),
TrapEntry::MapLabelToKey(label, key) => {
write!(f, "{}=@\"{}\"", label, key.replace("\"", "\"\""))
}
TrapEntry::GenericTuple(name, args) => {
write!(f, "{}(", name)?;
for (index, arg) in args.iter().enumerate() {
if index > 0 {
write!(f, ",")?;
}
write!(f, "{}", arg)?;
}
write!(f, ")")
}
TrapEntry::Comment(line) => write!(f, "// {}", line),
}
}
}
#[derive(Debug, Copy, Clone)]
// Identifiers of the form #0, #1...
struct Label(u32);
impl fmt::Display for Label {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "#{:x}", self.0)
}
}
// Numeric indices.
#[derive(Debug, Copy, Clone)]
struct Index(usize);
@@ -761,69 +648,3 @@ impl fmt::Display for Index {
write!(f, "{}", self.0)
}
}
// Some untyped argument to a TrapEntry.
#[derive(Debug, Clone)]
enum Arg {
Label(Label),
Int(usize),
String(String),
}
const MAX_STRLEN: usize = 1048576;
impl fmt::Display for Arg {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Arg::Label(x) => write!(f, "{}", x),
Arg::Int(x) => write!(f, "{}", x),
Arg::String(x) => write!(
f,
"\"{}\"",
limit_string(x, MAX_STRLEN).replace("\"", "\"\"")
),
}
}
}
/// Limit the length (in bytes) of a string. If the string's length in bytes is
/// less than or equal to the limit then the entire string is returned. Otherwise
/// 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: &str, max_size: usize) -> &str {
if string.len() <= max_size {
return string;
}
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 && (p[index] & 0b11000000) == 0b10000000 {
index -= 1;
}
&string[0..index]
}
#[test]
fn limit_string_test() {
assert_eq!("hello", limit_string(&"hello world".to_owned(), 5));
assert_eq!("hi ☹", limit_string(&"hi ☹☹".to_owned(), 6));
assert_eq!("hi ", limit_string(&"hi ☹☹".to_owned(), 5));
}
#[test]
fn escape_key_test() {
assert_eq!("foo!", escape_key("foo!"));
assert_eq!("foo&lbrace;&rbrace;", escape_key("foo{}"));
assert_eq!("&lbrace;&rbrace;", escape_key("{}"));
assert_eq!("", escape_key(""));
assert_eq!("/path/to/foo.rb", escape_key("/path/to/foo.rb"));
assert_eq!(
"/path/to/foo&amp;&lbrace;&rbrace;&quot;&commat;&num;.rb",
escape_key("/path/to/foo&{}\"@#.rb")
);
}

View File

@@ -1,51 +1,19 @@
mod extractor;
mod trap;
#[macro_use]
extern crate lazy_static;
extern crate num_cpus;
use clap::arg;
use flate2::write::GzEncoder;
use encoding::{self};
use rayon::prelude::*;
use std::borrow::Cow;
use std::fs;
use std::io::{BufRead, BufWriter};
use std::io::BufRead;
use std::path::{Path, PathBuf};
use tree_sitter::{Language, Parser, Range};
enum TrapCompression {
None,
Gzip,
}
impl TrapCompression {
fn from_env() -> TrapCompression {
match std::env::var("CODEQL_RUBY_TRAP_COMPRESSION") {
Ok(method) => match TrapCompression::from_string(&method) {
Some(c) => c,
None => {
tracing::error!("Unknown compression method '{}'; using gzip.", &method);
TrapCompression::Gzip
}
},
// Default compression method if the env var isn't set:
Err(_) => TrapCompression::Gzip,
}
}
fn from_string(s: &str) -> Option<TrapCompression> {
match s.to_lowercase().as_ref() {
"none" => Some(TrapCompression::None),
"gzip" => Some(TrapCompression::Gzip),
_ => None,
}
}
fn extension(&self) -> &str {
match self {
TrapCompression::None => "trap",
TrapCompression::Gzip => "trap.gz",
}
}
}
/**
* Gets the number of threads the extractor should use, by reading the
* CODEQL_THREADS environment variable and using it as described in the
@@ -75,6 +43,21 @@ fn num_codeql_threads() -> usize {
}
}
lazy_static! {
static ref CP_NUMBER: regex::Regex = regex::Regex::new("cp([0-9]+)").unwrap();
}
fn encoding_from_name(encoding_name: &str) -> Option<&(dyn encoding::Encoding + Send + Sync)> {
match encoding::label::encoding_from_whatwg_label(encoding_name) {
s @ Some(_) => s,
None => CP_NUMBER.captures(encoding_name).and_then(|cap| {
encoding::label::encoding_from_windows_code_page(
str::parse(cap.get(1).unwrap().as_str()).unwrap(),
)
}),
}
}
fn main() -> std::io::Result<()> {
tracing_subscriber::fmt()
.with_target(false)
@@ -118,7 +101,7 @@ fn main() -> std::io::Result<()> {
.value_of("output-dir")
.expect("missing --output-dir");
let trap_dir = PathBuf::from(trap_dir);
let trap_compression = TrapCompression::from_env();
let trap_compression = trap::Compression::from_env("CODEQL_RUBY_TRAP_COMPRESSION");
let file_list = matches.value_of("file-list").expect("missing --file-list");
let file_list = fs::File::open(file_list)?;
@@ -140,8 +123,9 @@ fn main() -> std::io::Result<()> {
let path = PathBuf::from(line).canonicalize()?;
let src_archive_file = path_for(&src_archive_dir, &path, "");
let mut source = std::fs::read(&path)?;
let mut needs_conversion = false;
let code_ranges;
let mut trap_writer = extractor::new_trap_writer();
let mut trap_writer = trap::Writer::new();
if path.extension().map_or(false, |x| x == "erb") {
tracing::info!("scanning: {}", path.display());
extractor::extract(
@@ -168,6 +152,43 @@ fn main() -> std::io::Result<()> {
}
code_ranges = ranges;
} else {
if let Some(encoding_name) = scan_coding_comment(&source) {
// If the input is already UTF-8 then there is no need to recode the source
// If the declared encoding is 'binary' or 'ascii-8bit' then it is not clear how
// to interpret characters. In this case it is probably best to leave the input
// unchanged.
if !encoding_name.eq_ignore_ascii_case("utf-8")
&& !encoding_name.eq_ignore_ascii_case("ascii-8bit")
&& !encoding_name.eq_ignore_ascii_case("binary")
{
if let Some(encoding) = encoding_from_name(&encoding_name) {
needs_conversion =
encoding.whatwg_name().unwrap_or_default() != "utf-8";
if needs_conversion {
match encoding
.decode(&source, encoding::types::DecoderTrap::Replace)
{
Ok(str) => source = str.as_bytes().to_owned(),
Err(msg) => {
needs_conversion = false;
tracing::warn!(
"{}: character decoding failure: {} ({})",
&path.to_string_lossy(),
msg,
&encoding_name
);
}
}
}
} else {
tracing::warn!(
"{}: unknown character encoding: '{}'",
&path.to_string_lossy(),
&encoding_name
);
}
}
}
code_ranges = vec![];
}
extractor::extract(
@@ -180,34 +201,30 @@ fn main() -> std::io::Result<()> {
&code_ranges,
)?;
std::fs::create_dir_all(&src_archive_file.parent().unwrap())?;
std::fs::copy(&path, &src_archive_file)?;
write_trap(&trap_dir, path, trap_writer, &trap_compression)
if needs_conversion {
std::fs::write(&src_archive_file, &source)?;
} else {
std::fs::copy(&path, &src_archive_file)?;
}
write_trap(&trap_dir, path, &trap_writer, trap_compression)
})
.expect("failed to extract files");
let path = PathBuf::from("extras");
let mut trap_writer = extractor::new_trap_writer();
trap_writer.populate_empty_location();
write_trap(&trap_dir, path, trap_writer, &trap_compression)
let mut trap_writer = trap::Writer::new();
extractor::populate_empty_location(&mut trap_writer);
write_trap(&trap_dir, path, &trap_writer, trap_compression)
}
fn write_trap(
trap_dir: &Path,
path: PathBuf,
trap_writer: extractor::TrapWriter,
trap_compression: &TrapCompression,
trap_writer: &trap::Writer,
trap_compression: trap::Compression,
) -> std::io::Result<()> {
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);
match trap_compression {
TrapCompression::None => trap_writer.output(&mut trap_file),
TrapCompression::Gzip => {
let mut compressed_writer = GzEncoder::new(trap_file, flate2::Compression::fast());
trap_writer.output(&mut compressed_writer)
}
}
trap_writer.write_to_file(&trap_file, trap_compression)
}
fn scan_erb(
@@ -299,3 +316,143 @@ fn path_for(dir: &Path, path: &Path, ext: &str) -> PathBuf {
}
result
}
fn skip_space(content: &[u8], index: usize) -> usize {
let mut index = index;
while index < content.len() {
let c = content[index] as char;
// white space except \n
let is_space = c == ' ' || ('\t'..='\r').contains(&c) && c != '\n';
if !is_space {
break;
}
index += 1;
}
index
}
fn scan_coding_comment(content: &[u8]) -> std::option::Option<Cow<str>> {
let mut index = 0;
// skip UTF-8 BOM marker if there is one
if content.len() >= 3 && content[0] == 0xef && content[1] == 0xbb && content[2] == 0xbf {
index += 3;
}
// skip #! line if there is one
if index + 1 < content.len()
&& content[index] as char == '#'
&& content[index + 1] as char == '!'
{
index += 2;
while index < content.len() && content[index] as char != '\n' {
index += 1
}
index += 1
}
index = skip_space(content, index);
if index >= content.len() || content[index] as char != '#' {
return None;
}
index += 1;
const CODING: [char; 12] = ['C', 'c', 'O', 'o', 'D', 'd', 'I', 'i', 'N', 'n', 'G', 'g'];
let mut word_index = 0;
while index < content.len() && word_index < CODING.len() && content[index] as char != '\n' {
if content[index] as char == CODING[word_index]
|| content[index] as char == CODING[word_index + 1]
{
word_index += 2
} else {
word_index = 0;
}
index += 1;
}
if word_index < CODING.len() {
return None;
}
index = skip_space(content, index);
if index < content.len() && content[index] as char != ':' && content[index] as char != '=' {
return None;
}
index += 1;
index = skip_space(content, index);
let start = index;
while index < content.len() {
let c = content[index] as char;
if c == '-' || c == '_' || c.is_ascii_alphanumeric() {
index += 1;
} else {
break;
}
}
if index > start {
return Some(String::from_utf8_lossy(&content[start..index]));
}
None
}
#[test]
fn test_scan_coding_comment() {
let text = "# encoding: utf-8";
let result = scan_coding_comment(text.as_bytes());
assert_eq!(result, Some("utf-8".into()));
let text = "#coding:utf-8";
let result = scan_coding_comment(&text.as_bytes());
assert_eq!(result, Some("utf-8".into()));
let text = "# foo\n# encoding: utf-8";
let result = scan_coding_comment(&text.as_bytes());
assert_eq!(result, None);
let text = "# encoding: latin1 encoding: utf-8";
let result = scan_coding_comment(&text.as_bytes());
assert_eq!(result, Some("latin1".into()));
let text = "# encoding: nonsense";
let result = scan_coding_comment(&text.as_bytes());
assert_eq!(result, Some("nonsense".into()));
let text = "# coding = utf-8";
let result = scan_coding_comment(&text.as_bytes());
assert_eq!(result, Some("utf-8".into()));
let text = "# CODING = utf-8";
let result = scan_coding_comment(&text.as_bytes());
assert_eq!(result, Some("utf-8".into()));
let text = "# CoDiNg = utf-8";
let result = scan_coding_comment(&text.as_bytes());
assert_eq!(result, Some("utf-8".into()));
let text = "# blah blahblahcoding = utf-8";
let result = scan_coding_comment(&text.as_bytes());
assert_eq!(result, Some("utf-8".into()));
// unicode BOM is ignored
let text = "\u{FEFF}# encoding: utf-8";
let result = scan_coding_comment(&text.as_bytes());
assert_eq!(result, Some("utf-8".into()));
let text = "\u{FEFF} # encoding: utf-8";
let result = scan_coding_comment(&text.as_bytes());
assert_eq!(result, Some("utf-8".into()));
let text = "#! /usr/bin/env ruby\n # encoding: utf-8";
let result = scan_coding_comment(&text.as_bytes());
assert_eq!(result, Some("utf-8".into()));
let text = "\u{FEFF}#! /usr/bin/env ruby\n # encoding: utf-8";
let result = scan_coding_comment(&text.as_bytes());
assert_eq!(result, Some("utf-8".into()));
// A #! must be the first thing on a line, otherwise it's a normal comment
let text = " #! /usr/bin/env ruby encoding = utf-8";
let result = scan_coding_comment(&text.as_bytes());
assert_eq!(result, Some("utf-8".into()));
let text = " #! /usr/bin/env ruby \n # encoding = utf-8";
let result = scan_coding_comment(&text.as_bytes());
assert_eq!(result, None);
}

275
ruby/extractor/src/trap.rs Normal file
View File

@@ -0,0 +1,275 @@
use std::borrow::Cow;
use std::fmt;
use std::io::{BufWriter, Write};
use std::path::Path;
use flate2::write::GzEncoder;
pub struct Writer {
/// The accumulated trap entries
trap_output: Vec<Entry>,
/// A counter for generating fresh labels
counter: u32,
/// cache of global keys
global_keys: std::collections::HashMap<String, Label>,
}
impl Writer {
pub fn new() -> Writer {
Writer {
counter: 0,
trap_output: Vec::new(),
global_keys: std::collections::HashMap::new(),
}
}
pub fn fresh_id(&mut self) -> Label {
let label = Label(self.counter);
self.counter += 1;
self.trap_output.push(Entry::FreshId(label));
label
}
/// Gets a label that will hold the unique ID of the passed string at import time.
/// This can be used for incrementally importable TRAP files -- use globally unique
/// strings to compute a unique ID for table tuples.
///
/// Note: You probably want to make sure that the key strings that you use are disjoint
/// for disjoint column types; the standard way of doing this is to prefix (or append)
/// the column type name to the ID. Thus, you might identify methods in Java by the
/// full ID "methods_com.method.package.DeclaringClass.method(argumentList)".
pub fn global_id(&mut self, key: &str) -> (Label, bool) {
if let Some(label) = self.global_keys.get(key) {
return (*label, false);
}
let label = Label(self.counter);
self.counter += 1;
self.global_keys.insert(key.to_owned(), label);
self.trap_output
.push(Entry::MapLabelToKey(label, key.to_owned()));
(label, true)
}
pub fn add_tuple(&mut self, table_name: &str, args: Vec<Arg>) {
self.trap_output
.push(Entry::GenericTuple(table_name.to_owned(), args))
}
pub fn comment(&mut self, text: String) {
self.trap_output.push(Entry::Comment(text));
}
pub fn write_to_file(&self, path: &Path, compression: Compression) -> std::io::Result<()> {
let trap_file = std::fs::File::create(path)?;
match compression {
Compression::None => {
let mut trap_file = BufWriter::new(trap_file);
self.write_trap_entries(&mut trap_file)
}
Compression::Gzip => {
let trap_file = GzEncoder::new(trap_file, flate2::Compression::fast());
let mut trap_file = BufWriter::new(trap_file);
self.write_trap_entries(&mut trap_file)
}
}
}
fn write_trap_entries<W: Write>(&self, file: &mut W) -> std::io::Result<()> {
for trap_entry in &self.trap_output {
writeln!(file, "{}", trap_entry)?;
}
std::io::Result::Ok(())
}
}
pub enum Entry {
/// Maps the label to a fresh id, e.g. `#123=*`.
FreshId(Label),
/// Maps the label to a key, e.g. `#7=@"foo"`.
MapLabelToKey(Label, String),
/// foo_bar(arg*)
GenericTuple(String, Vec<Arg>),
Comment(String),
}
impl fmt::Display for Entry {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Entry::FreshId(label) => write!(f, "{}=*", label),
Entry::MapLabelToKey(label, key) => {
write!(f, "{}=@\"{}\"", label, key.replace("\"", "\"\""))
}
Entry::GenericTuple(name, args) => {
write!(f, "{}(", name)?;
for (index, arg) in args.iter().enumerate() {
if index > 0 {
write!(f, ",")?;
}
write!(f, "{}", arg)?;
}
write!(f, ")")
}
Entry::Comment(line) => write!(f, "// {}", line),
}
}
}
#[derive(Debug, Copy, Clone)]
// Identifiers of the form #0, #1...
pub struct Label(u32);
impl fmt::Display for Label {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "#{:x}", self.0)
}
}
// Some untyped argument to a TrapEntry.
#[derive(Debug, Clone)]
pub enum Arg {
Label(Label),
Int(usize),
String(String),
}
const MAX_STRLEN: usize = 1048576;
impl fmt::Display for Arg {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Arg::Label(x) => write!(f, "{}", x),
Arg::Int(x) => write!(f, "{}", x),
Arg::String(x) => write!(
f,
"\"{}\"",
limit_string(x, MAX_STRLEN).replace("\"", "\"\"")
),
}
}
}
pub struct Program(Vec<Entry>);
impl fmt::Display for Program {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut text = String::new();
for trap_entry in &self.0 {
text.push_str(&format!("{}\n", trap_entry));
}
write!(f, "{}", text)
}
}
pub fn full_id_for_file(normalized_path: &str) -> String {
format!("{};sourcefile", escape_key(normalized_path))
}
pub fn full_id_for_folder(normalized_path: &str) -> String {
format!("{};folder", escape_key(normalized_path))
}
/// Escapes a string for use in a TRAP key, by replacing special characters with
/// HTML entities.
fn escape_key<'a, S: Into<Cow<'a, str>>>(key: S) -> Cow<'a, str> {
fn needs_escaping(c: char) -> bool {
matches!(c, '&' | '{' | '}' | '"' | '@' | '#')
}
let key = key.into();
if key.contains(needs_escaping) {
let mut escaped = String::with_capacity(2 * key.len());
for c in key.chars() {
match c {
'&' => escaped.push_str("&amp;"),
'{' => escaped.push_str("&lbrace;"),
'}' => escaped.push_str("&rbrace;"),
'"' => escaped.push_str("&quot;"),
'@' => escaped.push_str("&commat;"),
'#' => escaped.push_str("&num;"),
_ => escaped.push(c),
}
}
Cow::Owned(escaped)
} else {
key
}
}
/// Limit the length (in bytes) of a string. If the string's length in bytes is
/// less than or equal to the limit then the entire string is returned. Otherwise
/// 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: &str, max_size: usize) -> &str {
if string.len() <= max_size {
return string;
}
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 && (p[index] & 0b11000000) == 0b10000000 {
index -= 1;
}
&string[0..index]
}
#[derive(Clone, Copy)]
pub enum Compression {
None,
Gzip,
}
impl Compression {
pub fn from_env(var_name: &str) -> Compression {
match std::env::var(var_name) {
Ok(method) => match Compression::from_string(&method) {
Some(c) => c,
None => {
tracing::error!("Unknown compression method '{}'; using gzip.", &method);
Compression::Gzip
}
},
// Default compression method if the env var isn't set:
Err(_) => Compression::Gzip,
}
}
pub fn from_string(s: &str) -> Option<Compression> {
match s.to_lowercase().as_ref() {
"none" => Some(Compression::None),
"gzip" => Some(Compression::Gzip),
_ => None,
}
}
pub fn extension(&self) -> &str {
match self {
Compression::None => "trap",
Compression::Gzip => "trap.gz",
}
}
}
#[test]
fn limit_string_test() {
assert_eq!("hello", limit_string(&"hello world".to_owned(), 5));
assert_eq!("hi ☹", limit_string(&"hi ☹☹".to_owned(), 6));
assert_eq!("hi ", limit_string(&"hi ☹☹".to_owned(), 5));
}
#[test]
fn escape_key_test() {
assert_eq!("foo!", escape_key("foo!"));
assert_eq!("foo&lbrace;&rbrace;", escape_key("foo{}"));
assert_eq!("&lbrace;&rbrace;", escape_key("{}"));
assert_eq!("", escape_key(""));
assert_eq!("/path/to/foo.rb", escape_key("/path/to/foo.rb"));
assert_eq!(
"/path/to/foo&amp;&lbrace;&rbrace;&quot;&commat;&num;.rb",
escape_key("/path/to/foo&{}\"@#.rb")
);
}

View File

@@ -12,4 +12,4 @@ node-types = { path = "../node-types" }
tracing = "0.1"
tracing-subscriber = { version = "0.3.3", features = ["env-filter"] }
tree-sitter-embedded-template = { git = "https://github.com/tree-sitter/tree-sitter-embedded-template.git", rev = "1a538da253d73f896b9f6c0c7d79cda58791ac5c" }
tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "6334d6ab3d04a5672da695d3b155ca3301511f8d" }
tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "e75d04404c9dd71ad68850d5c672b226d5e694f3" }

View File

@@ -10,6 +10,6 @@ private class MyConsistencyConfiguration extends ConsistencyConfiguration {
or
n instanceof SummaryNode
or
n instanceof HashSplatArgumentsNode
n instanceof SynthHashSplatArgumentNode
}
}

View File

@@ -1,5 +1,31 @@
## 0.3.2
### Minor Analysis Improvements
* Calls to `Arel.sql` are now recognised as propagating taint from their argument.
* Calls to `ActiveRecord::Relation#annotate` are now recognized as `SqlExecution`s so that it will be considered as a sink for queries like rb/sql-injection.
## 0.3.1
### Minor Analysis Improvements
* Fixed a bug causing every expression in the database to be considered a system-command execution sink when calls to any of the following methods exist:
* The `spawn`, `fspawn`, `popen4`, `pspawn`, `system`, `_pspawn` methods and the backtick operator from the `POSIX::spawn` gem.
* The `execute_command`, `rake`, `rails_command`, and `git` methods in `Rails::Generation::Actions`.
* Improved modeling of sensitive data sources, so common words like `certain` and `secretary` are no longer considered a certificate and a secret (respectively).
## 0.3.0
### Deprecated APIs
* The `BarrierGuard` class has been deprecated. Such barriers and sanitizers can now instead be created using the new `BarrierGuard` parameterized module.
## 0.2.3
### Minor Analysis Improvements
- Calls to `Zip::File.open` and `Zip::File.new` have been added as `FileSystemAccess` sinks. As a result queries like `rb/path-injection` now flag up cases where users may access arbitrary archive files.
## 0.2.2
### Major Analysis Improvements

View File

@@ -1,4 +0,0 @@
---
category: minorAnalysis
---
* Improved modeling of sensitive data sources, so common words like `certain` and `secretary` are no longer considered a certificate and a secret (respectively).

View File

@@ -0,0 +1,5 @@
---
category: minorAnalysis
---
* Calls to `ActiveRecord::Base.create` and `ActiveRecord::Base.update` are now
recognised as write accesses.

View File

@@ -0,0 +1,5 @@
---
category: minorAnalysis
---
* Arguments to `Mime::Type#match?` and `Mime::Type#=~` are now recognised as
regular expression sources.

View File

@@ -1,4 +1,5 @@
---
category: deprecated
---
## 0.3.0
### Deprecated APIs
* The `BarrierGuard` class has been deprecated. Such barriers and sanitizers can now instead be created using the new `BarrierGuard` parameterized module.

View File

@@ -0,0 +1,8 @@
## 0.3.1
### Minor Analysis Improvements
* Fixed a bug causing every expression in the database to be considered a system-command execution sink when calls to any of the following methods exist:
* The `spawn`, `fspawn`, `popen4`, `pspawn`, `system`, `_pspawn` methods and the backtick operator from the `POSIX::spawn` gem.
* The `execute_command`, `rake`, `rails_command`, and `git` methods in `Rails::Generation::Actions`.
* Improved modeling of sensitive data sources, so common words like `certain` and `secretary` are no longer considered a certificate and a secret (respectively).

View File

@@ -0,0 +1,6 @@
## 0.3.2
### Minor Analysis Improvements
* Calls to `Arel.sql` are now recognised as propagating taint from their argument.
* Calls to `ActiveRecord::Relation#annotate` are now recognized as `SqlExecution`s so that it will be considered as a sink for queries like rb/sql-injection.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.2.3
lastReleaseVersion: 0.3.2

View File

@@ -19,48 +19,154 @@ private import codeql.ruby.dataflow.internal.DataFlowDispatch as DataFlowDispatc
*/
module API {
/**
* An abstract representation of a definition or use of an API component such as a Ruby module,
* or the result of a method call.
* A node in the API graph, representing a value that has crossed the boundary between this
* codebase and an external library (or in general, any external codebase).
*
* ### Basic usage
*
* API graphs are typically used to identify "API calls", that is, calls to an external function
* whose implementation is not necessarily part of the current codebase.
*
* The most basic use of API graphs is typically as follows:
* 1. Start with `API::getTopLevelMember` for the relevant library.
* 2. Follow up with a chain of accessors such as `getMethod` describing how to get to the relevant API function.
* 3. Map the resulting API graph nodes to data-flow nodes, using `asSource` or `asSink`.
*
* For example, a simplified way to get arguments to `Foo.bar` would be
* ```codeql
* API::getTopLevelMember("Foo").getMethod("bar").getParameter(0).asSink()
* ```
*
* The most commonly used accessors are `getMember`, `getMethod`, `getParameter`, and `getReturn`.
*
* ### API graph nodes
*
* There are two kinds of nodes in the API graphs, distinguished by who is "holding" the value:
* - **Use-nodes** represent values held by the current codebase, which came from an external library.
* (The current codebase is "using" a value that came from the library).
* - **Def-nodes** represent values held by the external library, which came from this codebase.
* (The current codebase "defines" the value seen by the library).
*
* API graph nodes are associated with data-flow nodes in the current codebase.
* (Since external libraries are not part of the database, there is no way to associate with concrete
* data-flow nodes from the external library).
* - **Use-nodes** are associated with data-flow nodes where a value enters the current codebase,
* such as the return value of a call to an external function.
* - **Def-nodes** are associated with data-flow nodes where a value leaves the current codebase,
* such as an argument passed in a call to an external function.
*
*
* ### Access paths and edge labels
*
* Nodes in the API graph are associated with a set of access paths, describing a series of operations
* that may be performed to obtain that value.
*
* For example, the access path `API::getTopLevelMember("Foo").getMethod("bar")` represents the action of
* reading the top-level constant `Foo` and then accessing the method `bar` on the resulting object.
* It would be associated with a call such as `Foo.bar()`.
*
* Each edge in the graph is labelled by such an "operation". For an edge `A->B`, the type of the `A` node
* determines who is performing the operation, and the type of the `B` node determines who ends up holding
* the result:
* - An edge starting from a use-node describes what the current codebase is doing to a value that
* came from a library.
* - An edge starting from a def-node describes what the external library might do to a value that
* came from the current codebase.
* - An edge ending in a use-node means the result ends up in the current codebase (at its associated data-flow node).
* - An edge ending in a def-node means the result ends up in external code (its associated data-flow node is
* the place where it was "last seen" in the current codebase before flowing out)
*
* Because the implementation of the external library is not visible, it is not known exactly what operations
* it will perform on values that flow there. Instead, the edges starting from a def-node are operations that would
* lead to an observable effect within the current codebase; without knowing for certain if the library will actually perform
* those operations. (When constructing these edges, we assume the library is somewhat well-behaved).
*
* For example, given this snippet:
* ```ruby
* Foo.bar(->(x) { doSomething(x) })
* ```
* A callback is passed to the external function `Foo.bar`. We can't know if `Foo.bar` will actually invoke this callback.
* But _if_ the library should decide to invoke the callback, then a value will flow into the current codebase via the `x` parameter.
* For that reason, an edge is generated representing the argument-passing operation that might be performed by `Foo.bar`.
* This edge is going from the def-node associated with the callback to the use-node associated with the parameter `x` of the lambda.
*/
class Node extends Impl::TApiNode {
/**
* Gets a data-flow node corresponding to a use of the API component represented by this node.
* Gets a data-flow node where this value may flow after entering the current codebase.
*
* For example, `Kernel.format "%s world!", "Hello"` is a use of the return of the `format` function of
* the `Kernel` module.
*
* This includes indirect uses found via data flow.
* This is similar to `asSource()` but additionally includes nodes that are transitively reachable by data flow.
* See `asSource()` for examples.
*/
DataFlow::Node getAUse() {
DataFlow::Node getAValueReachableFromSource() {
exists(DataFlow::LocalSourceNode src | Impl::use(this, src) |
Impl::trackUseNode(src).flowsTo(result)
)
}
/**
* Gets an immediate use of the API component represented by this node.
* Gets a data-flow node where this value enters the current codebase.
*
* Unlike `getAUse()`, this predicate only gets the immediate references, not the indirect uses
* found via data flow.
* For example:
* ```ruby
* # API::getTopLevelMember("Foo").asSource()
* Foo
*
* # API::getTopLevelMember("Foo").getMethod("bar").getReturn().asSource()
* Foo.bar
*
* # 'x' is found by:
* # API::getTopLevelMember("Foo").getMethod("bar").getBlock().getParameter(0).asSource()
* Foo.bar do |x|
* end
* ```
*/
DataFlow::LocalSourceNode getAnImmediateUse() { Impl::use(this, result) }
DataFlow::LocalSourceNode asSource() { Impl::use(this, result) }
/**
* Gets a data-flow node corresponding the value flowing into this API component.
* Gets a data-flow node where this value leaves the current codebase and flows into an
* external library (or in general, any external codebase).
*
* Concretely, this corresponds to an argument passed to a call to external code.
*
* For example:
* ```ruby
* # 'x' is found by:
* # API::getTopLevelMember("Foo").getMethod("bar").getParameter(0).asSink()
* Foo.bar(x)
*
* Foo.bar(-> {
* # 'x' is found by:
* # API::getTopLevelMember("Foo").getMethod("bar").getParameter(0).getReturn().asSink()
* x
* })
* ```
*/
DataFlow::Node getARhs() { Impl::def(this, result) }
DataFlow::Node asSink() { Impl::def(this, result) }
/**
* Gets a data-flow node that may interprocedurally flow to the value escaping into this API component.
* Get a data-flow node that transitively flows to an external library (or in general, any external codebase).
*
* This is similar to `asSink()` but additionally includes nodes that transitively reach a sink by data flow.
* See `asSink()` for examples.
*/
DataFlow::Node getAValueReachingRhs() { result = Impl::trackDefNode(this.getARhs()) }
DataFlow::Node getAValueReachingSink() { result = Impl::trackDefNode(this.asSink()) }
/** DEPRECATED. This predicate has been renamed to `getAValueReachableFromSource()`. */
deprecated DataFlow::Node getAUse() { result = this.getAValueReachableFromSource() }
/** DEPRECATED. This predicate has been renamed to `asSource()`. */
deprecated DataFlow::LocalSourceNode getAnImmediateUse() { result = this.asSource() }
/** DEPRECATED. This predicate has been renamed to `asSink()`. */
deprecated DataFlow::Node getARhs() { result = this.asSink() }
/** DEPRECATED. This predicate has been renamed to `getAValueReachingSink()`. */
deprecated DataFlow::Node getAValueReachingRhs() { result = this.getAValueReachingSink() }
/**
* Gets a call to a method on the receiver represented by this API component.
*/
DataFlow::CallNode getAMethodCall(string method) {
result = this.getReturn(method).getAnImmediateUse()
}
DataFlow::CallNode getAMethodCall(string method) { result = this.getReturn(method).asSource() }
/**
* Gets a node representing member `m` of this API component.
@@ -135,7 +241,7 @@ module API {
/**
* Gets a `new` call to the function represented by this API component.
*/
DataFlow::ExprNode getAnInstantiation() { result = this.getInstance().getAnImmediateUse() }
DataFlow::ExprNode getAnInstantiation() { result = this.getInstance().asSource() }
/**
* Gets a node representing a (direct or indirect) subclass of the class represented by this node.

View File

@@ -3,14 +3,17 @@
*/
private import codeql.ruby.frameworks.Core
private import codeql.ruby.frameworks.ActionCable
private import codeql.ruby.frameworks.ActionController
private import codeql.ruby.frameworks.ActiveRecord
private import codeql.ruby.frameworks.ActiveStorage
private import codeql.ruby.frameworks.ActionView
private import codeql.ruby.frameworks.ActiveSupport
private import codeql.ruby.frameworks.Archive
private import codeql.ruby.frameworks.Arel
private import codeql.ruby.frameworks.GraphQL
private import codeql.ruby.frameworks.Rails
private import codeql.ruby.frameworks.Railties
private import codeql.ruby.frameworks.Stdlib
private import codeql.ruby.frameworks.Files
private import codeql.ruby.frameworks.HttpClients

View File

@@ -92,20 +92,32 @@ private class ParsedStringRegExp extends RegExp {
override string getFlags() { none() }
}
/** Provides a class for modeling regular expression interpretations. */
module RegExpInterpretation {
/**
* A node that is not a regular expression literal, but is used in places that
* may interpret it as one. Instances of this class are typically strings that
* flow to method calls like `RegExp.new`.
*/
abstract class Range extends DataFlow::Node { }
}
/**
* Holds if `source` may be interpreted as a regular expression.
* A node interpreted as a regular expression.
*/
private predicate isInterpretedAsRegExp(DataFlow::Node source) {
// The first argument to an invocation of `Regexp.new` or `Regexp.compile`.
source = API::getTopLevelMember("Regexp").getAMethodCall(["compile", "new"]).getArgument(0)
or
// The argument of a call that coerces the argument to a regular expression.
exists(DataFlow::CallNode mce |
mce.getMethodName() = ["match", "match?"] and
source = mce.getArgument(0) and
// exclude https://ruby-doc.org/core-2.4.0/Regexp.html#method-i-match
not mce.getReceiver().asExpr().getExpr() instanceof AST::RegExpLiteral
)
class StdLibRegExpInterpretation extends RegExpInterpretation::Range {
StdLibRegExpInterpretation() {
// The first argument to an invocation of `Regexp.new` or `Regexp.compile`.
this = API::getTopLevelMember("Regexp").getAMethodCall(["compile", "new"]).getArgument(0)
or
// The argument of a call that coerces the argument to a regular expression.
exists(DataFlow::CallNode mce |
mce.getMethodName() = ["match", "match?"] and
this = mce.getArgument(0) and
// exclude https://ruby-doc.org/core-2.4.0/Regexp.html#method-i-match
not mce.getReceiver().asExpr().getExpr() instanceof AST::RegExpLiteral
)
}
}
private class RegExpConfiguration extends Configuration {
@@ -120,7 +132,7 @@ private class RegExpConfiguration extends Configuration {
)
}
override predicate isSink(DataFlow::Node sink) { isInterpretedAsRegExp(sink) }
override predicate isSink(DataFlow::Node sink) { sink instanceof RegExpInterpretation::Range }
override predicate isSanitizer(DataFlow::Node node) {
// stop flow if `node` is receiver of

View File

@@ -36,7 +36,7 @@ private import ExprNodes
* constant value in some cases.
*/
private module Propagation {
private ExprCfgNode getSource(VariableReadAccessCfgNode read) {
ExprCfgNode getSource(VariableReadAccessCfgNode read) {
exists(Ssa::WriteDefinition def |
def.assigns(result) and
read = def.getARead()
@@ -509,3 +509,53 @@ private module Cached {
}
import Cached
/**
* Holds if the control flow node `e` refers to an array constructed from the
* array literal `arr`.
* Example:
* ```rb
* [1, 2, 3]
* C = [1, 2, 3]; C
* x = [1, 2, 3]; x
* ```
*/
predicate isArrayConstant(ExprCfgNode e, ArrayLiteralCfgNode arr) {
// [...]
e = arr
or
// e = [...]; e
isArrayConstant(getSource(e), arr)
or
isArrayExpr(e.getExpr(), arr)
}
/**
* Holds if the expression `e` refers to an array constructed from the array literal `arr`.
*/
private predicate isArrayExpr(Expr e, ArrayLiteralCfgNode arr) {
// e = [...]
e = arr.getExpr()
or
// Like above, but handles the desugaring of array literals to Array.[] calls.
e.getDesugared() = arr.getExpr()
or
// A = [...]; A
// A = a; A
isArrayExpr(e.(ConstantReadAccess).getValue(), arr)
or
// Recurse via CFG nodes. Necessary for example in:
// a = [...]
// A = a
// A
//
// We map from A to a via ConstantReadAccess::getValue, yielding the Expr a.
// To get to [...] we need to go via getSource(ExprCfgNode e), so we find a
// CFG node for a and call `isArrayConstant`.
//
// The use of `forex` is intended to ensure that a is an array constant in all
// control flow paths.
// Note(hmac): I don't think this is necessary, as `getSource` will not return
// results if the source is a phi node.
forex(ExprCfgNode n | n = e.getAControlFlowNode() | isArrayConstant(n, arr))
}

View File

@@ -357,6 +357,14 @@ module ExprNodes {
)
}
/**
* Gets the `n`th positional argument of this call.
* Unlike `getArgument`, this excludes keyword arguments.
*/
final ExprCfgNode getPositionalArgument(int n) {
result = this.getArgument(n) and not result instanceof PairCfgNode
}
/** Gets the number of arguments of this call. */
final int getNumberOfArguments() { result = e.getNumberOfArguments() }
@@ -374,6 +382,9 @@ module ExprNodes {
MethodCallCfgNode() { super.getExpr() instanceof MethodCall }
override MethodCall getExpr() { result = super.getExpr() }
/** Gets the name of this method call. */
string getMethodName() { result = this.getExpr().getMethodName() }
}
private class CaseExprChildMapping extends ExprChildMapping, CaseExpr {

View File

@@ -881,7 +881,12 @@ import Cached
* graph is restricted to nodes from `RelevantNode`.
*/
module TestOutput {
abstract class RelevantNode extends Node { }
abstract class RelevantNode extends Node {
/**
* Gets a string used to resolve ties in node and edge ordering.
*/
string getOrderDisambuigation() { result = "" }
}
query predicate nodes(RelevantNode n, string attr, string val) {
attr = "semmle.order" and
@@ -894,7 +899,8 @@ module TestOutput {
p
order by
l.getFile().getBaseName(), l.getFile().getAbsolutePath(), l.getStartLine(),
l.getStartColumn(), l.getEndLine(), l.getEndColumn(), p.toString()
l.getStartColumn(), l.getEndLine(), l.getEndColumn(), p.toString(),
p.getOrderDisambuigation()
)
).toString()
}
@@ -916,7 +922,8 @@ module TestOutput {
s
order by
l.getFile().getBaseName(), l.getFile().getAbsolutePath(), l.getStartLine(),
l.getStartColumn(), l.getEndLine(), l.getEndColumn(), t.toString()
l.getStartColumn(), l.getEndLine(), l.getEndColumn(), t.toString(), s.toString(),
s.getOrderDisambuigation()
)
).toString()
}

View File

@@ -3,6 +3,10 @@
private import ruby
private import codeql.ruby.DataFlow
private import codeql.ruby.CFG
private import codeql.ruby.controlflow.CfgNodes
private import codeql.ruby.dataflow.SSA
private import codeql.ruby.ast.internal.Constant
private import codeql.ruby.InclusionTests
private predicate stringConstCompare(CfgNodes::ExprCfgNode g, CfgNode e, boolean branch) {
exists(CfgNodes::ExprNodes::ComparisonOperationCfgNode c |
@@ -61,19 +65,7 @@ deprecated class StringConstCompare extends DataFlow::BarrierGuard,
// The value of the condition that results in the node being validated.
private boolean checkedBranch;
StringConstCompare() {
exists(CfgNodes::ExprNodes::StringLiteralCfgNode strLitNode |
this.getExpr() instanceof EqExpr and checkedBranch = true
or
this.getExpr() instanceof CaseEqExpr and checkedBranch = true
or
this.getExpr() instanceof NEExpr and checkedBranch = false
|
this.getLeftOperand() = strLitNode and this.getRightOperand() = checkedNode
or
this.getLeftOperand() = checkedNode and this.getRightOperand() = strLitNode
)
}
StringConstCompare() { stringConstCompare(this, checkedNode, checkedBranch) }
override predicate checks(CfgNode expr, boolean branch) {
expr = checkedNode and branch = checkedBranch
@@ -81,15 +73,19 @@ deprecated class StringConstCompare extends DataFlow::BarrierGuard,
}
private predicate stringConstArrayInclusionCall(CfgNodes::ExprCfgNode g, CfgNode e, boolean branch) {
exists(CfgNodes::ExprNodes::MethodCallCfgNode mc, ArrayLiteral aLit |
mc = g and
mc.getExpr().getMethodName() = "include?" and
[mc.getExpr().getReceiver(), mc.getExpr().getReceiver().(ConstantReadAccess).getValue()] = aLit
exists(InclusionTest t |
t.asExpr() = g and
e = t.getContainedNode().asExpr() and
branch = t.getPolarity()
|
forall(Expr elem | elem = aLit.getAnElement() | elem instanceof StringLiteral) and
mc.getArgument(0) = e
) and
branch = true
exists(ExprNodes::ArrayLiteralCfgNode arr |
isArrayConstant(t.getContainerNode().asExpr(), arr)
|
forall(ExprCfgNode elem | elem = arr.getAnArgument() |
elem instanceof ExprNodes::StringLiteralCfgNode
)
)
)
}
/**
@@ -132,16 +128,7 @@ deprecated class StringConstArrayInclusionCall extends DataFlow::BarrierGuard,
CfgNodes::ExprNodes::MethodCallCfgNode {
private CfgNode checkedNode;
StringConstArrayInclusionCall() {
exists(ArrayLiteral aLit |
this.getExpr().getMethodName() = "include?" and
[this.getExpr().getReceiver(), this.getExpr().getReceiver().(ConstantReadAccess).getValue()] =
aLit
|
forall(Expr elem | elem = aLit.getAnElement() | elem instanceof StringLiteral) and
this.getArgument(0) = checkedNode
)
}
StringConstArrayInclusionCall() { stringConstArrayInclusionCall(this, checkedNode, true) }
override predicate checks(CfgNode expr, boolean branch) { expr = checkedNode and branch = true }
}

View File

@@ -65,7 +65,12 @@ class DataFlowCallable extends TDataFlowCallable {
string toString() { result = [this.asCallable().toString(), this.asLibraryCallable()] }
/** Gets the location of this callable. */
Location getLocation() { result = this.asCallable().getLocation() }
Location getLocation() {
result = this.asCallable().getLocation()
or
this instanceof TLibraryCallable and
result instanceof EmptyLocation
}
}
/**

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -227,6 +227,9 @@ private module Cached {
} or
TSelfParameterNode(MethodBase m) or
TBlockParameterNode(MethodBase m) or
TSynthHashSplatParameterNode(DataFlowCallable c) {
isParameterNode(_, c, any(ParameterPosition p | p.isKeyword(_)))
} or
TExprPostUpdateNode(CfgNodes::ExprCfgNode n) {
n instanceof Argument or
n = any(CfgNodes::ExprNodes::InstanceVariableAccessCfgNode v).getReceiver()
@@ -240,12 +243,13 @@ private module Cached {
TSummaryParameterNode(FlowSummaryImpl::Public::SummarizedCallable c, ParameterPosition pos) {
FlowSummaryImpl::Private::summaryParameterNodeRange(c, pos)
} or
THashSplatArgumentsNode(CfgNodes::ExprNodes::CallCfgNode c) {
TSynthHashSplatArgumentNode(CfgNodes::ExprNodes::CallCfgNode c) {
exists(Argument arg | arg.isArgumentOf(c, any(ArgumentPosition pos | pos.isKeyword(_))))
}
class TParameterNode =
TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or TSummaryParameterNode;
TNormalParameterNode or TBlockParameterNode or TSelfParameterNode or
TSynthHashSplatParameterNode or TSummaryParameterNode;
private predicate defaultValueFlow(NamedParameter p, ExprNode e) {
p.(OptionalParameter).getDefaultValue() = e.getExprNode().getExpr()
@@ -328,18 +332,21 @@ private module Cached {
cached
predicate isLocalSourceNode(Node n) {
n instanceof ParameterNode
or
n instanceof PostUpdateNodes::ExprPostUpdateNode
or
// Nodes that can't be reached from another entry definition or expression.
not reachedFromExprOrEntrySsaDef(n)
or
// Ensure all entry SSA definitions are local sources -- for parameters, this
// is needed by type tracking. Note that when the parameter has a default value,
// it will be reachable from an expression (the default value) and therefore
// won't be caught by the rule above.
entrySsaDefinition(n)
not n instanceof SynthHashSplatParameterNode and
(
n instanceof ParameterNode
or
n instanceof PostUpdateNodes::ExprPostUpdateNode
or
// Nodes that can't be reached from another entry definition or expression.
not reachedFromExprOrEntrySsaDef(n)
or
// Ensure all entry SSA definitions are local sources -- for parameters, this
// is needed by type tracking. Note that when the parameter has a default value,
// it will be reachable from an expression (the default value) and therefore
// won't be caught by the rule above.
entrySsaDefinition(n)
)
}
cached
@@ -415,7 +422,9 @@ predicate nodeIsHidden(Node n) {
or
n instanceof SynthReturnNode
or
n instanceof HashSplatArgumentsNode
n instanceof SynthHashSplatParameterNode
or
n instanceof SynthHashSplatArgumentNode
}
/** An SSA definition, viewed as a node in a data flow graph. */
@@ -470,10 +479,13 @@ private module ParameterNodes {
abstract class ParameterNodeImpl extends NodeImpl {
abstract Parameter getParameter();
abstract predicate isSourceParameterOf(Callable c, ParameterPosition pos);
abstract predicate isParameterOf(DataFlowCallable c, ParameterPosition pos);
predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
this.isSourceParameterOf(c.asCallable(), pos)
final predicate isSourceParameterOf(Callable c, ParameterPosition pos) {
exists(DataFlowCallable callable |
this.isParameterOf(callable, pos) and
c = callable.asCallable()
)
}
}
@@ -488,21 +500,23 @@ private module ParameterNodes {
override Parameter getParameter() { result = parameter }
override predicate isSourceParameterOf(Callable c, ParameterPosition pos) {
exists(int i | pos.isPositional(i) and c.getParameter(i) = parameter |
parameter instanceof SimpleParameter
or
parameter instanceof OptionalParameter
)
or
parameter =
any(KeywordParameter kp |
c.getAParameter() = kp and
pos.isKeyword(kp.getName())
override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
exists(Callable callable | callable = c.asCallable() |
exists(int i | pos.isPositional(i) and callable.getParameter(i) = parameter |
parameter instanceof SimpleParameter
or
parameter instanceof OptionalParameter
)
or
parameter = c.getAParameter().(HashSplatParameter) and
pos.isHashSplat()
or
parameter =
any(KeywordParameter kp |
callable.getAParameter() = kp and
pos.isKeyword(kp.getName())
)
or
parameter = callable.getAParameter().(HashSplatParameter) and
pos.isHashSplat()
)
}
override CfgScope getCfgScope() { result = parameter.getCallable() }
@@ -525,8 +539,8 @@ private module ParameterNodes {
override Parameter getParameter() { none() }
override predicate isSourceParameterOf(Callable c, ParameterPosition pos) {
method = c and pos.isSelf()
override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
method = c.asCallable() and pos.isSelf()
}
override CfgScope getCfgScope() { result = method }
@@ -551,8 +565,8 @@ private module ParameterNodes {
result = method.getAParameter() and result instanceof BlockParameter
}
override predicate isSourceParameterOf(Callable c, ParameterPosition pos) {
c = method and pos.isBlock()
override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
c.asCallable() = method and pos.isBlock()
}
override CfgScope getCfgScope() { result = method }
@@ -570,6 +584,73 @@ private module ParameterNodes {
}
}
/**
* For all methods containing keyword parameters, we construct a synthesized
* (hidden) parameter node to contain all keyword arguments. This allows us
* to handle cases like
*
* ```rb
* def foo(p1:, p2:)
* sink(p1)
* sink(p2)
* end
*
* args = {:p1 => taint(1), :p2 => taint(2) }
* foo(**args)
* ```
*
* by adding read steps out of the synthesized parameter node to the relevant
* keyword parameters.
*
* Note that this will introduce a bit of redundancy in cases like
*
* ```rb
* foo(p1: taint(1), p2: taint(2))
* ```
*
* where direct keyword matching is possible, since we construct a synthesized hash
* splat argument (`SynthHashSplatArgumentNode`) at the call site, which means that
* `taint(1)` will flow into `p1` both via normal keyword matching and via the synthesized
* nodes (and similarly for `p2`). However, this redunancy is OK since
* (a) it means that type-tracking through keyword arguments also works in most cases,
* (b) read/store steps can be avoided when direct keyword matching is possible, and
* hence access path limits are not a concern, and
* (c) since the synthesized nodes are hidden, the reported data-flow paths will be
* collapsed anyway.
*/
class SynthHashSplatParameterNode extends ParameterNodeImpl, TSynthHashSplatParameterNode {
private DataFlowCallable callable;
SynthHashSplatParameterNode() { this = TSynthHashSplatParameterNode(callable) }
/**
* Gets a keyword parameter that will be the result of reading `c` out of this
* synthesized node.
*/
ParameterNode getAKeywordParameter(ContentSet c) {
exists(string name |
isParameterNode(result, callable, any(ParameterPosition p | p.isKeyword(name)))
|
c = getKeywordContent(name) or
c.isSingleton(TUnknownElementContent())
)
}
final override Parameter getParameter() { none() }
final override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
c = callable and pos.isHashSplat()
}
final override CfgScope getCfgScope() { result = callable.asCallable() }
final override DataFlowCallable getEnclosingCallable() { result = callable }
final override Location getLocationImpl() { result = callable.getLocation() }
final override string toStringImpl() { result = "**kwargs" }
}
/** A parameter for a library callable with a flow summary. */
class SummaryParameterNode extends ParameterNodeImpl, TSummaryParameterNode {
private FlowSummaryImpl::Public::SummarizedCallable sc;
@@ -579,8 +660,6 @@ private module ParameterNodes {
override Parameter getParameter() { none() }
override predicate isSourceParameterOf(Callable c, ParameterPosition pos) { none() }
override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
sc = c.asLibraryCallable() and pos = pos_
}
@@ -689,10 +768,10 @@ private module ArgumentNodes {
* part of the method signature, such that those cannot end up in the hash-splat
* parameter.
*/
class HashSplatArgumentsNode extends ArgumentNode, THashSplatArgumentsNode {
class SynthHashSplatArgumentNode extends ArgumentNode, TSynthHashSplatArgumentNode {
CfgNodes::ExprNodes::CallCfgNode c;
HashSplatArgumentsNode() { this = THashSplatArgumentsNode(c) }
SynthHashSplatArgumentNode() { this = TSynthHashSplatArgumentNode(c) }
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
this.sourceArgumentOf(call.asCall(), pos)
@@ -704,10 +783,10 @@ private module ArgumentNodes {
}
}
private class HashSplatArgumentsNodeImpl extends NodeImpl, THashSplatArgumentsNode {
private class SynthHashSplatArgumentNodeImpl extends NodeImpl, TSynthHashSplatArgumentNode {
CfgNodes::ExprNodes::CallCfgNode c;
HashSplatArgumentsNodeImpl() { this = THashSplatArgumentsNode(c) }
SynthHashSplatArgumentNodeImpl() { this = TSynthHashSplatArgumentNode(c) }
override CfgScope getCfgScope() { result = c.getExpr().getCfgScope() }
@@ -929,7 +1008,7 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
or
// Wrap all keyword arguments in a synthesized hash-splat argument node
exists(CfgNodes::ExprNodes::CallCfgNode call, ArgumentPosition keywordPos, string name |
node2 = THashSplatArgumentsNode(call) and
node2 = TSynthHashSplatArgumentNode(call) and
node1.asExpr().(Argument).isArgumentOf(call, keywordPos) and
keywordPos.isKeyword(name) and
c = getKeywordContent(name)
@@ -962,6 +1041,8 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
))
)
or
node2 = node1.(SynthHashSplatParameterNode).getAKeywordParameter(c)
or
FlowSummaryImpl::Private::Steps::summaryReadStep(node1, c, node2)
}

View File

@@ -71,6 +71,14 @@ class CallNode extends LocalSourceNode, ExprNode {
/** Gets the data-flow node corresponding to the named argument of the call corresponding to this data-flow node */
ExprNode getKeywordArgument(string name) { result.getExprNode() = node.getKeywordArgument(name) }
/**
* Gets the `n`th positional argument of this call.
* Unlike `getArgument`, this excludes keyword arguments.
*/
final ExprNode getPositionalArgument(int n) {
result.getExprNode() = node.getPositionalArgument(n)
}
/** Gets the name of the the method called by the method call (if any) corresponding to this data-flow node */
string getMethodName() { result = node.getExpr().(MethodCall).getMethodName() }

View File

@@ -0,0 +1,29 @@
/**
* Modeling for `ActionCable`, which is a websocket gem that ships with Rails.
* https://rubygems.org/gems/actioncable
*/
private import ruby
private import codeql.ruby.Concepts
private import codeql.ruby.ApiGraphs
private import codeql.ruby.frameworks.stdlib.Logger::Logger as StdlibLogger
/**
* Modeling for `ActionCable`.
*/
module ActionCable {
/**
* `ActionCable::Connection::TaggedLoggerProxy`
*/
module Logger {
private class ActionCableLoggerInstantiation extends StdlibLogger::LoggerInstantiation {
ActionCableLoggerInstantiation() {
this =
API::getTopLevelMember("ActionCable")
.getMember("Connection")
.getMember("TaggedLoggerProxy")
.getAnInstantiation()
}
}
}
}

View File

@@ -33,7 +33,7 @@ class ActionControllerControllerClass extends ClassDeclaration {
// In Rails applications `ApplicationController` typically extends `ActionController::Base`, but we
// treat it separately in case the `ApplicationController` definition is not in the database.
API::getTopLevelMember("ApplicationController")
].getASubclass().getAUse().asExpr().getExpr()
].getASubclass().getAValueReachableFromSource().asExpr().getExpr()
}
/**
@@ -83,7 +83,7 @@ class ActionControllerActionMethod extends Method, HTTP::Server::RequestHandler:
* Gets a route to this handler, if one exists.
* May return multiple results.
*/
ActionDispatch::Route getARoute() {
ActionDispatch::Routing::Route getARoute() {
exists(string name |
isRoute(result, name, controllerClass) and
isActionControllerMethod(this, name, controllerClass)
@@ -93,10 +93,10 @@ class ActionControllerActionMethod extends Method, HTTP::Server::RequestHandler:
pragma[nomagic]
private predicate isRoute(
ActionDispatch::Route route, string name, ActionControllerControllerClass controllerClass
ActionDispatch::Routing::Route route, string name, ActionControllerControllerClass controllerClass
) {
route.getController() + "_controller" =
ActionDispatch::underscore(namespaceDeclaration(controllerClass)) and
ActionDispatch::Routing::underscore(namespaceDeclaration(controllerClass)) and
name = route.getAction()
}
@@ -180,18 +180,31 @@ private class ActionControllerHtmlEscapeCall extends HtmlEscapeCall {
* specific URL/path or to a different action in this controller.
*/
class RedirectToCall extends ActionControllerContextCall {
RedirectToCall() { this.getMethodName() = "redirect_to" }
RedirectToCall() {
this.getMethodName() = ["redirect_to", "redirect_back", "redirect_back_or_to"]
}
/** Gets the `Expr` representing the URL to redirect to, if any */
Expr getRedirectUrl() { result = this.getArgument(0) }
Expr getRedirectUrl() {
this.getMethodName() = "redirect_back" and result = this.getKeywordArgument("fallback_location")
or
this.getMethodName() = ["redirect_to", "redirect_back_or_to"] and result = this.getArgument(0)
}
/** Gets the `ActionControllerActionMethod` to redirect to, if any */
ActionControllerActionMethod getRedirectActionMethod() {
exists(string methodName |
this.getKeywordArgument("action").getConstantValue().isStringlikeValue(methodName) and
methodName = result.getName() and
result.getEnclosingModule() = this.getControllerClass()
)
this.getKeywordArgument("action").getConstantValue().isStringlikeValue(result.getName()) and
result.getEnclosingModule() = this.getControllerClass()
}
/**
* Holds if this method call allows a redirect to an external host.
*/
predicate allowsExternalRedirect() {
// Unless the option allow_other_host is explicitly set to false, assume that external redirects are allowed.
// TODO: Take into account `config.action_controller.raise_on_open_redirects`.
// TODO: Take into account that this option defaults to false in Rails 7.
not this.getKeywordArgument("allow_other_host").getConstantValue().isBoolean(false)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -54,7 +54,7 @@ class ActiveRecordModelClass extends ClassDeclaration {
// In Rails applications `ApplicationRecord` typically extends `ActiveRecord::Base`, but we
// treat it separately in case the `ApplicationRecord` definition is not in the database.
API::getTopLevelMember("ApplicationRecord")
].getASubclass().getAUse().asExpr().getExpr()
].getASubclass().getAValueReachableFromSource().asExpr().getExpr()
}
// Gets the class declaration for this class and all of its super classes
@@ -133,6 +133,11 @@ private Expr sqlFragmentArgument(MethodCall call) {
or
methodName = "reload" and
result = call.getKeywordArgument("lock")
or
// Calls to `annotate` can be used to add block comments to SQL queries. These are potentially vulnerable to
// SQLi if user supplied input is passed in as an argument.
methodName = "annotate" and
result = call.getArgument(_)
)
)
}
@@ -330,12 +335,13 @@ class ActiveRecordInstance extends DataFlow::Node {
ActiveRecordModelClass getClass() { result = instantiation.getClass() }
}
// A call whose receiver may be an active record model object
private class ActiveRecordInstanceMethodCall extends DataFlow::CallNode {
/** A call whose receiver may be an active record model object */
class ActiveRecordInstanceMethodCall extends DataFlow::CallNode {
private ActiveRecordInstance instance;
ActiveRecordInstanceMethodCall() { this.getReceiver() = instance }
/** Gets the `ActiveRecordInstance` that is the receiver of this call. */
ActiveRecordInstance getInstance() { result = instance }
}
@@ -358,15 +364,6 @@ private module Persistence {
)
}
/**
* Holds if `call` has a keyword argument of with value `value`.
*/
private predicate keywordArgumentWithValue(DataFlow::CallNode call, DataFlow::ExprNode value) {
exists(ExprNodes::PairCfgNode pair | pair = call.getArgument(_).asExpr() |
value.asExpr() = pair.getValue()
)
}
/** A call to e.g. `User.create(name: "foo")` */
private class CreateLikeCall extends DataFlow::CallNode, PersistentWriteAccess::Range {
CreateLikeCall() {
@@ -380,8 +377,12 @@ private module Persistence {
override DataFlow::Node getValue() {
// attrs as hash elements in arg0
hashArgumentWithValue(this, 0, result) or
keywordArgumentWithValue(this, result)
hashArgumentWithValue(this, 0, result)
or
result = this.getKeywordArgument(_)
or
result = this.getPositionalArgument(0) and
not result.asExpr() instanceof ExprNodes::HashLiteralCfgNode
}
}
@@ -393,11 +394,19 @@ private module Persistence {
}
override DataFlow::Node getValue() {
keywordArgumentWithValue(this, result)
// User.update(1, name: "foo")
result = this.getKeywordArgument(_)
or
// User.update(1, params)
exists(int n | n > 0 |
result = this.getPositionalArgument(n) and
not result.asExpr() instanceof ExprNodes::ArrayLiteralCfgNode
)
or
// Case where 2 array args are passed - the first an array of IDs, and the
// second an array of hashes - each hash corresponding to an ID in the
// first array.
// User.update([1,2,3], [{name: "foo"}, {name: "bar"}])
exists(ExprNodes::ArrayLiteralCfgNode hashesArray |
this.getArgument(0).asExpr() instanceof ExprNodes::ArrayLiteralCfgNode and
hashesArray = this.getArgument(1).asExpr()
@@ -412,6 +421,28 @@ private module Persistence {
}
}
/**
* A call to `ActiveRecord::Relation#touch_all`, which updates the `updated_at`
* attribute on all records in the relation, setting it to the current time or
* the time specified. If passed additional attribute names, they will also be
* updated with the time.
* Examples:
* ```rb
* Person.all.touch_all
* Person.where(name: "David").touch_all
* Person.all.touch_all(:created_at)
* Person.all.touch_all(time: Time.new(2020, 5, 16, 0, 0, 0))
* ```
*/
private class TouchAllCall extends DataFlow::CallNode, PersistentWriteAccess::Range {
TouchAllCall() {
exists(this.asExpr().getExpr().(ActiveRecordModelClassMethodCall).getReceiverClass()) and
this.getMethodName() = "touch_all"
}
override DataFlow::Node getValue() { result = this.getKeywordArgument("time") }
}
/** A call to e.g. `User.insert_all([{name: "foo"}, {name: "bar"}])` */
private class InsertAllLikeCall extends DataFlow::CallNode, PersistentWriteAccess::Range {
private ExprNodes::ArrayLiteralCfgNode arr;
@@ -444,8 +475,12 @@ private module Persistence {
// attrs as hash elements in arg0
hashArgumentWithValue(this, 0, result)
or
// attrs as variable in arg0
result = this.getPositionalArgument(0) and
not result.asExpr() instanceof ExprNodes::HashLiteralCfgNode
or
// keyword arg
keywordArgumentWithValue(this, result)
result = this.getKeywordArgument(_)
}
}

View File

@@ -7,7 +7,6 @@ private import ruby
private import codeql.ruby.Concepts
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.FlowSummary
private import codeql.ruby.Concepts
private import codeql.ruby.ApiGraphs
private import codeql.ruby.frameworks.stdlib.Logger::Logger as StdlibLogger

View File

@@ -0,0 +1,31 @@
/**
* Provides modeling for Arel, a low level SQL library that powers ActiveRecord.
* Version: 7.0.3
* https://api.rubyonrails.org/classes/Arel.html
*/
private import codeql.ruby.ApiGraphs
private import codeql.ruby.dataflow.FlowSummary
/**
* Provides modeling for Arel, a low level SQL library that powers ActiveRecord.
* Version: 7.0.3
* https://api.rubyonrails.org/classes/Arel.html
*/
module Arel {
/**
* Flow summary for `Arel.sql`. This method wraps a SQL string, marking it as
* safe.
*/
private class SqlSummary extends SummarizedCallable {
SqlSummary() { this = "Arel.sql" }
override MethodCall getACall() {
result = API::getTopLevelMember("Arel").getAMethodCall("sql").asExpr().getExpr()
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[0]" and output = "ReturnValue" and preservesValue = false
}
}
}

View File

@@ -41,7 +41,12 @@ private API::Node graphQlSchema() { result = API::getTopLevelMember("GraphQL").g
private class GraphqlRelayClassicMutationClass extends ClassDeclaration {
GraphqlRelayClassicMutationClass() {
this.getSuperclassExpr() =
graphQlSchema().getMember("RelayClassicMutation").getASubclass*().getAUse().asExpr().getExpr()
graphQlSchema()
.getMember("RelayClassicMutation")
.getASubclass*()
.getAValueReachableFromSource()
.asExpr()
.getExpr()
}
}
@@ -71,7 +76,12 @@ private class GraphqlRelayClassicMutationClass extends ClassDeclaration {
private class GraphqlSchemaResolverClass extends ClassDeclaration {
GraphqlSchemaResolverClass() {
this.getSuperclassExpr() =
graphQlSchema().getMember("Resolver").getASubclass().getAUse().asExpr().getExpr()
graphQlSchema()
.getMember("Resolver")
.getASubclass()
.getAValueReachableFromSource()
.asExpr()
.getExpr()
}
}
@@ -92,7 +102,12 @@ private class GraphqlSchemaResolverClass extends ClassDeclaration {
class GraphqlSchemaObjectClass extends ClassDeclaration {
GraphqlSchemaObjectClass() {
this.getSuperclassExpr() =
graphQlSchema().getMember("Object").getASubclass().getAUse().asExpr().getExpr()
graphQlSchema()
.getMember("Object")
.getASubclass()
.getAValueReachableFromSource()
.asExpr()
.getExpr()
}
/** Gets a `GraphqlFieldDefinitionMethodCall` called in this class. */

View File

@@ -62,9 +62,9 @@ module PosixSpawn {
// is shell interpreted unless there is another argument with a string
// constant value.
override predicate isShellInterpreted(DataFlow::Node arg) {
this.argument(arg) and
not exists(DataFlow::Node otherArg |
otherArg != arg and
this.argument(arg) and
this.argument(otherArg) and
otherArg.asExpr().getConstantValue().isString(_)
)

View File

@@ -63,11 +63,7 @@ private module Config {
)
or
// `Rails.application.config`
this =
API::getTopLevelMember("Rails")
.getReturn("application")
.getReturn("config")
.getAnImmediateUse()
this = API::getTopLevelMember("Rails").getReturn("application").getReturn("config").asSource()
or
// `Rails.application.configure { ... config ... }`
// `Rails::Application.configure { ... config ... }`

View File

@@ -0,0 +1,62 @@
/**
* Modeling for `railties`, which is a gem containing various internals and utilities for the Rails framework.
* https://rubygems.org/gems/railties
*/
private import ruby
private import codeql.ruby.Concepts
private import codeql.ruby.ApiGraphs
private import codeql.ruby.DataFlow
private import codeql.ruby.ast.internal.Module
/**
* Modeling for `railties`.
*/
module Railties {
/**
* A class which `include`s `Rails::Generators::Actions`.
*/
private class GeneratorsActionsContext extends ClassDeclaration {
GeneratorsActionsContext() {
exists(IncludeOrPrependCall i |
i.getEnclosingModule() = this and
i.getArgument(0) =
API::getTopLevelMember("Rails")
.getMember("Generators")
.getMember("Actions")
.getAValueReachableFromSource()
.asExpr()
.getExpr()
)
}
}
/**
* A call to `Rails::Generators::Actions#execute_command`.
* This method concatenates its first and second arguments and executes the result as a shell command.
*/
private class ExecuteCommandCall extends SystemCommandExecution::Range, DataFlow::CallNode {
ExecuteCommandCall() {
this.asExpr().getExpr().getEnclosingModule() instanceof GeneratorsActionsContext and
this.getMethodName() = "execute_command"
}
override DataFlow::Node getAnArgument() { result = this.getArgument([0, 1]) }
override predicate isShellInterpreted(DataFlow::Node arg) { arg = this.getAnArgument() }
}
/**
* A call to a method in `Rails::Generators::Actions` which delegates to `execute_command`.
*/
private class ExecuteCommandWrapperCall extends SystemCommandExecution::Range, DataFlow::CallNode {
ExecuteCommandWrapperCall() {
this.asExpr().getExpr().getEnclosingModule() instanceof GeneratorsActionsContext and
this.getMethodName() = ["rake", "rails_command", "git"]
}
override DataFlow::Node getAnArgument() { result = this.getArgument(0) }
override predicate isShellInterpreted(DataFlow::Node arg) { arg = this.getAnArgument() }
}
}

View File

@@ -4,3 +4,4 @@
import stdlib.Open3
import stdlib.Logger
import stdlib.Pathname

View File

@@ -143,7 +143,7 @@ private DataFlow::LocalSourceNode trackFeature(Feature f, boolean enable, TypeTr
or
// Use of a constant f
enable = true and
result = parseOptionsModule().getMember(f.getConstantName()).getAUse()
result = parseOptionsModule().getMember(f.getConstantName()).getAValueReachableFromSource()
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

View File

@@ -99,7 +99,8 @@ module Hash {
HashNewSummary() { this = "Hash[]" }
final override ElementReference getACall() {
result.getReceiver() = API::getTopLevelMember("Hash").getAUse().asExpr().getExpr() and
result.getReceiver() =
API::getTopLevelMember("Hash").getAValueReachableFromSource().asExpr().getExpr() and
result.getNumberOfArguments() = 1
}
@@ -138,7 +139,8 @@ module Hash {
}
final override ElementReference getACall() {
result.getReceiver() = API::getTopLevelMember("Hash").getAUse().asExpr().getExpr() and
result.getReceiver() =
API::getTopLevelMember("Hash").getAValueReachableFromSource().asExpr().getExpr() and
key = result.getArgument(i - 1).getConstantValue() and
exists(result.getArgument(i))
}

View File

@@ -137,7 +137,7 @@ class IOOrFileWriteMethodCall extends IOOrFileMethodCall {
receiverKind = "class" and
api = ["IO", "File"] and
this = API::getTopLevelMember(api).getAMethodCall(methodName) and
methodName = ["binwrite", "write"] and
methodName = ["binwrite", "write", "atomic_write"] and
dataNode = this.getArgument(1)
or
// e.g. `{IO,File}.new("foo.txt", "a+).puts("hello")`

View File

@@ -25,7 +25,7 @@ private import codeql.ruby.dataflow.RemoteFlowSources
* A remote flow source originating from a CSV source row.
*/
private class RemoteFlowSourceFromCsv extends RemoteFlowSource::Range {
RemoteFlowSourceFromCsv() { this = ModelOutput::getASourceNode("remote").getAnImmediateUse() }
RemoteFlowSourceFromCsv() { this = ModelOutput::getASourceNode("remote").asSource() }
override string getSourceType() { result = "Remote flow (from model)" }
}

View File

@@ -30,8 +30,8 @@ class ExconHttpRequest extends HTTP::Client::Request::Range {
DataFlow::Node connectionUse;
ExconHttpRequest() {
requestUse = requestNode.getAnImmediateUse() and
connectionUse = connectionNode.getAnImmediateUse() and
requestUse = requestNode.asSource() and
connectionUse = connectionNode.asSource() and
connectionNode =
[
// one-off requests
@@ -66,7 +66,8 @@ class ExconHttpRequest extends HTTP::Client::Request::Range {
override predicate disablesCertificateValidation(DataFlow::Node disablingNode) {
// Check for `ssl_verify_peer: false` in the options hash.
exists(DataFlow::Node arg, int i |
i > 0 and arg = connectionNode.getAUse().(DataFlow::CallNode).getArgument(i)
i > 0 and
arg = connectionNode.getAValueReachableFromSource().(DataFlow::CallNode).getArgument(i)
|
argSetsVerifyPeer(arg, false, disablingNode)
)
@@ -79,7 +80,8 @@ class ExconHttpRequest extends HTTP::Client::Request::Range {
disableCall.asExpr().getASuccessor+() = requestUse.asExpr() and
disablingNode = disableCall and
not exists(DataFlow::Node arg, int i |
i > 0 and arg = connectionNode.getAUse().(DataFlow::CallNode).getArgument(i)
i > 0 and
arg = connectionNode.getAValueReachableFromSource().(DataFlow::CallNode).getArgument(i)
|
argSetsVerifyPeer(arg, true, _)
)

View File

@@ -38,8 +38,8 @@ class FaradayHttpRequest extends HTTP::Client::Request::Range {
] and
requestNode =
connectionNode.getReturn(["get", "head", "delete", "post", "put", "patch", "trace"]) and
requestUse = requestNode.getAnImmediateUse() and
connectionUse = connectionNode.getAnImmediateUse() and
requestUse = requestNode.asSource() and
connectionUse = connectionNode.asSource() and
this = requestUse.asExpr().getExpr()
}
@@ -58,7 +58,8 @@ class FaradayHttpRequest extends HTTP::Client::Request::Range {
// or
// `{ ssl: { verify_mode: OpenSSL::SSL::VERIFY_NONE } }`
exists(DataFlow::Node arg, int i |
i > 0 and arg = connectionNode.getAUse().(DataFlow::CallNode).getArgument(i)
i > 0 and
arg = connectionNode.getAValueReachableFromSource().(DataFlow::CallNode).getArgument(i)
|
// Either passed as an individual key:value argument, e.g.:
// Faraday.new(..., ssl: {...})
@@ -132,7 +133,11 @@ private predicate isVerifyModeNonePair(CfgNodes::ExprNodes::PairCfgNode p) {
key.asExpr() = p.getKey() and
value.asExpr() = p.getValue() and
isSymbolLiteral(key, "verify_mode") and
value = API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").getAUse()
value =
API::getTopLevelMember("OpenSSL")
.getMember("SSL")
.getMember("VERIFY_NONE")
.getAValueReachableFromSource()
)
}

View File

@@ -29,7 +29,7 @@ class HttpClientRequest extends HTTP::Client::Request::Range {
API::getTopLevelMember("HTTPClient").getInstance()
] and
requestNode = connectionNode.getReturn(method) and
requestUse = requestNode.getAnImmediateUse() and
requestUse = requestNode.asSource() and
method in [
"get", "head", "delete", "options", "post", "put", "trace", "get_content", "post_content"
] and
@@ -52,10 +52,12 @@ class HttpClientRequest extends HTTP::Client::Request::Range {
// Look for calls to set
// `c.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE`
// on an HTTPClient connection object `c`.
disablingNode =
connectionNode.getReturn("ssl_config").getReturn("verify_mode=").getAnImmediateUse() and
disablingNode = connectionNode.getReturn("ssl_config").getReturn("verify_mode=").asSource() and
disablingNode.(DataFlow::CallNode).getArgument(0) =
API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").getAUse()
API::getTopLevelMember("OpenSSL")
.getMember("SSL")
.getMember("VERIFY_NONE")
.getAValueReachableFromSource()
}
override string getFramework() { result = "HTTPClient" }

View File

@@ -28,7 +28,7 @@ class HttpartyRequest extends HTTP::Client::Request::Range {
DataFlow::CallNode requestUse;
HttpartyRequest() {
requestUse = requestNode.getAnImmediateUse() and
requestUse = requestNode.asSource() and
requestNode =
API::getTopLevelMember("HTTParty")
.getReturn(["get", "head", "delete", "options", "post", "put", "patch"]) and

View File

@@ -25,7 +25,7 @@ class NetHttpRequest extends HTTP::Client::Request::Range {
NetHttpRequest() {
exists(string method |
request = requestNode.getAnImmediateUse() and
request = requestNode.asSource() and
this = request.asExpr().getExpr()
|
// Net::HTTP.get(...)
@@ -59,7 +59,7 @@ class NetHttpRequest extends HTTP::Client::Request::Range {
new = API::getTopLevelMember("Net").getMember("HTTP").getInstance() and
requestNode = new.getReturn(_)
|
result = new.getAnImmediateUse().(DataFlow::CallNode).getArgument(0)
result = new.asSource().(DataFlow::CallNode).getArgument(0)
)
}
@@ -73,7 +73,10 @@ class NetHttpRequest extends HTTP::Client::Request::Range {
// foo.request(...)
exists(DataFlow::CallNode setter |
disablingNode =
API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").getAUse() and
API::getTopLevelMember("OpenSSL")
.getMember("SSL")
.getMember("VERIFY_NONE")
.getAValueReachableFromSource() and
setter.asExpr().getExpr().(SetterMethodCall).getMethodName() = "verify_mode=" and
disablingNode = setter.getArgument(0) and
localFlow(setter.getReceiver(), request.getReceiver())

View File

@@ -28,7 +28,7 @@ class OpenUriRequest extends HTTP::Client::Request::Range {
[API::getTopLevelMember("URI"), API::getTopLevelMember("URI").getReturn("parse")]
.getReturn("open"), API::getTopLevelMember("OpenURI").getReturn("open_uri")
] and
requestUse = requestNode.getAnImmediateUse() and
requestUse = requestNode.asSource() and
this = requestUse.asExpr().getExpr()
}
@@ -110,7 +110,11 @@ private predicate isSslVerifyModeNonePair(CfgNodes::ExprNodes::PairCfgNode p) {
key.asExpr() = p.getKey() and
value.asExpr() = p.getValue() and
isSslVerifyModeLiteral(key) and
value = API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").getAUse()
value =
API::getTopLevelMember("OpenSSL")
.getMember("SSL")
.getMember("VERIFY_NONE")
.getAValueReachableFromSource()
)
}

View File

@@ -22,7 +22,7 @@ class RestClientHttpRequest extends HTTP::Client::Request::Range {
API::Node connectionNode;
RestClientHttpRequest() {
requestUse = requestNode.getAnImmediateUse() and
requestUse = requestNode.asSource() and
this = requestUse.asExpr().getExpr() and
(
connectionNode =
@@ -52,7 +52,8 @@ class RestClientHttpRequest extends HTTP::Client::Request::Range {
// `RestClient::Resource::new` takes an options hash argument, and we're
// looking for `{ verify_ssl: OpenSSL::SSL::VERIFY_NONE }`.
exists(DataFlow::Node arg, int i |
i > 0 and arg = connectionNode.getAUse().(DataFlow::CallNode).getArgument(i)
i > 0 and
arg = connectionNode.getAValueReachableFromSource().(DataFlow::CallNode).getArgument(i)
|
// Either passed as an individual key:value argument, e.g.:
// RestClient::Resource.new(..., verify_ssl: OpenSSL::SSL::VERIFY_NONE)
@@ -79,7 +80,11 @@ private predicate isVerifySslNonePair(CfgNodes::ExprNodes::PairCfgNode p) {
key.asExpr() = p.getKey() and
value.asExpr() = p.getValue() and
isSslVerifyModeLiteral(key) and
value = API::getTopLevelMember("OpenSSL").getMember("SSL").getMember("VERIFY_NONE").getAUse()
value =
API::getTopLevelMember("OpenSSL")
.getMember("SSL")
.getMember("VERIFY_NONE")
.getAValueReachableFromSource()
)
}

View File

@@ -19,7 +19,7 @@ class TyphoeusHttpRequest extends HTTP::Client::Request::Range {
API::Node requestNode;
TyphoeusHttpRequest() {
requestUse = requestNode.getAnImmediateUse() and
requestUse = requestNode.asSource() and
requestNode =
API::getTopLevelMember("Typhoeus")
.getReturn(["get", "head", "delete", "options", "post", "put", "patch"]) and

View File

@@ -0,0 +1,188 @@
/** Modeling of the `Pathname` class from the Ruby standard library. */
private import codeql.ruby.AST
private import codeql.ruby.ApiGraphs
private import codeql.ruby.Concepts
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.FlowSummary
private import codeql.ruby.dataflow.internal.DataFlowDispatch
private import codeql.ruby.frameworks.data.ModelsAsData
/**
* Modeling of the `Pathname` class from the Ruby standard library.
*
* https://docs.ruby-lang.org/en/3.1/Pathname.html
*/
module Pathname {
/**
* An instance of the `Pathname` class. For example, in
*
* ```rb
* pn = Pathname.new "foo.txt'"
* puts pn.read
* ```
*
* there are three `PathnameInstance`s - the call to `Pathname.new`, the
* assignment `pn = ...`, and the read access to `pn` on the second line.
*
* Every `PathnameInstance` is considered to be a `FileNameSource`.
*/
class PathnameInstance extends FileNameSource, DataFlow::Node {
PathnameInstance() { this = pathnameInstance() }
}
private DataFlow::Node pathnameInstance() {
// A call to `Pathname.new`.
result = API::getTopLevelMember("Pathname").getAnInstantiation()
or
// Class methods on `Pathname` that return a new `Pathname`.
result = API::getTopLevelMember("Pathname").getAMethodCall(["getwd", "pwd",])
or
// Instance methods on `Pathname` that return a new `Pathname`.
exists(DataFlow::CallNode c | result = c |
c.getReceiver() = pathnameInstance() and
c.getMethodName() =
[
"+", "/", "basename", "cleanpath", "expand_path", "join", "realpath",
"relative_path_from", "sub", "sub_ext", "to_path"
]
)
or
exists(DataFlow::Node inst |
inst = pathnameInstance() and
inst.(DataFlow::LocalSourceNode).flowsTo(result)
)
}
/** A call where the receiver is a `Pathname`. */
class PathnameCall extends DataFlow::CallNode {
PathnameCall() { this.getReceiver() instanceof PathnameInstance }
}
/**
* A call to `Pathname#open` or `Pathname#opendir`, considered as a
* `FileSystemAccess`.
*/
class PathnameOpen extends FileSystemAccess::Range, PathnameCall {
PathnameOpen() { this.getMethodName() = ["open", "opendir"] }
override DataFlow::Node getAPathArgument() { result = this.getReceiver() }
}
/** A call to `Pathname#read`, considered as a `FileSystemReadAccess`. */
class PathnameRead extends FileSystemReadAccess::Range, PathnameCall {
PathnameRead() { this.getMethodName() = "read" }
// The path is the receiver (the `Pathname` object).
override DataFlow::Node getAPathArgument() { result = this.getReceiver() }
// The read data is the return value of the call.
override DataFlow::Node getADataNode() { result = this }
}
/** A call to `Pathname#write`, considered as a `FileSystemWriteAccess`. */
class PathnameWrite extends FileSystemWriteAccess::Range, PathnameCall {
PathnameWrite() { this.getMethodName() = "write" }
// The path is the receiver (the `Pathname` object).
override DataFlow::Node getAPathArgument() { result = this.getReceiver() }
// The data to write is the 0th argument.
override DataFlow::Node getADataNode() { result = this.getArgument(0) }
}
/** A call to `Pathname#to_s`, considered as a `FileNameSource`. */
class PathnameToSFilenameSource extends FileNameSource, PathnameCall {
PathnameToSFilenameSource() { this.getMethodName() = "to_s" }
}
private class PathnamePermissionModification extends FileSystemPermissionModification::Range,
PathnameCall {
private DataFlow::Node permissionArg;
PathnamePermissionModification() {
exists(string methodName | this.getMethodName() = methodName |
methodName = ["chmod", "mkdir"] and permissionArg = this.getArgument(0)
or
methodName = "mkpath" and permissionArg = this.getKeywordArgument("mode")
or
methodName = "open" and permissionArg = this.getArgument(1)
// TODO: defaults for optional args? This may depend on the umask
)
}
override DataFlow::Node getAPermissionNode() { result = permissionArg }
}
/**
* Type summaries for the `Pathname` class, i.e. method calls that produce new
* `Pathname` instances.
*/
private class PathnameTypeSummary extends ModelInput::TypeModelCsv {
override predicate row(string row) {
// package1;type1;package2;type2;path
row =
[
// Pathname.new : Pathname
";Pathname;;;Member[Pathname].Instance",
// Pathname#+(path) : Pathname
";Pathname;;Pathname;Method[+].ReturnValue",
// Pathname#/(path) : Pathname
";Pathname;;Pathname;Method[/].ReturnValue",
// Pathname#basename(path) : Pathname
";Pathname;;Pathname;Method[basename].ReturnValue",
// Pathname#cleanpath(path) : Pathname
";Pathname;;Pathname;Method[cleanpath].ReturnValue",
// Pathname#expand_path(path) : Pathname
";Pathname;;Pathname;Method[expand_path].ReturnValue",
// Pathname#join(path) : Pathname
";Pathname;;Pathname;Method[join].ReturnValue",
// Pathname#realpath(path) : Pathname
";Pathname;;Pathname;Method[realpath].ReturnValue",
// Pathname#relative_path_from(path) : Pathname
";Pathname;;Pathname;Method[relative_path_from].ReturnValue",
// Pathname#sub(path) : Pathname
";Pathname;;Pathname;Method[sub].ReturnValue",
// Pathname#sub_ext(path) : Pathname
";Pathname;;Pathname;Method[sub_ext].ReturnValue",
// Pathname#to_path(path) : Pathname
";Pathname;;Pathname;Method[to_path].ReturnValue",
]
}
}
/** Taint flow summaries for the `Pathname` class. */
private class PathnameTaintSummary extends ModelInput::SummaryModelCsv {
override predicate row(string row) {
row =
[
// Pathname.new(path)
";;Member[Pathname].Method[new];Argument[0];ReturnValue;taint",
// Pathname#dirname
";Pathname;Method[dirname];Argument[self];ReturnValue;taint",
// Pathname#each_filename
";Pathname;Method[each_filename];Argument[self];Argument[block].Parameter[0];taint",
// Pathname#expand_path
";Pathname;Method[expand_path];Argument[self];ReturnValue;taint",
// Pathname#join
";Pathname;Method[join];Argument[self,any];ReturnValue;taint",
// Pathname#parent
";Pathname;Method[parent];Argument[self];ReturnValue;taint",
// Pathname#realpath
";Pathname;Method[realpath];Argument[self];ReturnValue;taint",
// Pathname#relative_path_from
";Pathname;Method[relative_path_from];Argument[self];ReturnValue;taint",
// Pathname#to_path
";Pathname;Method[to_path];Argument[self];ReturnValue;taint",
// Pathname#basename
";Pathname;Method[basename];Argument[self];ReturnValue;taint",
// Pathname#cleanpath
";Pathname;Method[cleanpath];Argument[self];ReturnValue;taint",
// Pathname#sub
";Pathname;Method[sub];Argument[self];ReturnValue;taint",
// Pathname#sub_ext
";Pathname;Method[sub_ext];Argument[self];ReturnValue;taint",
]
}
}
}

View File

@@ -9,6 +9,7 @@
private import AST
private import codeql.ruby.Regexp as RE
private import codeql.ruby.ast.internal.Synthesis
private import ast.internal.AST
/**
* The query can extend this class to control which nodes are printed.
@@ -35,6 +36,8 @@ private predicate shouldPrintAstEdge(AstNode parent, string edgeName, AstNode ch
any(PrintAstConfiguration config).shouldPrintAstEdge(parent, edgeName, child)
}
private int nonSynthIndex() { result = min([-1, any(int i | exists(getSynthChild(_, i)))]) - 1 }
newtype TPrintNode =
TPrintRegularAstNode(AstNode n) { shouldPrintNode(n) } or
TPrintRegExpNode(RE::RegExpTerm term) {
@@ -112,13 +115,22 @@ class PrintRegularAstNode extends PrintAstNode, TPrintRegularAstNode {
)
}
private int getSynthAstNodeIndex() {
not astNode.isSynthesized() and result = nonSynthIndex()
or
astNode = getSynthChild(astNode.getParent(), result)
}
override int getOrder() {
this =
rank[result](PrintRegularAstNode p, Location l, File f |
l = p.getLocation() and
f = l.getFile()
|
p order by f.getBaseName(), f.getAbsolutePath(), l.getStartLine(), l.getStartColumn()
p
order by
f.getBaseName(), f.getAbsolutePath(), l.getStartLine(), l.getStartColumn(),
l.getEndLine(), l.getEndColumn(), p.getSynthAstNodeIndex()
)
}

View File

@@ -71,7 +71,9 @@ module UrlRedirect {
// We exclude any handlers with names containing create/update/destroy, as these are not likely to handle GET requests.
not exists(method.(ActionControllerActionMethod).getARoute()) and
not method.getName().regexpMatch(".*(create|update|destroy).*")
)
) and
// If this redirect is an ActionController method call, it is only vulnerable if it allows external redirects.
forall(RedirectToCall c | c = e.asExpr().getExpr() | c.allowsExternalRedirect())
)
}
}

View File

@@ -1,5 +1,5 @@
name: codeql/ruby-all
version: 0.3.0-dev
version: 0.3.3-dev
groups: ruby
extractor: ruby
dbscheme: ruby.dbscheme

View File

@@ -1,3 +1,26 @@
## 0.3.1
### New Queries
* Added a new experimental query, `rb/manually-checking-http-verb`, to detect cases when the HTTP verb for an incoming request is checked and then used as part of control flow.
* Added a new experimental query, `rb/weak-params`, to detect cases when the rails strong parameters pattern isn't followed and values flow into persistent store writes.
## 0.3.0
### Breaking Changes
* Contextual queries and the query libraries they depend on have been moved to the `codeql/ruby-all` package.
## 0.2.0
### New Queries
* Added a new query, `rb/improper-memoization`. The query finds cases where the parameter of a memoization method is not used in the memoization key.
### Minor Analysis Improvements
* The query "Use of a broken or weak cryptographic algorithm" (`rb/weak-cryptographic-algorithm`) now reports if a cryptographic operation is potentially insecure due to use of a weak block mode.
## 0.1.4
## 0.1.3

View File

@@ -1,4 +0,0 @@
---
category: minorAnalysis
---
* The query "Use of a broken or weak cryptographic algorithm" (`rb/weak-cryptographic-algorithm`) now report if a cryptographic operation is potentially insecure due to use of a weak block mode.

View File

@@ -1,4 +0,0 @@
---
category: newQuery
---
* Added a new query, `rb/improper-memoization`. The query finds cases where the parameter of a memoization method is not used in the memoization key.

View File

@@ -0,0 +1,9 @@
## 0.2.0
### New Queries
* Added a new query, `rb/improper-memoization`. The query finds cases where the parameter of a memoization method is not used in the memoization key.
### Minor Analysis Improvements
* The query "Use of a broken or weak cryptographic algorithm" (`rb/weak-cryptographic-algorithm`) now reports if a cryptographic operation is potentially insecure due to use of a weak block mode.

View File

@@ -0,0 +1,5 @@
## 0.3.0
### Breaking Changes
* Contextual queries and the query libraries they depend on have been moved to the `codeql/ruby-all` package.

View File

@@ -0,0 +1,6 @@
## 0.3.1
### New Queries
* Added a new experimental query, `rb/manually-checking-http-verb`, to detect cases when the HTTP verb for an incoming request is checked and then used as part of control flow.
* Added a new experimental query, `rb/weak-params`, to detect cases when the rails strong parameters pattern isn't followed and values flow into persistent store writes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.1.4
lastReleaseVersion: 0.3.1

View File

@@ -0,0 +1,23 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Manually checking the HTTP request verb inside of a controller method can lead to
CSRF bypass if GET or HEAD requests are handled improperly.
</p>
</overview>
<recommendation>
<p>
It is better to use different controller methods for each resource/http verb combination
and configure the Rails routes in your application to call them accordingly.
</p>
</recommendation>
<references>
<li>
See https://guides.rubyonrails.org/routing.html for more information.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,96 @@
/**
* @name Manually checking http verb instead of using built in rails routes and protections
* @description Manually checking HTTP verbs is an indication that multiple requests are routed to the same controller action. This could lead to bypassing necessary authorization methods and other protections, like CSRF protection. Prefer using different controller actions for each HTTP method and relying Rails routing to handle mappting resources and verbs to specific methods.
* @kind path-problem
* @problem.severity error
* @security-severity 5.0
* @precision low
* @id rb/manually-checking-http-verb
* @tags security
*/
import ruby
import codeql.ruby.DataFlow
import codeql.ruby.controlflow.CfgNodes
import codeql.ruby.frameworks.ActionController
import codeql.ruby.TaintTracking
import DataFlow::PathGraph
// any `request` calls in an action method
class Request extends DataFlow::CallNode {
Request() {
this.getMethodName() = "request" and
this.asExpr().getExpr().getEnclosingMethod() instanceof ActionControllerActionMethod
}
}
// `request.env`
class RequestEnvMethod extends DataFlow::CallNode {
RequestEnvMethod() {
this.getMethodName() = "env" and
any(Request r).flowsTo(this.getReceiver())
}
}
// `request.request_method`
class RequestRequestMethod extends DataFlow::CallNode {
RequestRequestMethod() {
this.getMethodName() = "request_method" and
any(Request r).flowsTo(this.getReceiver())
}
}
// `request.method`
class RequestMethod extends DataFlow::CallNode {
RequestMethod() {
this.getMethodName() = "method" and
any(Request r).flowsTo(this.getReceiver())
}
}
// `request.raw_request_method`
class RequestRawRequestMethod extends DataFlow::CallNode {
RequestRawRequestMethod() {
this.getMethodName() = "raw_request_method" and
any(Request r).flowsTo(this.getReceiver())
}
}
// `request.request_method_symbol`
class RequestRequestMethodSymbol extends DataFlow::CallNode {
RequestRequestMethodSymbol() {
this.getMethodName() = "request_method_symbol" and
any(Request r).flowsTo(this.getReceiver())
}
}
// `request.get?`
class RequestGet extends DataFlow::CallNode {
RequestGet() {
this.getMethodName() = "get?" and
any(Request r).flowsTo(this.getReceiver())
}
}
class HttpVerbConfig extends TaintTracking::Configuration {
HttpVerbConfig() { this = "HttpVerbConfig" }
override predicate isSource(DataFlow::Node source) {
source instanceof RequestMethod or
source instanceof RequestRequestMethod or
source instanceof RequestEnvMethod or
source instanceof RequestRawRequestMethod or
source instanceof RequestRequestMethodSymbol or
source instanceof RequestGet
}
override predicate isSink(DataFlow::Node sink) {
exists(ExprNodes::ConditionalExprCfgNode c | c.getCondition() = sink.asExpr()) or
exists(ExprNodes::CaseExprCfgNode c | c.getValue() = sink.asExpr())
}
}
from HttpVerbConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink,
"Manually checking HTTP verbs is an indication that multiple requests are routed to the same controller action. This could lead to bypassing necessary authorization methods and other protections, like CSRF protection. Prefer using different controller actions for each HTTP method and relying Rails routing to handle mappting resources and verbs to specific methods."

View File

@@ -0,0 +1,28 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Directly checking request parameters without following a strong params
pattern can lead to unintentional avenues for injection attacks.
</p>
</overview>
<recommendation>
<p>
Instead of manually checking parameters from the `param` object, it is
recommended that you follow the strong parameters pattern established in
Rails: https://api.rubyonrails.org/classes/ActionController/StrongParameters.html
</p>
<p>
In the strong parameters pattern, you are able to specify required and allowed
parameters for each action called by your controller methods. This acts as an
additional layer of data validation before being passed along to other areas
of your application, such as the model.
</p>
</recommendation>
<references>
</references>
</qhelp>

View File

@@ -0,0 +1,61 @@
/**
* @name Weak or direct parameter references are used
* @description Directly checking request parameters without following a strong params pattern can lead to unintentional avenues for injection attacks.
* @kind path-problem
* @problem.severity error
* @security-severity 5.0
* @precision medium
* @id rb/weak-params
* @tags security
*/
import ruby
import codeql.ruby.Concepts
import codeql.ruby.DataFlow
import codeql.ruby.TaintTracking
import codeql.ruby.frameworks.ActionController
import DataFlow::PathGraph
/**
* A call to `request` in an ActionController controller class.
* This probably refers to the incoming HTTP request object.
*/
class ActionControllerRequest extends DataFlow::Node {
ActionControllerRequest() {
exists(DataFlow::CallNode c |
c.asExpr().getExpr().getEnclosingModule() instanceof ActionControllerControllerClass and
c.getMethodName() = "request"
|
c.flowsTo(this)
)
}
}
/**
* A direct parameters reference that happens inside a controller class.
*/
class WeakParams extends DataFlow::CallNode {
WeakParams() {
this.getReceiver() instanceof ActionControllerRequest and
this.getMethodName() =
["path_parameters", "query_parameters", "request_parameters", "GET", "POST"]
}
}
/**
* A Taint tracking config where the source is a weak params access in a controller and the sink
* is a method call of a model class
*/
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "WeakParamsConfiguration" }
override predicate isSource(DataFlow::Node node) { node instanceof WeakParams }
// the sink is an instance of a Model class that receives a method call
override predicate isSink(DataFlow::Node node) { node = any(PersistentWriteAccess a).getValue() }
}
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink,
"By exposing all keys in request parameters or by blindy accessing them, unintended parameters could be used and lead to mass-assignment or have other unexpected side-effects. It is safer to follow the 'strong parameters' pattern in Rails, which is outlined here: https://api.rubyonrails.org/classes/ActionController/StrongParameters.html"

View File

@@ -1,5 +1,5 @@
name: codeql/ruby-queries
version: 0.2.0-dev
version: 0.3.2-dev
groups:
- ruby
- queries

View File

@@ -53,7 +53,7 @@ predicate matchesBeginningOfString(RegExpTerm term) {
}
/**
* Holds if the given sequence contains top-level domain preceded by a dot, such as `.com`,
* Holds if the given sequence `seq` contains top-level domain preceded by a dot, such as `.com`,
* excluding cases where this is at the very beginning of the regexp.
*
* `i` is bound to the index of the last child in the top-level domain part.

View File

@@ -1556,6 +1556,35 @@ constants/constants.rb:
# 73| getAnOperand/getLeftOperand: [ClassVariableAccess] @@fourty_six
# 73| getAnOperand/getRightOperand: [ConstantReadAccess] FOURTY_SIX
# 73| getScopeExpr: [ConstantReadAccess] Mod3
# 78| getStmt: [AssignExpr] ... = ...
# 78| getAnOperand/getLeftOperand: [LocalVariableAccess] a
# 78| getAnOperand/getRightOperand: [ArrayLiteral] [...]
# 78| getElement: [IntegerLiteral] 1
# 78| getElement: [IntegerLiteral] 2
# 78| getElement: [IntegerLiteral] 3
# 79| getStmt: [AssignExpr] ... = ...
# 79| getAnOperand/getLeftOperand: [ConstantAssignment] A
# 79| getAnOperand/getRightOperand: [ArrayLiteral] [...]
# 79| getElement: [IntegerLiteral] 1
# 79| getElement: [IntegerLiteral] 2
# 79| getElement: [IntegerLiteral] 3
# 80| getStmt: [AssignExpr] ... = ...
# 80| getAnOperand/getLeftOperand: [ConstantAssignment] B
# 80| getAnOperand/getRightOperand: [LocalVariableAccess] a
# 81| getStmt: [AssignExpr] ... = ...
# 81| getAnOperand/getLeftOperand: [ConstantAssignment] C
# 81| getAnOperand/getRightOperand: [ConstantReadAccess] A
# 82| getStmt: [AssignExpr] ... = ...
# 82| getAnOperand/getLeftOperand: [LocalVariableAccess] b
# 82| getAnOperand/getRightOperand: [ConstantReadAccess] B
# 84| getStmt: [IfExpr] if ...
# 84| getCondition: [MethodCall] call to condition
# 84| getReceiver: [SelfVariableAccess] self
# 84| getBranch/getThen: [StmtSequence] then ...
# 85| getStmt: [AssignExpr] ... = ...
# 85| getAnOperand/getLeftOperand: [LocalVariableAccess] c
# 85| getAnOperand/getRightOperand: [LocalVariableAccess] b
# 87| getStmt: [LocalVariableAccess] c
escape_sequences/escapes.rb:
# 1| [Toplevel] escapes.rb
# 6| getStmt: [StringLiteral] "\'"
@@ -1733,6 +1762,12 @@ escape_sequences/escapes.rb:
# 93| getStmt: [SymbolLiteral] :"\C-?"
# 93| getComponent: [StringEscapeSequenceComponent] \C
# 93| getComponent: [StringTextComponent] -?
misc/iso-8859-15.rb:
# 1| [Toplevel] iso-8859-15.rb
# 4| getStmt: [MethodCall] call to print
# 4| getReceiver: [SelfVariableAccess] self
# 4| getArgument: [StringLiteral] "EUR = €"
# 4| getComponent: [StringTextComponent] EUR = €
literals/literals.rb:
# 1| [Toplevel] literals.rb
# 2| getStmt: [NilLiteral] nil

View File

@@ -86,10 +86,10 @@ calls/calls.rb:
# 316| getStmt: [SetterMethodCall] call to foo=
# 316| getReceiver: [SelfVariableAccess] self
# 316| getArgument: [AssignExpr] ... = ...
# 316| getAnOperand/getRightOperand: [MethodCall] call to []
# 316| getArgument: [IntegerLiteral] 0
# 316| getReceiver: [LocalVariableAccess] __synth__0
# 316| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1
# 316| getAnOperand/getRightOperand: [MethodCall] call to []
# 316| getReceiver: [LocalVariableAccess] __synth__0
# 316| getArgument: [IntegerLiteral] 0
# 316| getStmt: [LocalVariableAccess] __synth__0__1
# 316| getStmt: [AssignExpr] ... = ...
# 316| getAnOperand/getLeftOperand: [MethodCall] call to bar
@@ -97,12 +97,12 @@ calls/calls.rb:
# 316| getStmt: [SetterMethodCall] call to bar=
# 316| getReceiver: [SelfVariableAccess] self
# 316| getArgument: [AssignExpr] ... = ...
# 316| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1
# 316| getAnOperand/getRightOperand: [MethodCall] call to []
# 316| getReceiver: [LocalVariableAccess] __synth__0
# 316| getArgument: [RangeLiteral] _ .. _
# 316| getBegin: [IntegerLiteral] 1
# 316| getEnd: [IntegerLiteral] -2
# 316| getReceiver: [LocalVariableAccess] __synth__0
# 316| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1
# 316| getStmt: [LocalVariableAccess] __synth__0__1
# 316| getStmt: [AssignExpr] ... = ...
# 316| getAnOperand/getLeftOperand: [ElementReference] ...[...]
@@ -111,13 +111,14 @@ calls/calls.rb:
# 316| getReceiver: [MethodCall] call to foo
# 316| getReceiver: [SelfVariableAccess] self
# 316| getArgument: [AssignExpr] ... = ...
# 316| getAnOperand/getRightOperand: [MethodCall] call to []
# 316| getArgument: [IntegerLiteral] -1
# 316| getReceiver: [LocalVariableAccess] __synth__0
# 316| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1
# 316| getAnOperand/getRightOperand: [MethodCall] call to []
# 316| getReceiver: [LocalVariableAccess] __synth__0
# 316| getArgument: [IntegerLiteral] -1
# 316| getArgument: [IntegerLiteral] 4
# 316| getStmt: [LocalVariableAccess] __synth__0__1
# 316| getStmt: [AssignExpr] ... = ...
# 316| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0
# 316| getAnOperand/getRightOperand: [SplatExpr] * ...
# 316| getAnOperand/getOperand/getReceiver: [ArrayLiteral] [...]
# 316| getDesugared: [MethodCall] call to []
@@ -126,14 +127,13 @@ calls/calls.rb:
# 316| getArgument: [IntegerLiteral] 2
# 316| getArgument: [IntegerLiteral] 3
# 316| getArgument: [IntegerLiteral] 4
# 316| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0
# 317| [AssignExpr] ... = ...
# 317| getDesugared: [StmtSequence] ...
# 317| getStmt: [AssignExpr] ... = ...
# 317| getAnOperand/getLeftOperand: [LocalVariableAccess] a
# 317| getAnOperand/getRightOperand: [MethodCall] call to []
# 317| getArgument: [IntegerLiteral] 0
# 317| getReceiver: [LocalVariableAccess] __synth__0
# 317| getArgument: [IntegerLiteral] 0
# 317| getStmt: [AssignExpr] ... = ...
# 317| getAnOperand/getLeftOperand: [ElementReference] ...[...]
# 317| getDesugared: [StmtSequence] ...
@@ -141,15 +141,16 @@ calls/calls.rb:
# 317| getReceiver: [MethodCall] call to foo
# 317| getReceiver: [SelfVariableAccess] self
# 317| getArgument: [AssignExpr] ... = ...
# 317| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1
# 317| getAnOperand/getRightOperand: [MethodCall] call to []
# 317| getReceiver: [LocalVariableAccess] __synth__0
# 317| getArgument: [RangeLiteral] _ .. _
# 317| getBegin: [IntegerLiteral] 1
# 317| getEnd: [IntegerLiteral] -1
# 317| getReceiver: [LocalVariableAccess] __synth__0
# 317| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1
# 317| getArgument: [IntegerLiteral] 5
# 317| getStmt: [LocalVariableAccess] __synth__0__1
# 317| getStmt: [AssignExpr] ... = ...
# 317| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0
# 317| getAnOperand/getRightOperand: [SplatExpr] * ...
# 317| getAnOperand/getOperand/getReceiver: [ArrayLiteral] [...]
# 317| getDesugared: [MethodCall] call to []
@@ -157,7 +158,6 @@ calls/calls.rb:
# 317| getArgument: [IntegerLiteral] 1
# 317| getArgument: [IntegerLiteral] 2
# 317| getArgument: [IntegerLiteral] 3
# 317| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0
# 318| [AssignAddExpr] ... += ...
# 318| getDesugared: [StmtSequence] ...
# 318| getStmt: [AssignExpr] ... = ...
@@ -167,11 +167,11 @@ calls/calls.rb:
# 318| getReceiver: [LocalVariableAccess] __synth__0
# 318| getArgument: [LocalVariableAccess] __synth__1
# 318| getStmt: [AssignExpr] ... = ...
# 318| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__1
# 318| getAnOperand/getRightOperand: [AddExpr] ... + ...
# 318| getAnOperand/getLeftOperand/getReceiver: [MethodCall] call to count
# 318| getReceiver: [LocalVariableAccess] __synth__0
# 318| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1
# 318| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__1
# 318| getStmt: [LocalVariableAccess] __synth__1
# 319| [AssignAddExpr] ... += ...
# 319| getDesugared: [StmtSequence] ...
@@ -187,12 +187,12 @@ calls/calls.rb:
# 319| getAnOperand/getRightOperand: [IntegerLiteral] 0
# 319| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__1
# 319| getStmt: [AssignExpr] ... = ...
# 319| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__2
# 319| getAnOperand/getRightOperand: [AddExpr] ... + ...
# 319| getAnOperand/getLeftOperand/getReceiver: [MethodCall] call to []
# 319| getReceiver: [LocalVariableAccess] __synth__0
# 319| getArgument: [LocalVariableAccess] __synth__1
# 319| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1
# 319| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__2
# 319| getStmt: [LocalVariableAccess] __synth__2
# 320| [AssignMulExpr] ... *= ...
# 320| getDesugared: [StmtSequence] ...
@@ -223,6 +223,7 @@ calls/calls.rb:
# 320| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 1
# 320| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__3
# 320| getStmt: [AssignExpr] ... = ...
# 320| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__4
# 320| getAnOperand/getRightOperand: [MulExpr] ... * ...
# 320| getAnOperand/getLeftOperand/getReceiver: [MethodCall] call to []
# 320| getReceiver: [LocalVariableAccess] __synth__0
@@ -230,7 +231,6 @@ calls/calls.rb:
# 320| getArgument: [LocalVariableAccess] __synth__2
# 320| getArgument: [LocalVariableAccess] __synth__3
# 320| getAnOperand/getArgument/getRightOperand: [IntegerLiteral] 2
# 320| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__4
# 320| getStmt: [LocalVariableAccess] __synth__4
# 340| [ForExpr] for ... in ...
# 340| getDesugared: [MethodCall] call to each
@@ -240,24 +240,24 @@ calls/calls.rb:
# 340| getStmt: [AssignExpr] ... = ...
# 340| getDesugared: [StmtSequence] ...
# 340| getStmt: [AssignExpr] ... = ...
# 340| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1
# 340| getAnOperand/getRightOperand: [SplatExpr] * ...
# 340| getAnOperand/getOperand/getReceiver: [LocalVariableAccess] __synth__0__1
# 340| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1
# 340| getStmt: [AssignExpr] ... = ...
# 340| getAnOperand/getLeftOperand: [LocalVariableAccess] x
# 340| getAnOperand/getRightOperand: [MethodCall] call to []
# 340| getArgument: [IntegerLiteral] 0
# 340| getReceiver: [LocalVariableAccess] __synth__0__1
# 340| getArgument: [IntegerLiteral] 0
# 340| getStmt: [AssignExpr] ... = ...
# 340| getAnOperand/getLeftOperand: [LocalVariableAccess] y
# 340| getAnOperand/getRightOperand: [MethodCall] call to []
# 340| getArgument: [IntegerLiteral] 1
# 340| getReceiver: [LocalVariableAccess] __synth__0__1
# 340| getArgument: [IntegerLiteral] 1
# 340| getStmt: [AssignExpr] ... = ...
# 340| getAnOperand/getLeftOperand: [LocalVariableAccess] z
# 340| getAnOperand/getRightOperand: [MethodCall] call to []
# 340| getArgument: [IntegerLiteral] 2
# 340| getReceiver: [LocalVariableAccess] __synth__0__1
# 340| getArgument: [IntegerLiteral] 2
# 340| getAnOperand/getLeftOperand: [DestructuredLhsExpr] (..., ...)
# 341| getStmt: [MethodCall] call to foo
# 341| getReceiver: [SelfVariableAccess] self
@@ -286,9 +286,9 @@ calls/calls.rb:
# 362| getReceiver: [SelfVariableAccess] self
# 362| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1
# 362| getStmt: [IfExpr] if ...
# 362| getBranch/getThen: [NilLiteral] nil
# 362| getBranch/getElse: [MethodCall] call to empty?
# 362| getReceiver: [LocalVariableAccess] __synth__0__1
# 362| getBranch/getThen: [NilLiteral] nil
# 362| getCondition: [MethodCall] call to ==
# 362| getArgument: [LocalVariableAccess] __synth__0__1
# 362| getReceiver: [NilLiteral] nil
@@ -299,6 +299,7 @@ calls/calls.rb:
# 364| getReceiver: [SelfVariableAccess] self
# 364| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1
# 364| getStmt: [IfExpr] if ...
# 364| getBranch/getThen: [NilLiteral] nil
# 364| getBranch/getElse: [MethodCall] call to bar
# 364| getReceiver: [LocalVariableAccess] __synth__0__1
# 364| getArgument: [IntegerLiteral] 1
@@ -307,7 +308,6 @@ calls/calls.rb:
# 364| getParameter: [SimpleParameter] x
# 364| getDefiningAccess: [LocalVariableAccess] x
# 364| getStmt: [LocalVariableAccess] x
# 364| getBranch/getThen: [NilLiteral] nil
# 364| getCondition: [MethodCall] call to ==
# 364| getArgument: [LocalVariableAccess] __synth__0__1
# 364| getReceiver: [NilLiteral] nil
@@ -336,6 +336,18 @@ constants/constants.rb:
# 20| getComponent: [StringTextComponent] Chuck
# 20| getArgument: [StringLiteral] "Dave"
# 20| getComponent: [StringTextComponent] Dave
# 78| [ArrayLiteral] [...]
# 78| getDesugared: [MethodCall] call to []
# 78| getReceiver: [ConstantReadAccess] Array
# 78| getArgument: [IntegerLiteral] 1
# 78| getArgument: [IntegerLiteral] 2
# 78| getArgument: [IntegerLiteral] 3
# 79| [ArrayLiteral] [...]
# 79| getDesugared: [MethodCall] call to []
# 79| getReceiver: [ConstantReadAccess] Array
# 79| getArgument: [IntegerLiteral] 1
# 79| getArgument: [IntegerLiteral] 2
# 79| getArgument: [IntegerLiteral] 3
escape_sequences/escapes.rb:
# 58| [ArrayLiteral] %w(...)
# 58| getDesugared: [MethodCall] call to []
@@ -596,19 +608,19 @@ control/loops.rb:
# 22| getStmt: [AssignExpr] ... = ...
# 22| getDesugared: [StmtSequence] ...
# 22| getStmt: [AssignExpr] ... = ...
# 22| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1
# 22| getAnOperand/getRightOperand: [SplatExpr] * ...
# 22| getAnOperand/getOperand/getReceiver: [LocalVariableAccess] __synth__0__1
# 22| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1
# 22| getStmt: [AssignExpr] ... = ...
# 22| getAnOperand/getLeftOperand: [LocalVariableAccess] key
# 22| getAnOperand/getRightOperand: [MethodCall] call to []
# 22| getArgument: [IntegerLiteral] 0
# 22| getReceiver: [LocalVariableAccess] __synth__0__1
# 22| getArgument: [IntegerLiteral] 0
# 22| getStmt: [AssignExpr] ... = ...
# 22| getAnOperand/getLeftOperand: [LocalVariableAccess] value
# 22| getAnOperand/getRightOperand: [MethodCall] call to []
# 22| getArgument: [IntegerLiteral] 1
# 22| getReceiver: [LocalVariableAccess] __synth__0__1
# 22| getArgument: [IntegerLiteral] 1
# 22| getAnOperand/getLeftOperand: [DestructuredLhsExpr] (..., ...)
# 23| getStmt: [AssignAddExpr] ... += ...
# 23| getDesugared: [AssignExpr] ... = ...
@@ -641,19 +653,19 @@ control/loops.rb:
# 28| getStmt: [AssignExpr] ... = ...
# 28| getDesugared: [StmtSequence] ...
# 28| getStmt: [AssignExpr] ... = ...
# 28| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1
# 28| getAnOperand/getRightOperand: [SplatExpr] * ...
# 28| getAnOperand/getOperand/getReceiver: [LocalVariableAccess] __synth__0__1
# 28| getAnOperand/getLeftOperand: [LocalVariableAccess] __synth__0__1
# 28| getStmt: [AssignExpr] ... = ...
# 28| getAnOperand/getLeftOperand: [LocalVariableAccess] key
# 28| getAnOperand/getRightOperand: [MethodCall] call to []
# 28| getArgument: [IntegerLiteral] 0
# 28| getReceiver: [LocalVariableAccess] __synth__0__1
# 28| getArgument: [IntegerLiteral] 0
# 28| getStmt: [AssignExpr] ... = ...
# 28| getAnOperand/getLeftOperand: [LocalVariableAccess] value
# 28| getAnOperand/getRightOperand: [MethodCall] call to []
# 28| getArgument: [IntegerLiteral] 1
# 28| getReceiver: [LocalVariableAccess] __synth__0__1
# 28| getArgument: [IntegerLiteral] 1
# 28| getAnOperand/getLeftOperand: [DestructuredLhsExpr] (..., ...)
# 29| getStmt: [AssignAddExpr] ... += ...
# 29| getDesugared: [AssignExpr] ... = ...

View File

@@ -1656,10 +1656,56 @@ constants/constants.rb:
# 73| 1: [ReservedWord] ::
# 73| 2: [Constant] FOURTY_SIX
# 74| 5: [ReservedWord] end
# 78| 13: [Assignment] Assignment
# 78| 0: [Identifier] a
# 78| 1: [ReservedWord] =
# 78| 2: [Array] Array
# 78| 0: [ReservedWord] [
# 78| 1: [Integer] 1
# 78| 2: [ReservedWord] ,
# 78| 3: [Integer] 2
# 78| 4: [ReservedWord] ,
# 78| 5: [Integer] 3
# 78| 6: [ReservedWord] ]
# 79| 14: [Assignment] Assignment
# 79| 0: [Constant] A
# 79| 1: [ReservedWord] =
# 79| 2: [Array] Array
# 79| 0: [ReservedWord] [
# 79| 1: [Integer] 1
# 79| 2: [ReservedWord] ,
# 79| 3: [Integer] 2
# 79| 4: [ReservedWord] ,
# 79| 5: [Integer] 3
# 79| 6: [ReservedWord] ]
# 80| 15: [Assignment] Assignment
# 80| 0: [Constant] B
# 80| 1: [ReservedWord] =
# 80| 2: [Identifier] a
# 81| 16: [Assignment] Assignment
# 81| 0: [Constant] C
# 81| 1: [ReservedWord] =
# 81| 2: [Constant] A
# 82| 17: [Assignment] Assignment
# 82| 0: [Identifier] b
# 82| 1: [ReservedWord] =
# 82| 2: [Constant] B
# 84| 18: [If] If
# 84| 0: [ReservedWord] if
# 84| 1: [Identifier] condition
# 84| 2: [Then] Then
# 85| 0: [Assignment] Assignment
# 85| 0: [Identifier] c
# 85| 1: [ReservedWord] =
# 85| 2: [Identifier] b
# 86| 3: [ReservedWord] end
# 87| 19: [Identifier] c
# 26| [Comment] # A call to Kernel::Array; despite beginning with an upper-case character,
# 27| [Comment] # we don't consider this to be a constant access.
# 55| [Comment] # refers to ::ModuleA::FOURTY_FOUR
# 57| [Comment] # refers to ::ModuleA::ModuleB::ClassB::FOURTY_FOUR
# 76| [Comment] # Array constants
# 87| [Comment] # not recognised
control/cases.rb:
# 1| [Program] Program
# 2| 0: [Assignment] Assignment
@@ -4558,6 +4604,17 @@ literals/literals.rb:
# 193| cat file.txt
# 193|
# 195| 1: [HeredocEnd] SCRIPT
misc/iso-8859-15.rb:
# 1| [Program] Program
# 4| 0: [Call] Call
# 4| 0: [Identifier] print
# 4| 1: [ArgumentList] ArgumentList
# 4| 0: [String] String
# 4| 0: [ReservedWord] "
# 4| 1: [StringContent] EUR = €
# 4| 2: [ReservedWord] "
# 1| [Comment] #! /usr/bin/ruby
# 2| [Comment] # coding: iso-8859-15
misc/misc.erb:
# 2| [Program] Program
# 2| 0: [Call] Call

View File

@@ -109,6 +109,12 @@ exprValue
| constants/constants.rb:63:19:63:20 | 45 | 45 | int |
| constants/constants.rb:65:19:65:35 | FOURTY_FIVE | 45 | int |
| constants/constants.rb:71:18:71:19 | 46 | 46 | int |
| constants/constants.rb:78:6:78:6 | 1 | 1 | int |
| constants/constants.rb:78:9:78:9 | 2 | 2 | int |
| constants/constants.rb:78:12:78:12 | 3 | 3 | int |
| constants/constants.rb:79:6:79:6 | 1 | 1 | int |
| constants/constants.rb:79:9:79:9 | 2 | 2 | int |
| constants/constants.rb:79:12:79:12 | 3 | 3 | int |
| control/cases.rb:2:5:2:5 | 0 | 0 | int |
| control/cases.rb:3:5:3:5 | 0 | 0 | int |
| control/cases.rb:4:5:4:5 | 0 | 0 | int |
@@ -711,6 +717,7 @@ exprValue
| literals/literals.rb:198:8:198:8 | 5 | 5 | int |
| literals/literals.rb:199:2:199:2 | :y | :y | symbol |
| literals/literals.rb:199:7:199:7 | :Z | :Z | symbol |
| misc/iso-8859-15.rb:4:7:4:17 | "EUR = \u20ac" | EUR = \u20ac | string |
| misc/misc.erb:2:15:2:37 | "main_include_admin.js" | main_include_admin.js | string |
| misc/misc.rb:1:7:1:11 | "bar" | bar | string |
| misc/misc.rb:3:7:3:9 | foo | foo | string |
@@ -1004,6 +1011,12 @@ exprCfgNodeValue
| constants/constants.rb:63:19:63:20 | 45 | 45 | int |
| constants/constants.rb:65:19:65:35 | FOURTY_FIVE | 45 | int |
| constants/constants.rb:71:18:71:19 | 46 | 46 | int |
| constants/constants.rb:78:6:78:6 | 1 | 1 | int |
| constants/constants.rb:78:9:78:9 | 2 | 2 | int |
| constants/constants.rb:78:12:78:12 | 3 | 3 | int |
| constants/constants.rb:79:6:79:6 | 1 | 1 | int |
| constants/constants.rb:79:9:79:9 | 2 | 2 | int |
| constants/constants.rb:79:12:79:12 | 3 | 3 | int |
| control/cases.rb:2:5:2:5 | 0 | 0 | int |
| control/cases.rb:3:5:3:5 | 0 | 0 | int |
| control/cases.rb:4:5:4:5 | 0 | 0 | int |
@@ -1580,6 +1593,7 @@ exprCfgNodeValue
| literals/literals.rb:198:8:198:8 | 5 | 5 | int |
| literals/literals.rb:199:2:199:2 | :y | :y | symbol |
| literals/literals.rb:199:7:199:7 | :Z | :Z | symbol |
| misc/iso-8859-15.rb:4:7:4:17 | "EUR = \u20ac" | EUR = \u20ac | string |
| misc/misc.erb:2:15:2:37 | "main_include_admin.js" | main_include_admin.js | string |
| misc/misc.rb:1:7:1:11 | "bar" | bar | string |
| misc/misc.rb:3:7:3:9 | foo | foo | string |

View File

@@ -61,6 +61,13 @@ constantAccess
| constants.rb:71:5:71:14 | FOURTY_SIX | write | FOURTY_SIX | ConstantAssignment |
| constants.rb:73:18:73:21 | Mod3 | read | Mod3 | ConstantReadAccess |
| constants.rb:73:18:73:33 | FOURTY_SIX | read | FOURTY_SIX | ConstantReadAccess |
| constants.rb:78:5:78:13 | Array | read | Array | ConstantReadAccess |
| constants.rb:79:1:79:1 | A | write | A | ConstantAssignment |
| constants.rb:79:5:79:13 | Array | read | Array | ConstantReadAccess |
| constants.rb:80:1:80:1 | B | write | B | ConstantAssignment |
| constants.rb:81:1:81:1 | C | write | C | ConstantAssignment |
| constants.rb:81:5:81:5 | A | read | A | ConstantReadAccess |
| constants.rb:82:5:82:5 | B | read | B | ConstantReadAccess |
getConst
| constants.rb:1:1:15:3 | ModuleA | CONST_B | constants.rb:6:15:6:23 | "const_b" |
| constants.rb:1:1:15:3 | ModuleA | FOURTY_FOUR | constants.rb:53:17:53:29 | "fourty-four" |
@@ -71,23 +78,41 @@ getConst
| constants.rb:54:3:58:5 | ModuleA::ModuleB::ClassB | FOURTY_ONE | constants.rb:48:18:48:19 | 41 |
| constants.rb:62:3:64:5 | Mod1::Mod3 | FOURTY_FIVE | constants.rb:63:19:63:20 | 45 |
| constants.rb:70:3:72:5 | Mod1::Mod3::Mod5 | FOURTY_SIX | constants.rb:71:18:71:19 | 46 |
| file://:0:0:0:0 | Object | A | constants.rb:79:5:79:13 | [...] |
| file://:0:0:0:0 | Object | B | constants.rb:80:5:80:5 | a |
| file://:0:0:0:0 | Object | C | constants.rb:81:5:81:5 | A |
| file://:0:0:0:0 | Object | GREETING | constants.rb:17:12:17:64 | ... + ... |
lookupConst
| constants.rb:1:1:15:3 | ModuleA | CONST_B | constants.rb:6:15:6:23 | "const_b" |
| constants.rb:1:1:15:3 | ModuleA | FOURTY_FOUR | constants.rb:53:17:53:29 | "fourty-four" |
| constants.rb:2:5:4:7 | ModuleA::ClassA | A | constants.rb:79:5:79:13 | [...] |
| constants.rb:2:5:4:7 | ModuleA::ClassA | B | constants.rb:80:5:80:5 | a |
| constants.rb:2:5:4:7 | ModuleA::ClassA | C | constants.rb:81:5:81:5 | A |
| constants.rb:2:5:4:7 | ModuleA::ClassA | CONST_A | constants.rb:3:19:3:27 | "const_a" |
| constants.rb:2:5:4:7 | ModuleA::ClassA | GREETING | constants.rb:17:12:17:64 | ... + ... |
| constants.rb:8:5:14:7 | ModuleA::ModuleB | MAX_SIZE | constants.rb:39:30:39:33 | 1024 |
| constants.rb:12:9:13:11 | ModuleA::ModuleB::ClassC | A | constants.rb:79:5:79:13 | [...] |
| constants.rb:12:9:13:11 | ModuleA::ModuleB::ClassC | B | constants.rb:80:5:80:5 | a |
| constants.rb:12:9:13:11 | ModuleA::ModuleB::ClassC | C | constants.rb:81:5:81:5 | A |
| constants.rb:12:9:13:11 | ModuleA::ModuleB::ClassC | GREETING | constants.rb:17:12:17:64 | ... + ... |
| constants.rb:31:1:33:3 | ModuleA::ClassD | A | constants.rb:79:5:79:13 | [...] |
| constants.rb:31:1:33:3 | ModuleA::ClassD | B | constants.rb:80:5:80:5 | a |
| constants.rb:31:1:33:3 | ModuleA::ClassD | C | constants.rb:81:5:81:5 | A |
| constants.rb:31:1:33:3 | ModuleA::ClassD | CONST_A | constants.rb:3:19:3:27 | "const_a" |
| constants.rb:31:1:33:3 | ModuleA::ClassD | FOURTY_TWO | constants.rb:32:16:32:17 | 42 |
| constants.rb:31:1:33:3 | ModuleA::ClassD | GREETING | constants.rb:17:12:17:64 | ... + ... |
| constants.rb:35:1:37:3 | ModuleA::ModuleC | FOURTY_THREE | constants.rb:36:18:36:19 | 43 |
| constants.rb:54:3:58:5 | ModuleA::ModuleB::ClassB | A | constants.rb:79:5:79:13 | [...] |
| constants.rb:54:3:58:5 | ModuleA::ModuleB::ClassB | B | constants.rb:80:5:80:5 | a |
| constants.rb:54:3:58:5 | ModuleA::ModuleB::ClassB | C | constants.rb:81:5:81:5 | A |
| constants.rb:54:3:58:5 | ModuleA::ModuleB::ClassB | FOURTY_FOUR | constants.rb:56:19:56:20 | 44 |
| constants.rb:54:3:58:5 | ModuleA::ModuleB::ClassB | FOURTY_ONE | constants.rb:48:18:48:19 | 41 |
| constants.rb:54:3:58:5 | ModuleA::ModuleB::ClassB | GREETING | constants.rb:17:12:17:64 | ... + ... |
| constants.rb:62:3:64:5 | Mod1::Mod3 | FOURTY_FIVE | constants.rb:63:19:63:20 | 45 |
| constants.rb:70:3:72:5 | Mod1::Mod3::Mod5 | FOURTY_SIX | constants.rb:71:18:71:19 | 46 |
| file://:0:0:0:0 | Object | A | constants.rb:79:5:79:13 | [...] |
| file://:0:0:0:0 | Object | B | constants.rb:80:5:80:5 | a |
| file://:0:0:0:0 | Object | C | constants.rb:81:5:81:5 | A |
| file://:0:0:0:0 | Object | GREETING | constants.rb:17:12:17:64 | ... + ... |
constantValue
| constants.rb:17:22:17:45 | CONST_A | constants.rb:3:19:3:27 | "const_a" |
@@ -101,6 +126,8 @@ constantValue
| constants.rb:57:21:57:31 | FOURTY_FOUR | constants.rb:53:17:53:29 | "fourty-four" |
| constants.rb:57:21:57:31 | FOURTY_FOUR | constants.rb:56:19:56:20 | 44 |
| constants.rb:65:19:65:35 | FOURTY_FIVE | constants.rb:63:19:63:20 | 45 |
| constants.rb:81:5:81:5 | A | constants.rb:79:5:79:13 | [...] |
| constants.rb:82:5:82:5 | B | constants.rb:80:5:80:5 | a |
constantWriteAccessQualifiedName
| constants.rb:1:1:15:3 | ModuleA | ModuleA |
| constants.rb:2:5:4:7 | ClassA | ModuleA::ClassA |
@@ -133,3 +160,14 @@ constantWriteAccessQualifiedName
| constants.rb:70:3:72:5 | Mod5 | Mod3::Mod5 |
| constants.rb:71:5:71:14 | FOURTY_SIX | Mod1::Mod3::Mod5::FOURTY_SIX |
| constants.rb:71:5:71:14 | FOURTY_SIX | Mod3::Mod5::FOURTY_SIX |
| constants.rb:79:1:79:1 | A | A |
| constants.rb:80:1:80:1 | B | B |
| constants.rb:81:1:81:1 | C | C |
arrayConstant
| constants.rb:20:13:20:37 | call to [] | constants.rb:20:13:20:37 | call to [] |
| constants.rb:78:5:78:13 | call to [] | constants.rb:78:5:78:13 | call to [] |
| constants.rb:79:5:79:13 | call to [] | constants.rb:79:5:79:13 | call to [] |
| constants.rb:80:5:80:5 | a | constants.rb:78:5:78:13 | call to [] |
| constants.rb:81:5:81:5 | A | constants.rb:79:5:79:13 | call to [] |
| constants.rb:82:5:82:5 | B | constants.rb:78:5:78:13 | call to [] |
| constants.rb:85:7:85:7 | b | constants.rb:78:5:78:13 | call to [] |

View File

@@ -1,5 +1,6 @@
import ruby
import codeql.ruby.ast.internal.Module as M
import codeql.ruby.ast.internal.Constant
query predicate constantAccess(ConstantAccess a, string kind, string name, string cls) {
(
@@ -20,3 +21,5 @@ query predicate constantValue(ConstantReadAccess a, Expr e) { e = a.getValue() }
query predicate constantWriteAccessQualifiedName(ConstantWriteAccess w, string qualifiedName) {
w.getAQualifiedName() = qualifiedName
}
query predicate arrayConstant = isArrayConstant/2;

View File

@@ -72,3 +72,16 @@ module Mod4
end
@@fourty_six = Mod3::FOURTY_SIX
end
# Array constants
a = [1, 2, 3]
A = [1, 2, 3]
B = a
C = A
b = B
if condition
c = b
end
c # not recognised

View File

@@ -0,0 +1,4 @@
#! /usr/bin/ruby
# coding: iso-8859-15
print "EUR = <20>"

View File

@@ -14,6 +14,12 @@
| app/controllers/users_controller.rb:20:7:20:57 | call to update_attributes | app/controllers/users_controller.rb:20:49:20:55 | call to get_uid |
| app/controllers/users_controller.rb:23:7:23:42 | call to update_attribute | app/controllers/users_controller.rb:23:37:23:41 | "U13" |
| app/controllers/users_controller.rb:26:19:26:23 | ... = ... | app/controllers/users_controller.rb:26:19:26:23 | "U14" |
| app/controllers/users_controller.rb:31:7:31:32 | call to touch_all | app/controllers/users_controller.rb:31:28:31:31 | call to time |
| app/controllers/users_controller.rb:35:7:35:27 | call to update | app/controllers/users_controller.rb:35:22:35:26 | attrs |
| app/controllers/users_controller.rb:36:7:36:28 | call to update! | app/controllers/users_controller.rb:36:23:36:27 | attrs |
| app/controllers/users_controller.rb:39:7:39:24 | call to create | app/controllers/users_controller.rb:39:19:39:23 | attrs |
| app/controllers/users_controller.rb:40:7:40:25 | call to create! | app/controllers/users_controller.rb:40:20:40:24 | attrs |
| app/controllers/users_controller.rb:41:7:41:24 | call to insert | app/controllers/users_controller.rb:41:19:41:23 | attrs |
| app/models/user.rb:4:5:4:28 | call to update | app/models/user.rb:4:23:4:27 | "U15" |
| app/models/user.rb:5:5:5:23 | call to update | app/models/user.rb:5:18:5:22 | "U16" |
| app/models/user.rb:6:5:6:56 | call to update_attributes | app/models/user.rb:6:35:6:39 | "U17" |

View File

@@ -25,6 +25,20 @@ module Users
# AssignAttributeCall
user.name = "U14"
user.save
# TouchAllCall
User.touch_all
User.touch_all(time: time)
# UpdateLikeClassMethodCall
attrs = {name: "U15"}
User.update(8, attrs)
User.update!(8, attrs)
# CreateLikeClassMethodCall
User.create(attrs)
User.create!(attrs)
User.insert(attrs)
end
def get_uid

View File

@@ -26,10 +26,10 @@ class ApiUseTest extends InlineExpectationsTest {
l = n.getLocation() and
(
tag = "use" and
n = a.getAUse()
n = a.getAValueReachableFromSource()
or
tag = "def" and
n = a.getARhs()
n = a.asSink()
or
tag = "call" and
n = a.(API::MethodAccessNode).getCallNode()

View File

@@ -0,0 +1,22 @@
WARNING: Type BarrierGuard has been deprecated and may be removed in future (barrier-guards.ql:8,3-15)
oldStyleBarrierGuards
| barrier-guards.rb:3:4:3:15 | ... == ... | barrier-guards.rb:4:5:4:7 | foo | barrier-guards.rb:3:4:3:6 | foo | true |
| barrier-guards.rb:9:4:9:24 | call to include? | barrier-guards.rb:10:5:10:7 | foo | barrier-guards.rb:9:21:9:23 | foo | true |
| barrier-guards.rb:15:4:15:15 | ... != ... | barrier-guards.rb:18:5:18:7 | foo | barrier-guards.rb:15:4:15:6 | foo | false |
| barrier-guards.rb:21:8:21:19 | ... == ... | barrier-guards.rb:24:5:24:7 | foo | barrier-guards.rb:21:8:21:10 | foo | true |
| barrier-guards.rb:27:8:27:19 | ... != ... | barrier-guards.rb:28:5:28:7 | foo | barrier-guards.rb:27:8:27:10 | foo | false |
| barrier-guards.rb:37:4:37:20 | call to include? | barrier-guards.rb:38:5:38:7 | foo | barrier-guards.rb:37:17:37:19 | foo | true |
| barrier-guards.rb:43:4:43:15 | ... == ... | barrier-guards.rb:45:9:45:11 | foo | barrier-guards.rb:43:4:43:6 | foo | true |
| barrier-guards.rb:70:4:70:21 | call to include? | barrier-guards.rb:71:5:71:7 | foo | barrier-guards.rb:70:18:70:20 | foo | true |
| barrier-guards.rb:82:4:82:25 | ... != ... | barrier-guards.rb:83:5:83:7 | foo | barrier-guards.rb:82:15:82:17 | foo | true |
newStyleBarrierGuards
| barrier-guards.rb:4:5:4:7 | foo |
| barrier-guards.rb:10:5:10:7 | foo |
| barrier-guards.rb:18:5:18:7 | foo |
| barrier-guards.rb:24:5:24:7 | foo |
| barrier-guards.rb:28:5:28:7 | foo |
| barrier-guards.rb:38:5:38:7 | foo |
| barrier-guards.rb:45:9:45:11 | foo |
| barrier-guards.rb:71:5:71:7 | foo |
| barrier-guards.rb:83:5:83:7 | foo |
| barrier-guards.rb:91:5:91:7 | foo |

View File

@@ -0,0 +1,16 @@
import codeql.ruby.dataflow.internal.DataFlowPublic
import codeql.ruby.dataflow.BarrierGuards
import codeql.ruby.controlflow.CfgNodes
import codeql.ruby.controlflow.ControlFlowGraph
import codeql.ruby.DataFlow
query predicate oldStyleBarrierGuards(
BarrierGuard g, DataFlow::Node guardedNode, ExprCfgNode expr, boolean branch
) {
g.checks(expr, branch) and guardedNode = g.getAGuardedNode()
}
query predicate newStyleBarrierGuards(DataFlow::Node n) {
n instanceof StringConstCompareBarrier or
n instanceof StringConstArrayInclusionCallBarrier
}

View File

@@ -0,0 +1,104 @@
foo = "foo"
if foo == "foo"
foo
else
foo
end
if ["foo"].include?(foo)
foo
else
foo
end
if foo != "foo"
foo
else
foo
end
unless foo == "foo"
foo
else
foo
end
unless foo != "foo"
foo
else
foo
end
foo
FOO = ["foo"]
if FOO.include?(foo)
foo
else
foo
end
if foo == "foo"
capture {
foo # guarded
}
end
if foo == "foo"
capture {
foo = "bar"
foo # not guarded
}
end
if foo == "foo"
my_lambda = -> () {
foo # not guarded
}
foo = "bar"
my_lambda()
end
foos = nil
foos = ["foo"]
bars = NotAnArray.new
if foos.include?(foo)
foo
else
foo
end
if bars.include?(foo)
foo
else
foo
end
if foos.index(foo) != nil
foo
else
foo
end
if foos.index(foo)r == nil
foo
else
foo
end
bars = ["bar"]
if condition
bars = nil
end
if bars.include?(foo)
foo
else
foo
end

View File

@@ -26,6 +26,16 @@ edges
| params_flow.rb:35:12:35:20 | call to taint : | params_flow.rb:25:12:25:13 | p1 : |
| params_flow.rb:35:23:35:28 | ** ... [element :p3] : | params_flow.rb:25:17:25:24 | **kwargs [element :p3] : |
| params_flow.rb:35:25:35:28 | args [element :p3] : | params_flow.rb:35:23:35:28 | ** ... [element :p3] : |
| params_flow.rb:37:16:37:24 | call to taint : | params_flow.rb:38:10:38:13 | args [element :p1] : |
| params_flow.rb:37:34:37:42 | call to taint : | params_flow.rb:38:10:38:13 | args [element :p2] : |
| params_flow.rb:38:8:38:13 | ** ... [element :p1] : | params_flow.rb:25:12:25:13 | p1 : |
| params_flow.rb:38:8:38:13 | ** ... [element :p2] : | params_flow.rb:25:17:25:24 | **kwargs [element :p2] : |
| params_flow.rb:38:10:38:13 | args [element :p1] : | params_flow.rb:38:8:38:13 | ** ... [element :p1] : |
| params_flow.rb:38:10:38:13 | args [element :p2] : | params_flow.rb:38:8:38:13 | ** ... [element :p2] : |
| params_flow.rb:40:16:40:24 | call to taint : | params_flow.rb:41:26:41:29 | args [element :p1] : |
| params_flow.rb:41:13:41:21 | call to taint : | params_flow.rb:16:18:16:19 | p2 : |
| params_flow.rb:41:24:41:29 | ** ... [element :p1] : | params_flow.rb:16:13:16:14 | p1 : |
| params_flow.rb:41:26:41:29 | args [element :p1] : | params_flow.rb:41:24:41:29 | ** ... [element :p1] : |
nodes
| params_flow.rb:9:16:9:17 | p1 : | semmle.label | p1 : |
| params_flow.rb:9:20:9:21 | p2 : | semmle.label | p2 : |
@@ -60,6 +70,16 @@ nodes
| params_flow.rb:35:12:35:20 | call to taint : | semmle.label | call to taint : |
| params_flow.rb:35:23:35:28 | ** ... [element :p3] : | semmle.label | ** ... [element :p3] : |
| params_flow.rb:35:25:35:28 | args [element :p3] : | semmle.label | args [element :p3] : |
| params_flow.rb:37:16:37:24 | call to taint : | semmle.label | call to taint : |
| params_flow.rb:37:34:37:42 | call to taint : | semmle.label | call to taint : |
| params_flow.rb:38:8:38:13 | ** ... [element :p1] : | semmle.label | ** ... [element :p1] : |
| params_flow.rb:38:8:38:13 | ** ... [element :p2] : | semmle.label | ** ... [element :p2] : |
| params_flow.rb:38:10:38:13 | args [element :p1] : | semmle.label | args [element :p1] : |
| params_flow.rb:38:10:38:13 | args [element :p2] : | semmle.label | args [element :p2] : |
| params_flow.rb:40:16:40:24 | call to taint : | semmle.label | call to taint : |
| params_flow.rb:41:13:41:21 | call to taint : | semmle.label | call to taint : |
| params_flow.rb:41:24:41:29 | ** ... [element :p1] : | semmle.label | ** ... [element :p1] : |
| params_flow.rb:41:26:41:29 | args [element :p1] : | semmle.label | args [element :p1] : |
subpaths
#select
| params_flow.rb:10:10:10:11 | p1 | params_flow.rb:14:12:14:19 | call to taint : | params_flow.rb:10:10:10:11 | p1 | $@ | params_flow.rb:14:12:14:19 | call to taint : | call to taint : |
@@ -67,11 +87,15 @@ subpaths
| params_flow.rb:17:10:17:11 | p1 | params_flow.rb:21:13:21:20 | call to taint : | params_flow.rb:17:10:17:11 | p1 | $@ | params_flow.rb:21:13:21:20 | call to taint : | call to taint : |
| params_flow.rb:17:10:17:11 | p1 | params_flow.rb:22:27:22:34 | call to taint : | params_flow.rb:17:10:17:11 | p1 | $@ | params_flow.rb:22:27:22:34 | call to taint : | call to taint : |
| params_flow.rb:17:10:17:11 | p1 | params_flow.rb:23:33:23:40 | call to taint : | params_flow.rb:17:10:17:11 | p1 | $@ | params_flow.rb:23:33:23:40 | call to taint : | call to taint : |
| params_flow.rb:17:10:17:11 | p1 | params_flow.rb:40:16:40:24 | call to taint : | params_flow.rb:17:10:17:11 | p1 | $@ | params_flow.rb:40:16:40:24 | call to taint : | call to taint : |
| params_flow.rb:18:10:18:11 | p2 | params_flow.rb:21:27:21:34 | call to taint : | params_flow.rb:18:10:18:11 | p2 | $@ | params_flow.rb:21:27:21:34 | call to taint : | call to taint : |
| params_flow.rb:18:10:18:11 | p2 | params_flow.rb:22:13:22:20 | call to taint : | params_flow.rb:18:10:18:11 | p2 | $@ | params_flow.rb:22:13:22:20 | call to taint : | call to taint : |
| params_flow.rb:18:10:18:11 | p2 | params_flow.rb:23:16:23:23 | call to taint : | params_flow.rb:18:10:18:11 | p2 | $@ | params_flow.rb:23:16:23:23 | call to taint : | call to taint : |
| params_flow.rb:18:10:18:11 | p2 | params_flow.rb:41:13:41:21 | call to taint : | params_flow.rb:18:10:18:11 | p2 | $@ | params_flow.rb:41:13:41:21 | call to taint : | call to taint : |
| params_flow.rb:26:10:26:11 | p1 | params_flow.rb:33:12:33:19 | call to taint : | params_flow.rb:26:10:26:11 | p1 | $@ | params_flow.rb:33:12:33:19 | call to taint : | call to taint : |
| params_flow.rb:26:10:26:11 | p1 | params_flow.rb:35:12:35:20 | call to taint : | params_flow.rb:26:10:26:11 | p1 | $@ | params_flow.rb:35:12:35:20 | call to taint : | call to taint : |
| params_flow.rb:26:10:26:11 | p1 | params_flow.rb:37:16:37:24 | call to taint : | params_flow.rb:26:10:26:11 | p1 | $@ | params_flow.rb:37:16:37:24 | call to taint : | call to taint : |
| params_flow.rb:28:10:28:22 | ( ... ) | params_flow.rb:33:26:33:34 | call to taint : | params_flow.rb:28:10:28:22 | ( ... ) | $@ | params_flow.rb:33:26:33:34 | call to taint : | call to taint : |
| params_flow.rb:28:10:28:22 | ( ... ) | params_flow.rb:37:34:37:42 | call to taint : | params_flow.rb:28:10:28:22 | ( ... ) | $@ | params_flow.rb:37:34:37:42 | call to taint : | call to taint : |
| params_flow.rb:29:10:29:22 | ( ... ) | params_flow.rb:33:41:33:49 | call to taint : | params_flow.rb:29:10:29:22 | ( ... ) | $@ | params_flow.rb:33:41:33:49 | call to taint : | call to taint : |
| params_flow.rb:29:10:29:22 | ( ... ) | params_flow.rb:34:14:34:22 | call to taint : | params_flow.rb:29:10:29:22 | ( ... ) | $@ | params_flow.rb:34:14:34:22 | call to taint : | call to taint : |

View File

@@ -14,8 +14,8 @@ end
positional(taint(1), taint(2))
def keyword(p1:, p2:)
sink p1 # $ hasValueFlow=3 $ hasValueFlow=6 $ hasValueFlow=8
sink p2 # $ hasValueFlow=4 $ hasValueFlow=5 $ hasValueFlow=7
sink p1 # $ hasValueFlow=3 $ hasValueFlow=6 $ hasValueFlow=8 $ hasValueFlow=16
sink p2 # $ hasValueFlow=4 $ hasValueFlow=5 $ hasValueFlow=7 $ hasValueFlow=17
end
keyword(p1: taint(3), p2: taint(4))
@@ -23,9 +23,9 @@ keyword(p2: taint(5), p1: taint(6))
keyword(:p2 => taint(7), :p1 => taint(8))
def kwargs(p1:, **kwargs)
sink p1 # $ hasValueFlow=9 $ hasValueFlow=13
sink p1 # $ hasValueFlow=9 $ hasValueFlow=13 $ hasValueFlow=14
sink (kwargs[:p1])
sink (kwargs[:p2]) # $ hasValueFlow=10
sink (kwargs[:p2]) # $ hasValueFlow=10 $ hasValueFlow=15
sink (kwargs[:p3]) # $ hasValueFlow=11 $ hasValueFlow=12
sink (kwargs[:p4])
end
@@ -33,3 +33,9 @@ end
kwargs(p1: taint(9), p2: taint(10), p3: taint(11), p4: "")
args = { p3: taint(12), p4: "" }
kwargs(p1: taint(13), **args)
args = {:p1 => taint(14), :p2 => taint(15) }
kwargs(**args)
args = {:p1 => taint(16) }
keyword(p2: taint(17), **args)

View File

@@ -0,0 +1,219 @@
failures
edges
| pathname_flow.rb:4:10:4:33 | call to new : | pathname_flow.rb:5:10:5:11 | pn |
| pathname_flow.rb:4:23:4:32 | call to source : | pathname_flow.rb:4:10:4:33 | call to new : |
| pathname_flow.rb:9:7:9:30 | call to new : | pathname_flow.rb:11:8:11:12 | ... + ... |
| pathname_flow.rb:9:20:9:29 | call to source : | pathname_flow.rb:9:7:9:30 | call to new : |
| pathname_flow.rb:10:7:10:30 | call to new : | pathname_flow.rb:11:8:11:12 | ... + ... |
| pathname_flow.rb:10:20:10:29 | call to source : | pathname_flow.rb:10:7:10:30 | call to new : |
| pathname_flow.rb:15:8:15:31 | call to new : | pathname_flow.rb:16:8:16:9 | pn : |
| pathname_flow.rb:15:21:15:30 | call to source : | pathname_flow.rb:15:8:15:31 | call to new : |
| pathname_flow.rb:16:8:16:9 | pn : | pathname_flow.rb:16:8:16:17 | call to dirname |
| pathname_flow.rb:20:7:20:30 | call to new : | pathname_flow.rb:21:3:21:3 | a : |
| pathname_flow.rb:20:20:20:29 | call to source : | pathname_flow.rb:20:7:20:30 | call to new : |
| pathname_flow.rb:21:3:21:3 | a : | pathname_flow.rb:21:23:21:23 | x : |
| pathname_flow.rb:21:23:21:23 | x : | pathname_flow.rb:22:10:22:10 | x |
| pathname_flow.rb:27:7:27:30 | call to new : | pathname_flow.rb:28:8:28:8 | a : |
| pathname_flow.rb:27:20:27:29 | call to source : | pathname_flow.rb:27:7:27:30 | call to new : |
| pathname_flow.rb:28:8:28:8 | a : | pathname_flow.rb:28:8:28:22 | call to expand_path |
| pathname_flow.rb:32:7:32:30 | call to new : | pathname_flow.rb:35:8:35:8 | a : |
| pathname_flow.rb:32:20:32:29 | call to source : | pathname_flow.rb:32:7:32:30 | call to new : |
| pathname_flow.rb:34:7:34:30 | call to new : | pathname_flow.rb:35:18:35:18 | c : |
| pathname_flow.rb:34:20:34:29 | call to source : | pathname_flow.rb:34:7:34:30 | call to new : |
| pathname_flow.rb:35:8:35:8 | a : | pathname_flow.rb:35:8:35:19 | call to join |
| pathname_flow.rb:35:18:35:18 | c : | pathname_flow.rb:35:8:35:19 | call to join |
| pathname_flow.rb:39:7:39:30 | call to new : | pathname_flow.rb:40:8:40:8 | a : |
| pathname_flow.rb:39:20:39:29 | call to source : | pathname_flow.rb:39:7:39:30 | call to new : |
| pathname_flow.rb:40:8:40:8 | a : | pathname_flow.rb:40:8:40:17 | call to parent |
| pathname_flow.rb:44:7:44:30 | call to new : | pathname_flow.rb:45:8:45:8 | a : |
| pathname_flow.rb:44:20:44:29 | call to source : | pathname_flow.rb:44:7:44:30 | call to new : |
| pathname_flow.rb:45:8:45:8 | a : | pathname_flow.rb:45:8:45:19 | call to realpath |
| pathname_flow.rb:49:7:49:30 | call to new : | pathname_flow.rb:50:8:50:8 | a : |
| pathname_flow.rb:49:20:49:29 | call to source : | pathname_flow.rb:49:7:49:30 | call to new : |
| pathname_flow.rb:50:8:50:8 | a : | pathname_flow.rb:50:8:50:39 | call to relative_path_from |
| pathname_flow.rb:54:7:54:30 | call to new : | pathname_flow.rb:55:8:55:8 | a : |
| pathname_flow.rb:54:20:54:29 | call to source : | pathname_flow.rb:54:7:54:30 | call to new : |
| pathname_flow.rb:55:8:55:8 | a : | pathname_flow.rb:55:8:55:16 | call to to_path |
| pathname_flow.rb:59:7:59:30 | call to new : | pathname_flow.rb:60:8:60:8 | a : |
| pathname_flow.rb:59:20:59:29 | call to source : | pathname_flow.rb:59:7:59:30 | call to new : |
| pathname_flow.rb:60:8:60:8 | a : | pathname_flow.rb:60:8:60:13 | call to to_s |
| pathname_flow.rb:64:7:64:30 | call to new : | pathname_flow.rb:66:8:66:8 | b |
| pathname_flow.rb:64:20:64:29 | call to source : | pathname_flow.rb:64:7:64:30 | call to new : |
| pathname_flow.rb:70:7:70:30 | call to new : | pathname_flow.rb:72:8:72:8 | b |
| pathname_flow.rb:70:20:70:29 | call to source : | pathname_flow.rb:70:7:70:30 | call to new : |
| pathname_flow.rb:76:7:76:30 | call to new : | pathname_flow.rb:77:7:77:7 | a : |
| pathname_flow.rb:76:20:76:29 | call to source : | pathname_flow.rb:76:7:76:30 | call to new : |
| pathname_flow.rb:77:7:77:7 | a : | pathname_flow.rb:77:7:77:16 | call to basename : |
| pathname_flow.rb:77:7:77:16 | call to basename : | pathname_flow.rb:78:8:78:8 | b |
| pathname_flow.rb:82:7:82:30 | call to new : | pathname_flow.rb:83:7:83:7 | a : |
| pathname_flow.rb:82:20:82:29 | call to source : | pathname_flow.rb:82:7:82:30 | call to new : |
| pathname_flow.rb:83:7:83:7 | a : | pathname_flow.rb:83:7:83:17 | call to cleanpath : |
| pathname_flow.rb:83:7:83:17 | call to cleanpath : | pathname_flow.rb:84:8:84:8 | b |
| pathname_flow.rb:88:7:88:30 | call to new : | pathname_flow.rb:89:7:89:7 | a : |
| pathname_flow.rb:88:20:88:29 | call to source : | pathname_flow.rb:88:7:88:30 | call to new : |
| pathname_flow.rb:89:7:89:7 | a : | pathname_flow.rb:89:7:89:25 | call to sub : |
| pathname_flow.rb:89:7:89:25 | call to sub : | pathname_flow.rb:90:8:90:8 | b |
| pathname_flow.rb:94:7:94:30 | call to new : | pathname_flow.rb:95:7:95:7 | a : |
| pathname_flow.rb:94:20:94:29 | call to source : | pathname_flow.rb:94:7:94:30 | call to new : |
| pathname_flow.rb:95:7:95:7 | a : | pathname_flow.rb:95:7:95:23 | call to sub_ext : |
| pathname_flow.rb:95:7:95:23 | call to sub_ext : | pathname_flow.rb:96:8:96:8 | b |
| pathname_flow.rb:101:7:101:30 | call to new : | pathname_flow.rb:104:8:104:8 | b : |
| pathname_flow.rb:101:7:101:30 | call to new : | pathname_flow.rb:107:8:107:8 | c : |
| pathname_flow.rb:101:7:101:30 | call to new : | pathname_flow.rb:109:7:109:7 | a : |
| pathname_flow.rb:101:7:101:30 | call to new : | pathname_flow.rb:112:7:112:7 | a : |
| pathname_flow.rb:101:7:101:30 | call to new : | pathname_flow.rb:115:7:115:7 | a : |
| pathname_flow.rb:101:7:101:30 | call to new : | pathname_flow.rb:118:7:118:7 | a : |
| pathname_flow.rb:101:7:101:30 | call to new : | pathname_flow.rb:121:7:121:7 | a : |
| pathname_flow.rb:101:7:101:30 | call to new : | pathname_flow.rb:124:7:124:7 | a : |
| pathname_flow.rb:101:7:101:30 | call to new : | pathname_flow.rb:127:7:127:7 | a : |
| pathname_flow.rb:101:7:101:30 | call to new : | pathname_flow.rb:130:7:130:7 | a : |
| pathname_flow.rb:101:7:101:30 | call to new : | pathname_flow.rb:133:7:133:7 | a : |
| pathname_flow.rb:101:20:101:29 | call to source : | pathname_flow.rb:101:7:101:30 | call to new : |
| pathname_flow.rb:104:8:104:8 | b : | pathname_flow.rb:104:8:104:17 | call to realpath |
| pathname_flow.rb:107:8:107:8 | c : | pathname_flow.rb:107:8:107:17 | call to realpath |
| pathname_flow.rb:109:7:109:7 | a : | pathname_flow.rb:109:7:109:16 | call to basename : |
| pathname_flow.rb:109:7:109:16 | call to basename : | pathname_flow.rb:110:8:110:8 | d : |
| pathname_flow.rb:110:8:110:8 | d : | pathname_flow.rb:110:8:110:17 | call to realpath |
| pathname_flow.rb:112:7:112:7 | a : | pathname_flow.rb:112:7:112:17 | call to cleanpath : |
| pathname_flow.rb:112:7:112:17 | call to cleanpath : | pathname_flow.rb:113:8:113:8 | e : |
| pathname_flow.rb:113:8:113:8 | e : | pathname_flow.rb:113:8:113:17 | call to realpath |
| pathname_flow.rb:115:7:115:7 | a : | pathname_flow.rb:115:7:115:19 | call to expand_path : |
| pathname_flow.rb:115:7:115:19 | call to expand_path : | pathname_flow.rb:116:8:116:8 | f : |
| pathname_flow.rb:116:8:116:8 | f : | pathname_flow.rb:116:8:116:17 | call to realpath |
| pathname_flow.rb:118:7:118:7 | a : | pathname_flow.rb:118:7:118:19 | call to join : |
| pathname_flow.rb:118:7:118:19 | call to join : | pathname_flow.rb:119:8:119:8 | g : |
| pathname_flow.rb:119:8:119:8 | g : | pathname_flow.rb:119:8:119:17 | call to realpath |
| pathname_flow.rb:121:7:121:7 | a : | pathname_flow.rb:121:7:121:16 | call to realpath : |
| pathname_flow.rb:121:7:121:16 | call to realpath : | pathname_flow.rb:122:8:122:8 | h : |
| pathname_flow.rb:122:8:122:8 | h : | pathname_flow.rb:122:8:122:17 | call to realpath |
| pathname_flow.rb:124:7:124:7 | a : | pathname_flow.rb:124:7:124:38 | call to relative_path_from : |
| pathname_flow.rb:124:7:124:38 | call to relative_path_from : | pathname_flow.rb:125:8:125:8 | i : |
| pathname_flow.rb:125:8:125:8 | i : | pathname_flow.rb:125:8:125:17 | call to realpath |
| pathname_flow.rb:127:7:127:7 | a : | pathname_flow.rb:127:7:127:25 | call to sub : |
| pathname_flow.rb:127:7:127:25 | call to sub : | pathname_flow.rb:128:8:128:8 | j : |
| pathname_flow.rb:128:8:128:8 | j : | pathname_flow.rb:128:8:128:17 | call to realpath |
| pathname_flow.rb:130:7:130:7 | a : | pathname_flow.rb:130:7:130:23 | call to sub_ext : |
| pathname_flow.rb:130:7:130:23 | call to sub_ext : | pathname_flow.rb:131:8:131:8 | k : |
| pathname_flow.rb:131:8:131:8 | k : | pathname_flow.rb:131:8:131:17 | call to realpath |
| pathname_flow.rb:133:7:133:7 | a : | pathname_flow.rb:133:7:133:15 | call to to_path : |
| pathname_flow.rb:133:7:133:15 | call to to_path : | pathname_flow.rb:134:8:134:8 | l : |
| pathname_flow.rb:134:8:134:8 | l : | pathname_flow.rb:134:8:134:17 | call to realpath |
nodes
| pathname_flow.rb:4:10:4:33 | call to new : | semmle.label | call to new : |
| pathname_flow.rb:4:23:4:32 | call to source : | semmle.label | call to source : |
| pathname_flow.rb:5:10:5:11 | pn | semmle.label | pn |
| pathname_flow.rb:9:7:9:30 | call to new : | semmle.label | call to new : |
| pathname_flow.rb:9:20:9:29 | call to source : | semmle.label | call to source : |
| pathname_flow.rb:10:7:10:30 | call to new : | semmle.label | call to new : |
| pathname_flow.rb:10:20:10:29 | call to source : | semmle.label | call to source : |
| pathname_flow.rb:11:8:11:12 | ... + ... | semmle.label | ... + ... |
| pathname_flow.rb:15:8:15:31 | call to new : | semmle.label | call to new : |
| pathname_flow.rb:15:21:15:30 | call to source : | semmle.label | call to source : |
| pathname_flow.rb:16:8:16:9 | pn : | semmle.label | pn : |
| pathname_flow.rb:16:8:16:17 | call to dirname | semmle.label | call to dirname |
| pathname_flow.rb:20:7:20:30 | call to new : | semmle.label | call to new : |
| pathname_flow.rb:20:20:20:29 | call to source : | semmle.label | call to source : |
| pathname_flow.rb:21:3:21:3 | a : | semmle.label | a : |
| pathname_flow.rb:21:23:21:23 | x : | semmle.label | x : |
| pathname_flow.rb:22:10:22:10 | x | semmle.label | x |
| pathname_flow.rb:27:7:27:30 | call to new : | semmle.label | call to new : |
| pathname_flow.rb:27:20:27:29 | call to source : | semmle.label | call to source : |
| pathname_flow.rb:28:8:28:8 | a : | semmle.label | a : |
| pathname_flow.rb:28:8:28:22 | call to expand_path | semmle.label | call to expand_path |
| pathname_flow.rb:32:7:32:30 | call to new : | semmle.label | call to new : |
| pathname_flow.rb:32:20:32:29 | call to source : | semmle.label | call to source : |
| pathname_flow.rb:34:7:34:30 | call to new : | semmle.label | call to new : |
| pathname_flow.rb:34:20:34:29 | call to source : | semmle.label | call to source : |
| pathname_flow.rb:35:8:35:8 | a : | semmle.label | a : |
| pathname_flow.rb:35:8:35:19 | call to join | semmle.label | call to join |
| pathname_flow.rb:35:18:35:18 | c : | semmle.label | c : |
| pathname_flow.rb:39:7:39:30 | call to new : | semmle.label | call to new : |
| pathname_flow.rb:39:20:39:29 | call to source : | semmle.label | call to source : |
| pathname_flow.rb:40:8:40:8 | a : | semmle.label | a : |
| pathname_flow.rb:40:8:40:17 | call to parent | semmle.label | call to parent |
| pathname_flow.rb:44:7:44:30 | call to new : | semmle.label | call to new : |
| pathname_flow.rb:44:20:44:29 | call to source : | semmle.label | call to source : |
| pathname_flow.rb:45:8:45:8 | a : | semmle.label | a : |
| pathname_flow.rb:45:8:45:19 | call to realpath | semmle.label | call to realpath |
| pathname_flow.rb:49:7:49:30 | call to new : | semmle.label | call to new : |
| pathname_flow.rb:49:20:49:29 | call to source : | semmle.label | call to source : |
| pathname_flow.rb:50:8:50:8 | a : | semmle.label | a : |
| pathname_flow.rb:50:8:50:39 | call to relative_path_from | semmle.label | call to relative_path_from |
| pathname_flow.rb:54:7:54:30 | call to new : | semmle.label | call to new : |
| pathname_flow.rb:54:20:54:29 | call to source : | semmle.label | call to source : |
| pathname_flow.rb:55:8:55:8 | a : | semmle.label | a : |
| pathname_flow.rb:55:8:55:16 | call to to_path | semmle.label | call to to_path |
| pathname_flow.rb:59:7:59:30 | call to new : | semmle.label | call to new : |
| pathname_flow.rb:59:20:59:29 | call to source : | semmle.label | call to source : |
| pathname_flow.rb:60:8:60:8 | a : | semmle.label | a : |
| pathname_flow.rb:60:8:60:13 | call to to_s | semmle.label | call to to_s |
| pathname_flow.rb:64:7:64:30 | call to new : | semmle.label | call to new : |
| pathname_flow.rb:64:20:64:29 | call to source : | semmle.label | call to source : |
| pathname_flow.rb:66:8:66:8 | b | semmle.label | b |
| pathname_flow.rb:70:7:70:30 | call to new : | semmle.label | call to new : |
| pathname_flow.rb:70:20:70:29 | call to source : | semmle.label | call to source : |
| pathname_flow.rb:72:8:72:8 | b | semmle.label | b |
| pathname_flow.rb:76:7:76:30 | call to new : | semmle.label | call to new : |
| pathname_flow.rb:76:20:76:29 | call to source : | semmle.label | call to source : |
| pathname_flow.rb:77:7:77:7 | a : | semmle.label | a : |
| pathname_flow.rb:77:7:77:16 | call to basename : | semmle.label | call to basename : |
| pathname_flow.rb:78:8:78:8 | b | semmle.label | b |
| pathname_flow.rb:82:7:82:30 | call to new : | semmle.label | call to new : |
| pathname_flow.rb:82:20:82:29 | call to source : | semmle.label | call to source : |
| pathname_flow.rb:83:7:83:7 | a : | semmle.label | a : |
| pathname_flow.rb:83:7:83:17 | call to cleanpath : | semmle.label | call to cleanpath : |
| pathname_flow.rb:84:8:84:8 | b | semmle.label | b |
| pathname_flow.rb:88:7:88:30 | call to new : | semmle.label | call to new : |
| pathname_flow.rb:88:20:88:29 | call to source : | semmle.label | call to source : |
| pathname_flow.rb:89:7:89:7 | a : | semmle.label | a : |
| pathname_flow.rb:89:7:89:25 | call to sub : | semmle.label | call to sub : |
| pathname_flow.rb:90:8:90:8 | b | semmle.label | b |
| pathname_flow.rb:94:7:94:30 | call to new : | semmle.label | call to new : |
| pathname_flow.rb:94:20:94:29 | call to source : | semmle.label | call to source : |
| pathname_flow.rb:95:7:95:7 | a : | semmle.label | a : |
| pathname_flow.rb:95:7:95:23 | call to sub_ext : | semmle.label | call to sub_ext : |
| pathname_flow.rb:96:8:96:8 | b | semmle.label | b |
| pathname_flow.rb:101:7:101:30 | call to new : | semmle.label | call to new : |
| pathname_flow.rb:101:20:101:29 | call to source : | semmle.label | call to source : |
| pathname_flow.rb:104:8:104:8 | b : | semmle.label | b : |
| pathname_flow.rb:104:8:104:17 | call to realpath | semmle.label | call to realpath |
| pathname_flow.rb:107:8:107:8 | c : | semmle.label | c : |
| pathname_flow.rb:107:8:107:17 | call to realpath | semmle.label | call to realpath |
| pathname_flow.rb:109:7:109:7 | a : | semmle.label | a : |
| pathname_flow.rb:109:7:109:16 | call to basename : | semmle.label | call to basename : |
| pathname_flow.rb:110:8:110:8 | d : | semmle.label | d : |
| pathname_flow.rb:110:8:110:17 | call to realpath | semmle.label | call to realpath |
| pathname_flow.rb:112:7:112:7 | a : | semmle.label | a : |
| pathname_flow.rb:112:7:112:17 | call to cleanpath : | semmle.label | call to cleanpath : |
| pathname_flow.rb:113:8:113:8 | e : | semmle.label | e : |
| pathname_flow.rb:113:8:113:17 | call to realpath | semmle.label | call to realpath |
| pathname_flow.rb:115:7:115:7 | a : | semmle.label | a : |
| pathname_flow.rb:115:7:115:19 | call to expand_path : | semmle.label | call to expand_path : |
| pathname_flow.rb:116:8:116:8 | f : | semmle.label | f : |
| pathname_flow.rb:116:8:116:17 | call to realpath | semmle.label | call to realpath |
| pathname_flow.rb:118:7:118:7 | a : | semmle.label | a : |
| pathname_flow.rb:118:7:118:19 | call to join : | semmle.label | call to join : |
| pathname_flow.rb:119:8:119:8 | g : | semmle.label | g : |
| pathname_flow.rb:119:8:119:17 | call to realpath | semmle.label | call to realpath |
| pathname_flow.rb:121:7:121:7 | a : | semmle.label | a : |
| pathname_flow.rb:121:7:121:16 | call to realpath : | semmle.label | call to realpath : |
| pathname_flow.rb:122:8:122:8 | h : | semmle.label | h : |
| pathname_flow.rb:122:8:122:17 | call to realpath | semmle.label | call to realpath |
| pathname_flow.rb:124:7:124:7 | a : | semmle.label | a : |
| pathname_flow.rb:124:7:124:38 | call to relative_path_from : | semmle.label | call to relative_path_from : |
| pathname_flow.rb:125:8:125:8 | i : | semmle.label | i : |
| pathname_flow.rb:125:8:125:17 | call to realpath | semmle.label | call to realpath |
| pathname_flow.rb:127:7:127:7 | a : | semmle.label | a : |
| pathname_flow.rb:127:7:127:25 | call to sub : | semmle.label | call to sub : |
| pathname_flow.rb:128:8:128:8 | j : | semmle.label | j : |
| pathname_flow.rb:128:8:128:17 | call to realpath | semmle.label | call to realpath |
| pathname_flow.rb:130:7:130:7 | a : | semmle.label | a : |
| pathname_flow.rb:130:7:130:23 | call to sub_ext : | semmle.label | call to sub_ext : |
| pathname_flow.rb:131:8:131:8 | k : | semmle.label | k : |
| pathname_flow.rb:131:8:131:17 | call to realpath | semmle.label | call to realpath |
| pathname_flow.rb:133:7:133:7 | a : | semmle.label | a : |
| pathname_flow.rb:133:7:133:15 | call to to_path : | semmle.label | call to to_path : |
| pathname_flow.rb:134:8:134:8 | l : | semmle.label | l : |
| pathname_flow.rb:134:8:134:17 | call to realpath | semmle.label | call to realpath |
subpaths
#select

View File

@@ -0,0 +1,11 @@
/**
* @kind path-problem
*/
import ruby
import TestUtilities.InlineFlowTest
import PathGraph
from DataFlow::PathNode source, DataFlow::PathNode sink, DefaultValueFlowConf conf
where conf.hasFlowPath(source, sink)
select sink, source, sink, "$@", source, source.toString()

View File

@@ -0,0 +1,135 @@
require 'pathname'
def m_new
pn = Pathname.new(source 'a')
sink pn # $ hasTaintFlow=a
end
def m_plus
a = Pathname.new(source 'a')
b = Pathname.new(source 'b')
sink(a + b) # $ hasTaintFlow=a $ hasTaintFlow=b
end
def m_dirname
pn = Pathname.new(source 'a')
sink pn.dirname # $ hasTaintFlow=a
end
def m_each_filename
a = Pathname.new(source 'a')
a.each_filename do |x|
sink x # $ hasTaintFlow=a
end
end
def m_expand_path
a = Pathname.new(source 'a')
sink a.expand_path() # $ hasTaintFlow=a
end
def m_join
a = Pathname.new(source 'a')
b = Pathname.new('foo')
c = Pathname.new(source 'c')
sink a.join(b, c) # $ hasTaintFlow=a $ hasTaintFlow=c
end
def m_parent
a = Pathname.new(source 'a')
sink a.parent() # $ hasTaintFlow=a
end
def m_realpath
a = Pathname.new(source 'a')
sink a.realpath() # $ hasTaintFlow=a
end
def m_relative_path_from
a = Pathname.new(source 'a')
sink a.relative_path_from('/foo/bar') # $ hasTaintFlow=a
end
def m_to_path
a = Pathname.new(source 'a')
sink a.to_path # $ hasTaintFlow=a
end
def m_to_s
a = Pathname.new(source 'a')
sink a.to_s # $ hasTaintFlow=a
end
def m_plus
a = Pathname.new(source 'a')
b = a + 'foo'
sink b # $ hasTaintFlow=a
end
def m_slash
a = Pathname.new(source 'a')
b = a / 'foo'
sink b # $ hasTaintFlow=a
end
def m_basename
a = Pathname.new(source 'a')
b = a.basename
sink b # $ hasTaintFlow=a
end
def m_cleanpath
a = Pathname.new(source 'a')
b = a.cleanpath
sink b # $ hasTaintFlow=a
end
def m_sub
a = Pathname.new(source 'a')
b = a.sub('foo', 'bar')
sink b # $ hasTaintFlow=a
end
def m_sub_ext
a = Pathname.new(source 'a')
b = a.sub_ext('.txt')
sink b # $ hasTaintFlow=a
end
# Test flow through intermediate pathnames
def intermediate_pathnames
a = Pathname.new(source 'a')
b = a + 'foo'
sink b.realpath # $ hasTaintFlow=a
c = a / 'foo'
sink c.realpath # $ hasTaintFlow=a
d = a.basename
sink d.realpath # $ hasTaintFlow=a
e = a.cleanpath
sink e.realpath # $ hasTaintFlow=a
f = a.expand_path
sink f.realpath # $ hasTaintFlow=a
g = a.join('foo')
sink g.realpath # $ hasTaintFlow=a
h = a.realpath
sink h.realpath # $ hasTaintFlow=a
i = a.relative_path_from('/foo/bar')
sink i.realpath # $ hasTaintFlow=a
j = a.sub('foo', 'bar')
sink j.realpath # $ hasTaintFlow=a
k = a.sub_ext('.txt')
sink k.realpath # $ hasTaintFlow=a
l = a.to_path
sink l.realpath # $ hasTaintFlow=a
end

View File

@@ -19,19 +19,19 @@ edges
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:37:36:37:42 | tainted |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:37:36:37:42 | tainted |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:51:24:51:30 | tainted : |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:54:22:54:28 | tainted : |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:55:17:55:23 | tainted : |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:57:27:57:33 | tainted : |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:61:32:61:38 | tainted : |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:63:23:63:29 | tainted : |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:102:16:102:22 | tainted : |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:108:14:108:20 | tainted : |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:111:16:111:22 | tainted |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:111:16:111:22 | tainted |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:112:21:112:27 | tainted |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:112:21:112:27 | tainted |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:115:26:115:32 | tainted |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:115:26:115:32 | tainted |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:56:22:56:28 | tainted : |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:57:17:57:23 | tainted : |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:59:27:59:33 | tainted : |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:63:32:63:38 | tainted : |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:65:23:65:29 | tainted : |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:104:16:104:22 | tainted : |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:110:14:110:20 | tainted : |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:113:16:113:22 | tainted |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:113:16:113:22 | tainted |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:114:21:114:27 | tainted |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:114:21:114:27 | tainted |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:117:26:117:32 | tainted |
| summaries.rb:1:11:1:36 | call to identity : | summaries.rb:117:26:117:32 | tainted |
| summaries.rb:1:20:1:36 | call to source : | summaries.rb:1:11:1:36 | call to identity : |
| summaries.rb:1:20:1:36 | call to source : | summaries.rb:1:11:1:36 | call to identity : |
| summaries.rb:4:12:7:3 | call to apply_block : | summaries.rb:9:6:9:13 | tainted2 |
@@ -64,55 +64,58 @@ edges
| summaries.rb:44:8:44:8 | t : | summaries.rb:44:8:44:27 | call to matchedByNameRcv |
| summaries.rb:48:24:48:41 | call to source : | summaries.rb:48:8:48:42 | call to preserveTaint |
| summaries.rb:51:24:51:30 | tainted : | summaries.rb:51:6:51:31 | call to namedArg |
| summaries.rb:54:22:54:28 | tainted : | summaries.rb:54:6:54:29 | call to anyArg |
| summaries.rb:55:17:55:23 | tainted : | summaries.rb:55:6:55:24 | call to anyArg |
| summaries.rb:57:27:57:33 | tainted : | summaries.rb:57:6:57:34 | call to anyNamedArg |
| summaries.rb:61:32:61:38 | tainted : | summaries.rb:61:6:61:39 | call to anyPositionFromOne |
| summaries.rb:63:23:63:29 | tainted : | summaries.rb:63:40:63:40 | x : |
| summaries.rb:63:40:63:40 | x : | summaries.rb:64:8:64:8 | x |
| summaries.rb:71:24:71:53 | call to source : | summaries.rb:71:8:71:54 | call to preserveTaint |
| summaries.rb:74:26:74:56 | call to source : | summaries.rb:74:8:74:57 | call to preserveTaint |
| summaries.rb:77:15:77:29 | call to source : | summaries.rb:79:6:79:6 | a [element 1] : |
| summaries.rb:77:15:77:29 | call to source : | summaries.rb:79:6:79:6 | a [element 1] : |
| summaries.rb:77:15:77:29 | call to source : | summaries.rb:81:5:81:5 | a [element 1] : |
| summaries.rb:77:15:77:29 | call to source : | summaries.rb:81:5:81:5 | a [element 1] : |
| summaries.rb:77:32:77:46 | call to source : | summaries.rb:80:6:80:6 | a [element 2] : |
| summaries.rb:77:32:77:46 | call to source : | summaries.rb:80:6:80:6 | a [element 2] : |
| summaries.rb:77:32:77:46 | call to source : | summaries.rb:85:1:85:1 | a [element 2] : |
| summaries.rb:77:32:77:46 | call to source : | summaries.rb:85:1:85:1 | a [element 2] : |
| summaries.rb:79:6:79:6 | a [element 1] : | summaries.rb:79:6:79:9 | ...[...] |
| summaries.rb:79:6:79:6 | a [element 1] : | summaries.rb:79:6:79:9 | ...[...] |
| summaries.rb:80:6:80:6 | a [element 2] : | summaries.rb:80:6:80:9 | ...[...] |
| summaries.rb:80:6:80:6 | a [element 2] : | summaries.rb:80:6:80:9 | ...[...] |
| summaries.rb:81:5:81:5 | a [element 1] : | summaries.rb:81:5:81:22 | call to withElementOne [element 1] : |
| summaries.rb:81:5:81:5 | a [element 1] : | summaries.rb:81:5:81:22 | call to withElementOne [element 1] : |
| summaries.rb:81:5:81:22 | call to withElementOne [element 1] : | summaries.rb:83:6:83:6 | b [element 1] : |
| summaries.rb:81:5:81:22 | call to withElementOne [element 1] : | summaries.rb:83:6:83:6 | b [element 1] : |
| summaries.rb:83:6:83:6 | b [element 1] : | summaries.rb:83:6:83:9 | ...[...] |
| summaries.rb:83:6:83:6 | b [element 1] : | summaries.rb:83:6:83:9 | ...[...] |
| summaries.rb:85:1:85:1 | [post] a [element 2] : | summaries.rb:88:6:88:6 | a [element 2] : |
| summaries.rb:85:1:85:1 | [post] a [element 2] : | summaries.rb:88:6:88:6 | a [element 2] : |
| summaries.rb:85:1:85:1 | a [element 2] : | summaries.rb:85:1:85:1 | [post] a [element 2] : |
| summaries.rb:85:1:85:1 | a [element 2] : | summaries.rb:85:1:85:1 | [post] a [element 2] : |
| summaries.rb:88:6:88:6 | a [element 2] : | summaries.rb:88:6:88:9 | ...[...] |
| summaries.rb:88:6:88:6 | a [element 2] : | summaries.rb:88:6:88:9 | ...[...] |
| summaries.rb:91:1:91:1 | [post] x [@value] : | summaries.rb:92:6:92:6 | x [@value] : |
| summaries.rb:91:1:91:1 | [post] x [@value] : | summaries.rb:92:6:92:6 | x [@value] : |
| summaries.rb:91:13:91:26 | call to source : | summaries.rb:91:1:91:1 | [post] x [@value] : |
| summaries.rb:91:13:91:26 | call to source : | summaries.rb:91:1:91:1 | [post] x [@value] : |
| summaries.rb:92:6:92:6 | x [@value] : | summaries.rb:92:6:92:16 | call to get_value |
| summaries.rb:92:6:92:6 | x [@value] : | summaries.rb:92:6:92:16 | call to get_value |
| summaries.rb:102:16:102:22 | [post] tainted : | summaries.rb:108:14:108:20 | tainted : |
| summaries.rb:102:16:102:22 | [post] tainted : | summaries.rb:111:16:111:22 | tainted |
| summaries.rb:102:16:102:22 | [post] tainted : | summaries.rb:112:21:112:27 | tainted |
| summaries.rb:102:16:102:22 | [post] tainted : | summaries.rb:115:26:115:32 | tainted |
| summaries.rb:102:16:102:22 | tainted : | summaries.rb:102:16:102:22 | [post] tainted : |
| summaries.rb:102:16:102:22 | tainted : | summaries.rb:102:25:102:25 | [post] y : |
| summaries.rb:102:16:102:22 | tainted : | summaries.rb:102:33:102:33 | [post] z : |
| summaries.rb:102:25:102:25 | [post] y : | summaries.rb:104:6:104:6 | y |
| summaries.rb:102:33:102:33 | [post] z : | summaries.rb:105:6:105:6 | z |
| summaries.rb:108:1:108:1 | [post] x : | summaries.rb:109:6:109:6 | x |
| summaries.rb:108:14:108:20 | tainted : | summaries.rb:108:1:108:1 | [post] x : |
| summaries.rb:53:15:53:31 | call to source : | summaries.rb:54:21:54:24 | args [element :foo] : |
| summaries.rb:54:19:54:24 | ** ... [element :foo] : | summaries.rb:54:6:54:25 | call to namedArg |
| summaries.rb:54:21:54:24 | args [element :foo] : | summaries.rb:54:19:54:24 | ** ... [element :foo] : |
| summaries.rb:56:22:56:28 | tainted : | summaries.rb:56:6:56:29 | call to anyArg |
| summaries.rb:57:17:57:23 | tainted : | summaries.rb:57:6:57:24 | call to anyArg |
| summaries.rb:59:27:59:33 | tainted : | summaries.rb:59:6:59:34 | call to anyNamedArg |
| summaries.rb:63:32:63:38 | tainted : | summaries.rb:63:6:63:39 | call to anyPositionFromOne |
| summaries.rb:65:23:65:29 | tainted : | summaries.rb:65:40:65:40 | x : |
| summaries.rb:65:40:65:40 | x : | summaries.rb:66:8:66:8 | x |
| summaries.rb:73:24:73:53 | call to source : | summaries.rb:73:8:73:54 | call to preserveTaint |
| summaries.rb:76:26:76:56 | call to source : | summaries.rb:76:8:76:57 | call to preserveTaint |
| summaries.rb:79:15:79:29 | call to source : | summaries.rb:81:6:81:6 | a [element 1] : |
| summaries.rb:79:15:79:29 | call to source : | summaries.rb:81:6:81:6 | a [element 1] : |
| summaries.rb:79:15:79:29 | call to source : | summaries.rb:83:5:83:5 | a [element 1] : |
| summaries.rb:79:15:79:29 | call to source : | summaries.rb:83:5:83:5 | a [element 1] : |
| summaries.rb:79:32:79:46 | call to source : | summaries.rb:82:6:82:6 | a [element 2] : |
| summaries.rb:79:32:79:46 | call to source : | summaries.rb:82:6:82:6 | a [element 2] : |
| summaries.rb:79:32:79:46 | call to source : | summaries.rb:87:1:87:1 | a [element 2] : |
| summaries.rb:79:32:79:46 | call to source : | summaries.rb:87:1:87:1 | a [element 2] : |
| summaries.rb:81:6:81:6 | a [element 1] : | summaries.rb:81:6:81:9 | ...[...] |
| summaries.rb:81:6:81:6 | a [element 1] : | summaries.rb:81:6:81:9 | ...[...] |
| summaries.rb:82:6:82:6 | a [element 2] : | summaries.rb:82:6:82:9 | ...[...] |
| summaries.rb:82:6:82:6 | a [element 2] : | summaries.rb:82:6:82:9 | ...[...] |
| summaries.rb:83:5:83:5 | a [element 1] : | summaries.rb:83:5:83:22 | call to withElementOne [element 1] : |
| summaries.rb:83:5:83:5 | a [element 1] : | summaries.rb:83:5:83:22 | call to withElementOne [element 1] : |
| summaries.rb:83:5:83:22 | call to withElementOne [element 1] : | summaries.rb:85:6:85:6 | b [element 1] : |
| summaries.rb:83:5:83:22 | call to withElementOne [element 1] : | summaries.rb:85:6:85:6 | b [element 1] : |
| summaries.rb:85:6:85:6 | b [element 1] : | summaries.rb:85:6:85:9 | ...[...] |
| summaries.rb:85:6:85:6 | b [element 1] : | summaries.rb:85:6:85:9 | ...[...] |
| summaries.rb:87:1:87:1 | [post] a [element 2] : | summaries.rb:90:6:90:6 | a [element 2] : |
| summaries.rb:87:1:87:1 | [post] a [element 2] : | summaries.rb:90:6:90:6 | a [element 2] : |
| summaries.rb:87:1:87:1 | a [element 2] : | summaries.rb:87:1:87:1 | [post] a [element 2] : |
| summaries.rb:87:1:87:1 | a [element 2] : | summaries.rb:87:1:87:1 | [post] a [element 2] : |
| summaries.rb:90:6:90:6 | a [element 2] : | summaries.rb:90:6:90:9 | ...[...] |
| summaries.rb:90:6:90:6 | a [element 2] : | summaries.rb:90:6:90:9 | ...[...] |
| summaries.rb:93:1:93:1 | [post] x [@value] : | summaries.rb:94:6:94:6 | x [@value] : |
| summaries.rb:93:1:93:1 | [post] x [@value] : | summaries.rb:94:6:94:6 | x [@value] : |
| summaries.rb:93:13:93:26 | call to source : | summaries.rb:93:1:93:1 | [post] x [@value] : |
| summaries.rb:93:13:93:26 | call to source : | summaries.rb:93:1:93:1 | [post] x [@value] : |
| summaries.rb:94:6:94:6 | x [@value] : | summaries.rb:94:6:94:16 | call to get_value |
| summaries.rb:94:6:94:6 | x [@value] : | summaries.rb:94:6:94:16 | call to get_value |
| summaries.rb:104:16:104:22 | [post] tainted : | summaries.rb:110:14:110:20 | tainted : |
| summaries.rb:104:16:104:22 | [post] tainted : | summaries.rb:113:16:113:22 | tainted |
| summaries.rb:104:16:104:22 | [post] tainted : | summaries.rb:114:21:114:27 | tainted |
| summaries.rb:104:16:104:22 | [post] tainted : | summaries.rb:117:26:117:32 | tainted |
| summaries.rb:104:16:104:22 | tainted : | summaries.rb:104:16:104:22 | [post] tainted : |
| summaries.rb:104:16:104:22 | tainted : | summaries.rb:104:25:104:25 | [post] y : |
| summaries.rb:104:16:104:22 | tainted : | summaries.rb:104:33:104:33 | [post] z : |
| summaries.rb:104:25:104:25 | [post] y : | summaries.rb:106:6:106:6 | y |
| summaries.rb:104:33:104:33 | [post] z : | summaries.rb:107:6:107:6 | z |
| summaries.rb:110:1:110:1 | [post] x : | summaries.rb:111:6:111:6 | x |
| summaries.rb:110:14:110:20 | tainted : | summaries.rb:110:1:110:1 | [post] x : |
nodes
| summaries.rb:1:11:1:36 | call to identity : | semmle.label | call to identity : |
| summaries.rb:1:11:1:36 | call to identity : | semmle.label | call to identity : |
@@ -169,72 +172,76 @@ nodes
| summaries.rb:48:24:48:41 | call to source : | semmle.label | call to source : |
| summaries.rb:51:6:51:31 | call to namedArg | semmle.label | call to namedArg |
| summaries.rb:51:24:51:30 | tainted : | semmle.label | tainted : |
| summaries.rb:54:6:54:29 | call to anyArg | semmle.label | call to anyArg |
| summaries.rb:54:22:54:28 | tainted : | semmle.label | tainted : |
| summaries.rb:55:6:55:24 | call to anyArg | semmle.label | call to anyArg |
| summaries.rb:55:17:55:23 | tainted : | semmle.label | tainted : |
| summaries.rb:57:6:57:34 | call to anyNamedArg | semmle.label | call to anyNamedArg |
| summaries.rb:57:27:57:33 | tainted : | semmle.label | tainted : |
| summaries.rb:61:6:61:39 | call to anyPositionFromOne | semmle.label | call to anyPositionFromOne |
| summaries.rb:61:32:61:38 | tainted : | semmle.label | tainted : |
| summaries.rb:63:23:63:29 | tainted : | semmle.label | tainted : |
| summaries.rb:63:40:63:40 | x : | semmle.label | x : |
| summaries.rb:64:8:64:8 | x | semmle.label | x |
| summaries.rb:71:8:71:54 | call to preserveTaint | semmle.label | call to preserveTaint |
| summaries.rb:71:24:71:53 | call to source : | semmle.label | call to source : |
| summaries.rb:74:8:74:57 | call to preserveTaint | semmle.label | call to preserveTaint |
| summaries.rb:74:26:74:56 | call to source : | semmle.label | call to source : |
| summaries.rb:77:15:77:29 | call to source : | semmle.label | call to source : |
| summaries.rb:77:15:77:29 | call to source : | semmle.label | call to source : |
| summaries.rb:77:32:77:46 | call to source : | semmle.label | call to source : |
| summaries.rb:77:32:77:46 | call to source : | semmle.label | call to source : |
| summaries.rb:79:6:79:6 | a [element 1] : | semmle.label | a [element 1] : |
| summaries.rb:79:6:79:6 | a [element 1] : | semmle.label | a [element 1] : |
| summaries.rb:79:6:79:9 | ...[...] | semmle.label | ...[...] |
| summaries.rb:79:6:79:9 | ...[...] | semmle.label | ...[...] |
| summaries.rb:80:6:80:6 | a [element 2] : | semmle.label | a [element 2] : |
| summaries.rb:80:6:80:6 | a [element 2] : | semmle.label | a [element 2] : |
| summaries.rb:80:6:80:9 | ...[...] | semmle.label | ...[...] |
| summaries.rb:80:6:80:9 | ...[...] | semmle.label | ...[...] |
| summaries.rb:81:5:81:5 | a [element 1] : | semmle.label | a [element 1] : |
| summaries.rb:81:5:81:5 | a [element 1] : | semmle.label | a [element 1] : |
| summaries.rb:81:5:81:22 | call to withElementOne [element 1] : | semmle.label | call to withElementOne [element 1] : |
| summaries.rb:81:5:81:22 | call to withElementOne [element 1] : | semmle.label | call to withElementOne [element 1] : |
| summaries.rb:83:6:83:6 | b [element 1] : | semmle.label | b [element 1] : |
| summaries.rb:83:6:83:6 | b [element 1] : | semmle.label | b [element 1] : |
| summaries.rb:83:6:83:9 | ...[...] | semmle.label | ...[...] |
| summaries.rb:83:6:83:9 | ...[...] | semmle.label | ...[...] |
| summaries.rb:85:1:85:1 | [post] a [element 2] : | semmle.label | [post] a [element 2] : |
| summaries.rb:85:1:85:1 | [post] a [element 2] : | semmle.label | [post] a [element 2] : |
| summaries.rb:85:1:85:1 | a [element 2] : | semmle.label | a [element 2] : |
| summaries.rb:85:1:85:1 | a [element 2] : | semmle.label | a [element 2] : |
| summaries.rb:88:6:88:6 | a [element 2] : | semmle.label | a [element 2] : |
| summaries.rb:88:6:88:6 | a [element 2] : | semmle.label | a [element 2] : |
| summaries.rb:88:6:88:9 | ...[...] | semmle.label | ...[...] |
| summaries.rb:88:6:88:9 | ...[...] | semmle.label | ...[...] |
| summaries.rb:91:1:91:1 | [post] x [@value] : | semmle.label | [post] x [@value] : |
| summaries.rb:91:1:91:1 | [post] x [@value] : | semmle.label | [post] x [@value] : |
| summaries.rb:91:13:91:26 | call to source : | semmle.label | call to source : |
| summaries.rb:91:13:91:26 | call to source : | semmle.label | call to source : |
| summaries.rb:92:6:92:6 | x [@value] : | semmle.label | x [@value] : |
| summaries.rb:92:6:92:6 | x [@value] : | semmle.label | x [@value] : |
| summaries.rb:92:6:92:16 | call to get_value | semmle.label | call to get_value |
| summaries.rb:92:6:92:16 | call to get_value | semmle.label | call to get_value |
| summaries.rb:102:16:102:22 | [post] tainted : | semmle.label | [post] tainted : |
| summaries.rb:102:16:102:22 | tainted : | semmle.label | tainted : |
| summaries.rb:102:25:102:25 | [post] y : | semmle.label | [post] y : |
| summaries.rb:102:33:102:33 | [post] z : | semmle.label | [post] z : |
| summaries.rb:104:6:104:6 | y | semmle.label | y |
| summaries.rb:105:6:105:6 | z | semmle.label | z |
| summaries.rb:108:1:108:1 | [post] x : | semmle.label | [post] x : |
| summaries.rb:108:14:108:20 | tainted : | semmle.label | tainted : |
| summaries.rb:109:6:109:6 | x | semmle.label | x |
| summaries.rb:111:16:111:22 | tainted | semmle.label | tainted |
| summaries.rb:111:16:111:22 | tainted | semmle.label | tainted |
| summaries.rb:112:21:112:27 | tainted | semmle.label | tainted |
| summaries.rb:112:21:112:27 | tainted | semmle.label | tainted |
| summaries.rb:115:26:115:32 | tainted | semmle.label | tainted |
| summaries.rb:115:26:115:32 | tainted | semmle.label | tainted |
| summaries.rb:53:15:53:31 | call to source : | semmle.label | call to source : |
| summaries.rb:54:6:54:25 | call to namedArg | semmle.label | call to namedArg |
| summaries.rb:54:19:54:24 | ** ... [element :foo] : | semmle.label | ** ... [element :foo] : |
| summaries.rb:54:21:54:24 | args [element :foo] : | semmle.label | args [element :foo] : |
| summaries.rb:56:6:56:29 | call to anyArg | semmle.label | call to anyArg |
| summaries.rb:56:22:56:28 | tainted : | semmle.label | tainted : |
| summaries.rb:57:6:57:24 | call to anyArg | semmle.label | call to anyArg |
| summaries.rb:57:17:57:23 | tainted : | semmle.label | tainted : |
| summaries.rb:59:6:59:34 | call to anyNamedArg | semmle.label | call to anyNamedArg |
| summaries.rb:59:27:59:33 | tainted : | semmle.label | tainted : |
| summaries.rb:63:6:63:39 | call to anyPositionFromOne | semmle.label | call to anyPositionFromOne |
| summaries.rb:63:32:63:38 | tainted : | semmle.label | tainted : |
| summaries.rb:65:23:65:29 | tainted : | semmle.label | tainted : |
| summaries.rb:65:40:65:40 | x : | semmle.label | x : |
| summaries.rb:66:8:66:8 | x | semmle.label | x |
| summaries.rb:73:8:73:54 | call to preserveTaint | semmle.label | call to preserveTaint |
| summaries.rb:73:24:73:53 | call to source : | semmle.label | call to source : |
| summaries.rb:76:8:76:57 | call to preserveTaint | semmle.label | call to preserveTaint |
| summaries.rb:76:26:76:56 | call to source : | semmle.label | call to source : |
| summaries.rb:79:15:79:29 | call to source : | semmle.label | call to source : |
| summaries.rb:79:15:79:29 | call to source : | semmle.label | call to source : |
| summaries.rb:79:32:79:46 | call to source : | semmle.label | call to source : |
| summaries.rb:79:32:79:46 | call to source : | semmle.label | call to source : |
| summaries.rb:81:6:81:6 | a [element 1] : | semmle.label | a [element 1] : |
| summaries.rb:81:6:81:6 | a [element 1] : | semmle.label | a [element 1] : |
| summaries.rb:81:6:81:9 | ...[...] | semmle.label | ...[...] |
| summaries.rb:81:6:81:9 | ...[...] | semmle.label | ...[...] |
| summaries.rb:82:6:82:6 | a [element 2] : | semmle.label | a [element 2] : |
| summaries.rb:82:6:82:6 | a [element 2] : | semmle.label | a [element 2] : |
| summaries.rb:82:6:82:9 | ...[...] | semmle.label | ...[...] |
| summaries.rb:82:6:82:9 | ...[...] | semmle.label | ...[...] |
| summaries.rb:83:5:83:5 | a [element 1] : | semmle.label | a [element 1] : |
| summaries.rb:83:5:83:5 | a [element 1] : | semmle.label | a [element 1] : |
| summaries.rb:83:5:83:22 | call to withElementOne [element 1] : | semmle.label | call to withElementOne [element 1] : |
| summaries.rb:83:5:83:22 | call to withElementOne [element 1] : | semmle.label | call to withElementOne [element 1] : |
| summaries.rb:85:6:85:6 | b [element 1] : | semmle.label | b [element 1] : |
| summaries.rb:85:6:85:6 | b [element 1] : | semmle.label | b [element 1] : |
| summaries.rb:85:6:85:9 | ...[...] | semmle.label | ...[...] |
| summaries.rb:85:6:85:9 | ...[...] | semmle.label | ...[...] |
| summaries.rb:87:1:87:1 | [post] a [element 2] : | semmle.label | [post] a [element 2] : |
| summaries.rb:87:1:87:1 | [post] a [element 2] : | semmle.label | [post] a [element 2] : |
| summaries.rb:87:1:87:1 | a [element 2] : | semmle.label | a [element 2] : |
| summaries.rb:87:1:87:1 | a [element 2] : | semmle.label | a [element 2] : |
| summaries.rb:90:6:90:6 | a [element 2] : | semmle.label | a [element 2] : |
| summaries.rb:90:6:90:6 | a [element 2] : | semmle.label | a [element 2] : |
| summaries.rb:90:6:90:9 | ...[...] | semmle.label | ...[...] |
| summaries.rb:90:6:90:9 | ...[...] | semmle.label | ...[...] |
| summaries.rb:93:1:93:1 | [post] x [@value] : | semmle.label | [post] x [@value] : |
| summaries.rb:93:1:93:1 | [post] x [@value] : | semmle.label | [post] x [@value] : |
| summaries.rb:93:13:93:26 | call to source : | semmle.label | call to source : |
| summaries.rb:93:13:93:26 | call to source : | semmle.label | call to source : |
| summaries.rb:94:6:94:6 | x [@value] : | semmle.label | x [@value] : |
| summaries.rb:94:6:94:6 | x [@value] : | semmle.label | x [@value] : |
| summaries.rb:94:6:94:16 | call to get_value | semmle.label | call to get_value |
| summaries.rb:94:6:94:16 | call to get_value | semmle.label | call to get_value |
| summaries.rb:104:16:104:22 | [post] tainted : | semmle.label | [post] tainted : |
| summaries.rb:104:16:104:22 | tainted : | semmle.label | tainted : |
| summaries.rb:104:25:104:25 | [post] y : | semmle.label | [post] y : |
| summaries.rb:104:33:104:33 | [post] z : | semmle.label | [post] z : |
| summaries.rb:106:6:106:6 | y | semmle.label | y |
| summaries.rb:107:6:107:6 | z | semmle.label | z |
| summaries.rb:110:1:110:1 | [post] x : | semmle.label | [post] x : |
| summaries.rb:110:14:110:20 | tainted : | semmle.label | tainted : |
| summaries.rb:111:6:111:6 | x | semmle.label | x |
| summaries.rb:113:16:113:22 | tainted | semmle.label | tainted |
| summaries.rb:113:16:113:22 | tainted | semmle.label | tainted |
| summaries.rb:114:21:114:27 | tainted | semmle.label | tainted |
| summaries.rb:114:21:114:27 | tainted | semmle.label | tainted |
| summaries.rb:117:26:117:32 | tainted | semmle.label | tainted |
| summaries.rb:117:26:117:32 | tainted | semmle.label | tainted |
subpaths
invalidSpecComponent
#select
@@ -265,32 +272,33 @@ invalidSpecComponent
| summaries.rb:44:8:44:27 | call to matchedByNameRcv | summaries.rb:40:7:40:17 | call to source : | summaries.rb:44:8:44:27 | call to matchedByNameRcv | $@ | summaries.rb:40:7:40:17 | call to source : | call to source : |
| summaries.rb:48:8:48:42 | call to preserveTaint | summaries.rb:48:24:48:41 | call to source : | summaries.rb:48:8:48:42 | call to preserveTaint | $@ | summaries.rb:48:24:48:41 | call to source : | call to source : |
| summaries.rb:51:6:51:31 | call to namedArg | summaries.rb:1:20:1:36 | call to source : | summaries.rb:51:6:51:31 | call to namedArg | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:54:6:54:29 | call to anyArg | summaries.rb:1:20:1:36 | call to source : | summaries.rb:54:6:54:29 | call to anyArg | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:55:6:55:24 | call to anyArg | summaries.rb:1:20:1:36 | call to source : | summaries.rb:55:6:55:24 | call to anyArg | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:57:6:57:34 | call to anyNamedArg | summaries.rb:1:20:1:36 | call to source : | summaries.rb:57:6:57:34 | call to anyNamedArg | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:61:6:61:39 | call to anyPositionFromOne | summaries.rb:1:20:1:36 | call to source : | summaries.rb:61:6:61:39 | call to anyPositionFromOne | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:64:8:64:8 | x | summaries.rb:1:20:1:36 | call to source : | summaries.rb:64:8:64:8 | x | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:71:8:71:54 | call to preserveTaint | summaries.rb:71:24:71:53 | call to source : | summaries.rb:71:8:71:54 | call to preserveTaint | $@ | summaries.rb:71:24:71:53 | call to source : | call to source : |
| summaries.rb:74:8:74:57 | call to preserveTaint | summaries.rb:74:26:74:56 | call to source : | summaries.rb:74:8:74:57 | call to preserveTaint | $@ | summaries.rb:74:26:74:56 | call to source : | call to source : |
| summaries.rb:79:6:79:9 | ...[...] | summaries.rb:77:15:77:29 | call to source : | summaries.rb:79:6:79:9 | ...[...] | $@ | summaries.rb:77:15:77:29 | call to source : | call to source : |
| summaries.rb:79:6:79:9 | ...[...] | summaries.rb:77:15:77:29 | call to source : | summaries.rb:79:6:79:9 | ...[...] | $@ | summaries.rb:77:15:77:29 | call to source : | call to source : |
| summaries.rb:80:6:80:9 | ...[...] | summaries.rb:77:32:77:46 | call to source : | summaries.rb:80:6:80:9 | ...[...] | $@ | summaries.rb:77:32:77:46 | call to source : | call to source : |
| summaries.rb:80:6:80:9 | ...[...] | summaries.rb:77:32:77:46 | call to source : | summaries.rb:80:6:80:9 | ...[...] | $@ | summaries.rb:77:32:77:46 | call to source : | call to source : |
| summaries.rb:83:6:83:9 | ...[...] | summaries.rb:77:15:77:29 | call to source : | summaries.rb:83:6:83:9 | ...[...] | $@ | summaries.rb:77:15:77:29 | call to source : | call to source : |
| summaries.rb:83:6:83:9 | ...[...] | summaries.rb:77:15:77:29 | call to source : | summaries.rb:83:6:83:9 | ...[...] | $@ | summaries.rb:77:15:77:29 | call to source : | call to source : |
| summaries.rb:88:6:88:9 | ...[...] | summaries.rb:77:32:77:46 | call to source : | summaries.rb:88:6:88:9 | ...[...] | $@ | summaries.rb:77:32:77:46 | call to source : | call to source : |
| summaries.rb:88:6:88:9 | ...[...] | summaries.rb:77:32:77:46 | call to source : | summaries.rb:88:6:88:9 | ...[...] | $@ | summaries.rb:77:32:77:46 | call to source : | call to source : |
| summaries.rb:92:6:92:16 | call to get_value | summaries.rb:91:13:91:26 | call to source : | summaries.rb:92:6:92:16 | call to get_value | $@ | summaries.rb:91:13:91:26 | call to source : | call to source : |
| summaries.rb:92:6:92:16 | call to get_value | summaries.rb:91:13:91:26 | call to source : | summaries.rb:92:6:92:16 | call to get_value | $@ | summaries.rb:91:13:91:26 | call to source : | call to source : |
| summaries.rb:104:6:104:6 | y | summaries.rb:1:20:1:36 | call to source : | summaries.rb:104:6:104:6 | y | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:105:6:105:6 | z | summaries.rb:1:20:1:36 | call to source : | summaries.rb:105:6:105:6 | z | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:109:6:109:6 | x | summaries.rb:1:20:1:36 | call to source : | summaries.rb:109:6:109:6 | x | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:111:16:111:22 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:111:16:111:22 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:111:16:111:22 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:111:16:111:22 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:112:21:112:27 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:112:21:112:27 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:112:21:112:27 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:112:21:112:27 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:115:26:115:32 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:115:26:115:32 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:115:26:115:32 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:115:26:115:32 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:54:6:54:25 | call to namedArg | summaries.rb:53:15:53:31 | call to source : | summaries.rb:54:6:54:25 | call to namedArg | $@ | summaries.rb:53:15:53:31 | call to source : | call to source : |
| summaries.rb:56:6:56:29 | call to anyArg | summaries.rb:1:20:1:36 | call to source : | summaries.rb:56:6:56:29 | call to anyArg | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:57:6:57:24 | call to anyArg | summaries.rb:1:20:1:36 | call to source : | summaries.rb:57:6:57:24 | call to anyArg | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:59:6:59:34 | call to anyNamedArg | summaries.rb:1:20:1:36 | call to source : | summaries.rb:59:6:59:34 | call to anyNamedArg | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:63:6:63:39 | call to anyPositionFromOne | summaries.rb:1:20:1:36 | call to source : | summaries.rb:63:6:63:39 | call to anyPositionFromOne | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:66:8:66:8 | x | summaries.rb:1:20:1:36 | call to source : | summaries.rb:66:8:66:8 | x | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:73:8:73:54 | call to preserveTaint | summaries.rb:73:24:73:53 | call to source : | summaries.rb:73:8:73:54 | call to preserveTaint | $@ | summaries.rb:73:24:73:53 | call to source : | call to source : |
| summaries.rb:76:8:76:57 | call to preserveTaint | summaries.rb:76:26:76:56 | call to source : | summaries.rb:76:8:76:57 | call to preserveTaint | $@ | summaries.rb:76:26:76:56 | call to source : | call to source : |
| summaries.rb:81:6:81:9 | ...[...] | summaries.rb:79:15:79:29 | call to source : | summaries.rb:81:6:81:9 | ...[...] | $@ | summaries.rb:79:15:79:29 | call to source : | call to source : |
| summaries.rb:81:6:81:9 | ...[...] | summaries.rb:79:15:79:29 | call to source : | summaries.rb:81:6:81:9 | ...[...] | $@ | summaries.rb:79:15:79:29 | call to source : | call to source : |
| summaries.rb:82:6:82:9 | ...[...] | summaries.rb:79:32:79:46 | call to source : | summaries.rb:82:6:82:9 | ...[...] | $@ | summaries.rb:79:32:79:46 | call to source : | call to source : |
| summaries.rb:82:6:82:9 | ...[...] | summaries.rb:79:32:79:46 | call to source : | summaries.rb:82:6:82:9 | ...[...] | $@ | summaries.rb:79:32:79:46 | call to source : | call to source : |
| summaries.rb:85:6:85:9 | ...[...] | summaries.rb:79:15:79:29 | call to source : | summaries.rb:85:6:85:9 | ...[...] | $@ | summaries.rb:79:15:79:29 | call to source : | call to source : |
| summaries.rb:85:6:85:9 | ...[...] | summaries.rb:79:15:79:29 | call to source : | summaries.rb:85:6:85:9 | ...[...] | $@ | summaries.rb:79:15:79:29 | call to source : | call to source : |
| summaries.rb:90:6:90:9 | ...[...] | summaries.rb:79:32:79:46 | call to source : | summaries.rb:90:6:90:9 | ...[...] | $@ | summaries.rb:79:32:79:46 | call to source : | call to source : |
| summaries.rb:90:6:90:9 | ...[...] | summaries.rb:79:32:79:46 | call to source : | summaries.rb:90:6:90:9 | ...[...] | $@ | summaries.rb:79:32:79:46 | call to source : | call to source : |
| summaries.rb:94:6:94:16 | call to get_value | summaries.rb:93:13:93:26 | call to source : | summaries.rb:94:6:94:16 | call to get_value | $@ | summaries.rb:93:13:93:26 | call to source : | call to source : |
| summaries.rb:94:6:94:16 | call to get_value | summaries.rb:93:13:93:26 | call to source : | summaries.rb:94:6:94:16 | call to get_value | $@ | summaries.rb:93:13:93:26 | call to source : | call to source : |
| summaries.rb:106:6:106:6 | y | summaries.rb:1:20:1:36 | call to source : | summaries.rb:106:6:106:6 | y | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:107:6:107:6 | z | summaries.rb:1:20:1:36 | call to source : | summaries.rb:107:6:107:6 | z | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:111:6:111:6 | x | summaries.rb:1:20:1:36 | call to source : | summaries.rb:111:6:111:6 | x | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:113:16:113:22 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:113:16:113:22 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:113:16:113:22 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:113:16:113:22 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:114:21:114:27 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:114:21:114:27 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:114:21:114:27 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:114:21:114:27 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:117:26:117:32 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:117:26:117:32 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
| summaries.rb:117:26:117:32 | tainted | summaries.rb:1:20:1:36 | call to source : | summaries.rb:117:26:117:32 | tainted | $@ | summaries.rb:1:20:1:36 | call to source : | call to source : |
warning
| CSV type row should have 5 columns but has 2: test;TooFewColumns |
| CSV type row should have 5 columns but has 8: test;TooManyColumns;;;Member[Foo].Instance;too;many;columns |

View File

@@ -132,7 +132,7 @@ class CustomValueSink extends DefaultValueFlowConf {
override predicate isSink(DataFlow::Node sink) {
super.isSink(sink)
or
sink = ModelOutput::getASinkNode("test-sink").getARhs()
sink = ModelOutput::getASinkNode("test-sink").asSink()
}
}
@@ -140,7 +140,7 @@ class CustomTaintSink extends DefaultTaintFlowConf {
override predicate isSink(DataFlow::Node sink) {
super.isSink(sink)
or
sink = ModelOutput::getASinkNode("test-sink").getARhs()
sink = ModelOutput::getASinkNode("test-sink").asSink()
}
}

View File

@@ -50,6 +50,8 @@ end
sink(Foo.namedArg(foo: tainted)) # $ hasTaintFlow=tainted
sink(Foo.namedArg(tainted))
args = { foo: source("tainted") }
sink(Foo.namedArg(**args)) # $ hasTaintFlow=tainted
sink(Foo.anyArg(foo: tainted)) # $ hasTaintFlow=tainted
sink(Foo.anyArg(tainted)) # $ hasTaintFlow=tainted

View File

@@ -1,60 +1,93 @@
actionControllerControllerClasses
| ActiveRecord.rb:23:1:39:3 | FooController |
| ActiveRecord.rb:41:1:64:3 | BarController |
| ActiveRecord.rb:66:1:70:3 | BazController |
| active_record/ActiveRecord.rb:23:1:39:3 | FooController |
| active_record/ActiveRecord.rb:41:1:64:3 | BarController |
| active_record/ActiveRecord.rb:66:1:94:3 | BazController |
| active_record/ActiveRecord.rb:96:1:104:3 | AnnotatedController |
| app/controllers/comments_controller.rb:1:1:7:3 | CommentsController |
| app/controllers/foo/bars_controller.rb:3:1:31:3 | BarsController |
| app/controllers/foo/bars_controller.rb:3:1:39:3 | BarsController |
| app/controllers/photos_controller.rb:1:1:4:3 | PhotosController |
| app/controllers/posts_controller.rb:1:1:10:3 | PostsController |
| app/controllers/users/notifications_controller.rb:2:3:5:5 | NotificationsController |
actionControllerActionMethods
| ActiveRecord.rb:27:3:38:5 | some_request_handler |
| ActiveRecord.rb:42:3:47:5 | some_other_request_handler |
| ActiveRecord.rb:49:3:63:5 | safe_paths |
| ActiveRecord.rb:67:3:69:5 | yet_another_handler |
| active_record/ActiveRecord.rb:27:3:38:5 | some_request_handler |
| active_record/ActiveRecord.rb:42:3:47:5 | some_other_request_handler |
| active_record/ActiveRecord.rb:49:3:63:5 | safe_paths |
| active_record/ActiveRecord.rb:67:3:69:5 | yet_another_handler |
| active_record/ActiveRecord.rb:71:3:73:5 | create1 |
| active_record/ActiveRecord.rb:75:3:77:5 | create2 |
| active_record/ActiveRecord.rb:79:3:81:5 | create3 |
| active_record/ActiveRecord.rb:83:3:85:5 | update1 |
| active_record/ActiveRecord.rb:87:3:89:5 | update2 |
| active_record/ActiveRecord.rb:91:3:93:5 | update3 |
| active_record/ActiveRecord.rb:97:3:99:5 | index |
| active_record/ActiveRecord.rb:101:3:103:5 | unsafe_action |
| app/controllers/comments_controller.rb:2:3:3:5 | index |
| app/controllers/comments_controller.rb:5:3:6:5 | show |
| app/controllers/foo/bars_controller.rb:5:3:7:5 | index |
| app/controllers/foo/bars_controller.rb:9:3:18:5 | show_debug |
| app/controllers/foo/bars_controller.rb:20:3:24:5 | show |
| app/controllers/foo/bars_controller.rb:26:3:28:5 | go_back |
| app/controllers/foo/bars_controller.rb:30:3:32:5 | go_back_2 |
| app/controllers/photos_controller.rb:2:3:3:5 | show |
| app/controllers/posts_controller.rb:2:3:3:5 | index |
| app/controllers/posts_controller.rb:5:3:6:5 | show |
| app/controllers/posts_controller.rb:8:3:9:5 | upvote |
| app/controllers/users/notifications_controller.rb:3:5:4:7 | mark_as_read |
paramsCalls
| ActiveRecord.rb:28:30:28:35 | call to params |
| ActiveRecord.rb:29:29:29:34 | call to params |
| ActiveRecord.rb:30:31:30:36 | call to params |
| ActiveRecord.rb:32:21:32:26 | call to params |
| ActiveRecord.rb:34:34:34:39 | call to params |
| ActiveRecord.rb:35:23:35:28 | call to params |
| ActiveRecord.rb:35:38:35:43 | call to params |
| ActiveRecord.rb:43:10:43:15 | call to params |
| ActiveRecord.rb:50:11:50:16 | call to params |
| ActiveRecord.rb:54:12:54:17 | call to params |
| ActiveRecord.rb:59:12:59:17 | call to params |
| ActiveRecord.rb:62:15:62:20 | call to params |
| ActiveRecord.rb:68:21:68:26 | call to params |
| active_record/ActiveRecord.rb:28:30:28:35 | call to params |
| active_record/ActiveRecord.rb:29:29:29:34 | call to params |
| active_record/ActiveRecord.rb:30:31:30:36 | call to params |
| active_record/ActiveRecord.rb:32:21:32:26 | call to params |
| active_record/ActiveRecord.rb:34:34:34:39 | call to params |
| active_record/ActiveRecord.rb:35:23:35:28 | call to params |
| active_record/ActiveRecord.rb:35:38:35:43 | call to params |
| active_record/ActiveRecord.rb:43:10:43:15 | call to params |
| active_record/ActiveRecord.rb:50:11:50:16 | call to params |
| active_record/ActiveRecord.rb:54:12:54:17 | call to params |
| active_record/ActiveRecord.rb:59:12:59:17 | call to params |
| active_record/ActiveRecord.rb:62:15:62:20 | call to params |
| active_record/ActiveRecord.rb:68:21:68:26 | call to params |
| active_record/ActiveRecord.rb:72:18:72:23 | call to params |
| active_record/ActiveRecord.rb:76:24:76:29 | call to params |
| active_record/ActiveRecord.rb:76:49:76:54 | call to params |
| active_record/ActiveRecord.rb:80:25:80:30 | call to params |
| active_record/ActiveRecord.rb:80:50:80:55 | call to params |
| active_record/ActiveRecord.rb:84:21:84:26 | call to params |
| active_record/ActiveRecord.rb:88:27:88:32 | call to params |
| active_record/ActiveRecord.rb:88:52:88:57 | call to params |
| active_record/ActiveRecord.rb:92:28:92:33 | call to params |
| active_record/ActiveRecord.rb:92:53:92:58 | call to params |
| active_record/ActiveRecord.rb:102:59:102:64 | call to params |
| app/controllers/foo/bars_controller.rb:13:21:13:26 | call to params |
| app/controllers/foo/bars_controller.rb:14:10:14:15 | call to params |
| app/controllers/foo/bars_controller.rb:21:21:21:26 | call to params |
| app/controllers/foo/bars_controller.rb:22:10:22:15 | call to params |
| app/views/foo/bars/show.html.erb:5:9:5:14 | call to params |
paramsSources
| ActiveRecord.rb:28:30:28:35 | call to params |
| ActiveRecord.rb:29:29:29:34 | call to params |
| ActiveRecord.rb:30:31:30:36 | call to params |
| ActiveRecord.rb:32:21:32:26 | call to params |
| ActiveRecord.rb:34:34:34:39 | call to params |
| ActiveRecord.rb:35:23:35:28 | call to params |
| ActiveRecord.rb:35:38:35:43 | call to params |
| ActiveRecord.rb:43:10:43:15 | call to params |
| ActiveRecord.rb:50:11:50:16 | call to params |
| ActiveRecord.rb:54:12:54:17 | call to params |
| ActiveRecord.rb:59:12:59:17 | call to params |
| ActiveRecord.rb:62:15:62:20 | call to params |
| ActiveRecord.rb:68:21:68:26 | call to params |
| active_record/ActiveRecord.rb:28:30:28:35 | call to params |
| active_record/ActiveRecord.rb:29:29:29:34 | call to params |
| active_record/ActiveRecord.rb:30:31:30:36 | call to params |
| active_record/ActiveRecord.rb:32:21:32:26 | call to params |
| active_record/ActiveRecord.rb:34:34:34:39 | call to params |
| active_record/ActiveRecord.rb:35:23:35:28 | call to params |
| active_record/ActiveRecord.rb:35:38:35:43 | call to params |
| active_record/ActiveRecord.rb:43:10:43:15 | call to params |
| active_record/ActiveRecord.rb:50:11:50:16 | call to params |
| active_record/ActiveRecord.rb:54:12:54:17 | call to params |
| active_record/ActiveRecord.rb:59:12:59:17 | call to params |
| active_record/ActiveRecord.rb:62:15:62:20 | call to params |
| active_record/ActiveRecord.rb:68:21:68:26 | call to params |
| active_record/ActiveRecord.rb:72:18:72:23 | call to params |
| active_record/ActiveRecord.rb:76:24:76:29 | call to params |
| active_record/ActiveRecord.rb:76:49:76:54 | call to params |
| active_record/ActiveRecord.rb:80:25:80:30 | call to params |
| active_record/ActiveRecord.rb:80:50:80:55 | call to params |
| active_record/ActiveRecord.rb:84:21:84:26 | call to params |
| active_record/ActiveRecord.rb:88:27:88:32 | call to params |
| active_record/ActiveRecord.rb:88:52:88:57 | call to params |
| active_record/ActiveRecord.rb:92:28:92:33 | call to params |
| active_record/ActiveRecord.rb:92:53:92:58 | call to params |
| active_record/ActiveRecord.rb:102:59:102:64 | call to params |
| app/controllers/foo/bars_controller.rb:13:21:13:26 | call to params |
| app/controllers/foo/bars_controller.rb:14:10:14:15 | call to params |
| app/controllers/foo/bars_controller.rb:21:21:21:26 | call to params |
@@ -66,10 +99,12 @@ cookiesSources
| app/controllers/foo/bars_controller.rb:10:27:10:33 | call to cookies |
redirectToCalls
| app/controllers/foo/bars_controller.rb:17:5:17:30 | call to redirect_to |
| app/controllers/foo/bars_controller.rb:27:5:27:39 | call to redirect_back_or_to |
| app/controllers/foo/bars_controller.rb:31:5:31:56 | call to redirect_back |
actionControllerHelperMethods
getAssociatedControllerClasses
| app/controllers/foo/bars_controller.rb:3:1:31:3 | BarsController | app/views/foo/bars/_widget.html.erb:0:0:0:0 | app/views/foo/bars/_widget.html.erb |
| app/controllers/foo/bars_controller.rb:3:1:31:3 | BarsController | app/views/foo/bars/show.html.erb:0:0:0:0 | app/views/foo/bars/show.html.erb |
| app/controllers/foo/bars_controller.rb:3:1:39:3 | BarsController | app/views/foo/bars/_widget.html.erb:0:0:0:0 | app/views/foo/bars/_widget.html.erb |
| app/controllers/foo/bars_controller.rb:3:1:39:3 | BarsController | app/views/foo/bars/show.html.erb:0:0:0:0 | app/views/foo/bars/show.html.erb |
controllerTemplateFiles
| app/controllers/foo/bars_controller.rb:3:1:31:3 | BarsController | app/views/foo/bars/_widget.html.erb:0:0:0:0 | app/views/foo/bars/_widget.html.erb |
| app/controllers/foo/bars_controller.rb:3:1:31:3 | BarsController | app/views/foo/bars/show.html.erb:0:0:0:0 | app/views/foo/bars/show.html.erb |
| app/controllers/foo/bars_controller.rb:3:1:39:3 | BarsController | app/views/foo/bars/_widget.html.erb:0:0:0:0 | app/views/foo/bars/_widget.html.erb |
| app/controllers/foo/bars_controller.rb:3:1:39:3 | BarsController | app/views/foo/bars/show.html.erb:0:0:0:0 | app/views/foo/bars/show.html.erb |

View File

@@ -54,3 +54,15 @@ underscore
| HTTPServerRequest | httpserver_request |
| LotsOfCapitalLetters | lots_of_capital_letters |
| invalid | invalid |
mimeTypeInstances
| action_dispatch/mime_type.rb:2:6:2:28 | Use getMember("Mime").getMethod("fetch").getReturn() |
| action_dispatch/mime_type.rb:3:6:3:32 | Use getMember("Mime").getMember("Type").getMethod("new").getReturn() |
| action_dispatch/mime_type.rb:4:6:4:35 | Use getMember("Mime").getMember("Type").getMethod("lookup").getReturn() |
| action_dispatch/mime_type.rb:5:6:5:43 | Use getMember("Mime").getMember("Type").getMethod("lookup_by_extension").getReturn() |
| action_dispatch/mime_type.rb:6:6:6:47 | Use getMember("Mime").getMember("Type").getMethod("register").getReturn() |
| action_dispatch/mime_type.rb:7:6:7:64 | Use getMember("Mime").getMember("Type").getMethod("register_alias").getReturn() |
mimeTypeMatchRegExpInterpretations
| action_dispatch/mime_type.rb:11:11:11:19 | "foo/bar" |
| action_dispatch/mime_type.rb:12:7:12:15 | "foo/bar" |
| action_dispatch/mime_type.rb:13:11:13:11 | s |
| action_dispatch/mime_type.rb:14:7:14:7 | s |

View File

@@ -1,9 +1,13 @@
private import ruby
private import codeql.ruby.frameworks.ActionDispatch
private import codeql.ruby.frameworks.ActionController
private import codeql.ruby.ApiGraphs
private import codeql.ruby.frameworks.data.ModelsAsData
private import codeql.ruby.DataFlow
private import codeql.ruby.Regexp as RE
query predicate actionDispatchRoutes(
ActionDispatch::Route r, string method, string path, string controller, string action
ActionDispatch::Routing::Route r, string method, string path, string controller, string action
) {
r.getHttpMethod() = method and
r.getPath() = path and
@@ -12,15 +16,25 @@ query predicate actionDispatchRoutes(
}
query predicate actionDispatchControllerMethods(
ActionDispatch::Route r, ActionControllerActionMethod m
ActionDispatch::Routing::Route r, ActionControllerActionMethod m
) {
m.getARoute() = r
}
query predicate underscore(string input, string output) {
output = ActionDispatch::underscore(input) and
output = ActionDispatch::Routing::underscore(input) and
input in [
"Foo", "FooBar", "Foo::Bar", "FooBar::Baz", "Foo::Bar::Baz", "Foo::Bar::BazQuux", "invalid",
"HTTPServerRequest", "LotsOfCapitalLetters"
]
}
query predicate mimeTypeInstances(API::Node n) {
n = ModelOutput::getATypeNode("actiondispatch", "Mime::Type")
}
query predicate mimeTypeMatchRegExpInterpretations(
ActionDispatch::MimeTypeMatchRegExpInterpretation s
) {
any()
}

View File

@@ -14,7 +14,7 @@ rawCalls
renderCalls
| app/controllers/foo/bars_controller.rb:6:5:6:37 | call to render |
| app/controllers/foo/bars_controller.rb:23:5:23:76 | call to render |
| app/controllers/foo/bars_controller.rb:29:5:29:17 | call to render |
| app/controllers/foo/bars_controller.rb:37:5:37:17 | call to render |
| app/views/foo/bars/show.html.erb:31:5:31:89 | call to render |
renderToCalls
| app/controllers/foo/bars_controller.rb:15:16:15:97 | call to render_to_string |

View File

@@ -5,11 +5,13 @@ import codeql.ruby.DataFlow
query predicate systemCalls(
PosixSpawn::SystemCall call, DataFlow::Node arg, boolean shellInterpreted
) {
arg = call.getAnArgument() and
if call.isShellInterpreted(arg) then shellInterpreted = true else shellInterpreted = false
call.isShellInterpreted(arg) and shellInterpreted = true
or
not call.isShellInterpreted(arg) and arg = call.getAnArgument() and shellInterpreted = false
}
query predicate childCalls(PosixSpawn::ChildCall call, DataFlow::Node arg, boolean shellInterpreted) {
arg = call.getAnArgument() and
if call.isShellInterpreted(arg) then shellInterpreted = true else shellInterpreted = false
call.isShellInterpreted(arg) and shellInterpreted = true
or
not call.isShellInterpreted(arg) and arg = call.getAnArgument() and shellInterpreted = false
}

View File

@@ -0,0 +1 @@
| action_cable.rb:1:1:1:54 | call to new |

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