Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions src/abstract-interpretation/absint-visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import { AbstractDomain } from './domains/abstract-domain';
import type { AnyStateDomain, ValueDomain } from './domains/state-domain-like';
import { UnsupportedFunctions } from './unsupported-functions';

export type AbsintVisitorConfiguration = Omit<SemanticCfgGuidedVisitorConfiguration<NoInfo, ControlFlowInformation, NormalizedAst>, 'defaultVisitingOrder' | 'defaultVisitingType'>;
export type AbsintVisitorConfiguration = Omit<SemanticCfgGuidedVisitorConfiguration<NoInfo, ControlFlowInformation, NormalizedAst>, 'defaultVisitingOrder' | 'defaultVisitingType'>
& {
/** Whether unsupported functions should be mapped to Top */
ignoreUnsupportedFunctions?: boolean
};

/**
* A control flow graph visitor to perform abstract interpretation.
Expand Down Expand Up @@ -44,14 +48,19 @@ export abstract class AbstractInterpretationVisitor<StateDomain extends AnyState
*/
private readonly unassigned: Set<NodeId> = new Set();

private readonly absIntConfig: AbsintVisitorConfiguration;

/**
* A map mapping assignments of replacement calls to their replacement calls for replacement calls that have already been visited but whose assignment has not yet been processed.
*/
private readonly replacements: Map<NodeId, NodeId[]> = new Map();

constructor(config: Config, stateDomain: StateDomain) {
super({ ...config, defaultVisitingOrder: 'forward', defaultVisitingType: 'exit' });

this.absIntConfig = {
ignoreUnsupportedFunctions: true,
...config,
};
this.currentState = stateDomain.top();
}

Expand Down Expand Up @@ -196,7 +205,7 @@ export abstract class AbstractInterpretationVisitor<StateDomain extends AnyState
this.onVisitNode(vertexId);

// discard the inferred abstract state when encountering unsupported function calls
if(this.isUnsupportedFunctionCall(nodeId)) {
if(this.absIntConfig.ignoreUnsupportedFunctions && this.isUnsupportedFunctionCall(nodeId)) {
this.currentState = this.currentState.top();
}
this.trace.set(nodeId, this.currentState);
Expand Down
9 changes: 6 additions & 3 deletions src/taint-analysis/builder/taint-analysis-definition.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
import type { AnyAbstractDomain } from '../../abstract-interpretation/domains/abstract-domain';
import type { TaintMapper } from '../function-mapper';
import type { AbsintVisitorConfiguration } from '../../abstract-interpretation/absint-visitor';

export type TaintAnalysisName<Definition> = Definition extends TaintAnalysisDefinition<infer Name, infer _Domain> ? Name : never;
export type TaintAnalysisName<Definition> = Definition extends TaintAnalysisDefinition<infer Name, infer _Domain, infer _Config> ? Name : never;
export type TaintAnalysisDomain<Definition> = Definition extends TaintAnalysisDefinition<infer _Name, infer Domain> ? Domain : never;

/**
* Fluent builder class for defining new taint analyses.
*/
export class TaintAnalysisDefinition<Name extends string = string, Domain extends AnyAbstractDomain = AnyAbstractDomain> {
export class TaintAnalysisDefinition<Name extends string = string, Domain extends AnyAbstractDomain = AnyAbstractDomain, Config extends AbsintVisitorConfiguration = AbsintVisitorConfiguration> {
public readonly domain: Domain;
public mapper: TaintMapper<Domain> = [];
public name: Name;
public config: Config | undefined;

private _msg: string | undefined;

get msg(): string | undefined {
return this._msg;
}

constructor(name: Name, domain: Domain) {
constructor(name: Name, domain: Domain, config?: Config) {
this.name = name;
this.domain = domain;
this.config = config;
}

public through(fnMapping: TaintMapper<Domain>): this {
Expand Down
3 changes: 2 additions & 1 deletion src/taint-analysis/builder/taint-analysis.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ReadonlyFlowrAnalysisProvider } from '../../project/flowr-analyzer';
import type { FlowrAnalyzer, ReadonlyFlowrAnalysisProvider } from '../../project/flowr-analyzer';
import type { TaintAnalysisDefinition, TaintAnalysisDomain } from './taint-analysis-definition';
import type { AnyPredefinedTaintAnalysisName } from '../predefined/predefined';
import { predefinedTaintAnalyses } from '../predefined/predefined';
Expand Down Expand Up @@ -39,6 +39,7 @@ export class TaintAnalysis<Defs extends readonly string[] = []> {
const results: Map<Defs[number], TaintInferenceResult<TaintAnalysisDefinition<Defs[number]>>> = new Map();
for(const def of this.defs) {
const visitor = new TaintInferenceVisitor(def.domain, def.mapper, {
...def.config,
controlFlow: await this.analyzer.controlflow(),
ctx: this.analyzer.inspectContext(),
dfg: (await this.analyzer.dataflow()).graph,
Expand Down
24 changes: 13 additions & 11 deletions src/taint-analysis/function-mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,12 @@ import type { RNode } from '../r-bridge/lang-4.x/ast/model/model';
import type { ParentInformation } from '../r-bridge/lang-4.x/ast/model/processing/decorate';
import { RType } from '../r-bridge/lang-4.x/ast/model/type';
import { Identifier } from '../dataflow/environments/identifier';
import type {
AbstractValue,
AnyAbstractDomain
} from '../abstract-interpretation/domains/abstract-domain';
import type { AbstractValue, AnyAbstractDomain } from '../abstract-interpretation/domains/abstract-domain';
import { VariableResolve } from '../config';
import type {
FunctionParameterLocation } from '../abstract-interpretation/data-frame/mappers/arguments';
import { getFunctionArgument
, getArgumentValue
,
import type { FunctionParameterLocation } from '../abstract-interpretation/data-frame/mappers/arguments';
import {
getArgumentValue,
getFunctionArgument,
getFunctionArguments
} from '../abstract-interpretation/data-frame/mappers/arguments';
import type { DataflowGraph } from '../dataflow/graph/graph';
Expand Down Expand Up @@ -46,7 +42,13 @@ export function mapFnCallToTaint<Domain extends AnyAbstractDomain>(
}

const functionName = Identifier.getName(node.functionName.content);
const mapping = mapper.find(m => Identifier.matches(m.identifier, functionName));
const mapping = mapper.find(m => {
if(Identifier.is(m.identifier)) {
return Identifier.matches(m.identifier, functionName);
} else {
return m.identifier.find(s => Identifier.matches(s, functionName));
}
});

if(mapping?.taint) {
return { taint: mapping.taint };
Expand Down Expand Up @@ -75,7 +77,7 @@ export function mapFnCallToTaint<Domain extends AnyAbstractDomain>(
export type TaintMapper<Domain extends AnyAbstractDomain> = TaintMapping<Domain>[];

export type TaintMapping<Domain extends AnyAbstractDomain> = {
identifier: Identifier;
identifier: Identifier | Identifier[];
} & (
| { taint: AbstractValue<Domain>; condition?: TaintCondition<Domain> }
| { taint?: AbstractValue<Domain>; condition: TaintCondition<Domain> }
Expand Down
8 changes: 6 additions & 2 deletions src/taint-analysis/predefined/predefined.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { scaleAnalysis } from './scale-analysis';
import type { TaintAnalysisDefinition, TaintAnalysisName } from '../builder/taint-analysis-definition';
import { securityAnalysis } from './security-analysis';
import { randomnessAnalysis } from './randomness-analysis';

export const predefinedTaintAnalyses = {
'scale': scaleAnalysis,
} as const satisfies AnalysisMap<['scale']>;
'scale': scaleAnalysis,
'security': securityAnalysis,
'randomness': randomnessAnalysis
} as const satisfies AnalysisMap<['scale', 'security', 'randomness']>;

export type AnyPredefinedTaintAnalysisName = keyof typeof predefinedTaintAnalyses;
export type AllPredefinedTaintAnalysisNames = [AnyPredefinedTaintAnalysisName];
Expand Down
30 changes: 30 additions & 0 deletions src/taint-analysis/predefined/randomness-analysis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Bottom, Top } from '../../abstract-interpretation/domains/lattice';
import { TaintAnalysisDefinition } from '../builder/taint-analysis-definition';
import { FiniteDomainBuilder } from '../builder/domain';
import { Identifier } from '../../dataflow/environments/identifier';

export const Random = Symbol('Random');
export const Deterministic = Symbol('Deterministic');

export const randomnessDomain = new FiniteDomainBuilder<Top, Bottom, [Top, Bottom, ...[typeof Random, typeof Deterministic]]>()
.addLeqOrder(Bottom, [Random, Deterministic])
.addLeqOrder(Random, Top)
.addLeqOrder(Deterministic, Top)
.build();

export const randomnessAnalysis = new TaintAnalysisDefinition('randomness', randomnessDomain)
.through([ {
identifier: Identifier.make('c'),
taint: Deterministic
},
{
identifier: [
'jitter', 'sample', 'sample.int', 'arima.sim', 'kmeans', 'princomp', 'rcauchy', 'rchisq', 'rexp',
'rgamma', 'rgeom', 'rlnorm', 'rlogis', 'rmultinom', 'rnbinom', 'rnorm', 'rpois', 'runif', 'pointLabel',
'some', 'rbernoulli', 'rdunif', 'generateSeedVectors',
'rbeta', 'rf', 'rhyper', 'rweibull', 'rt', 'rvonmises', 'rwilcox', 'rxor', 'rhyper', 'rmvnorm',
'rsignrank', 'randomForest',
'permuted', 'permute', 'shuffle', 'shuffleSet', 'data_shuffle', 'sample_frac', 'sample_n', 'slice_sample',
],
taint: Random
}]);
57 changes: 57 additions & 0 deletions src/taint-analysis/predefined/security-analysis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Bottom, Top } from '../../abstract-interpretation/domains/lattice';
import { TaintAnalysisDefinition } from '../builder/taint-analysis-definition';
import { FiniteDomainBuilder } from '../builder/domain';
import { UserFunctions } from '../../queries/catalog/input-sources-query/input-source-functions';

export const UserInput = Symbol('User Input');
export const NetworkInput = Symbol('Network Input');
export const FileInput = Symbol('File Input');

type SecurityLatticeElements = [typeof UserInput, typeof NetworkInput, typeof FileInput];


export const securityDomain = new FiniteDomainBuilder<Top, Bottom, [Top, Bottom, ...SecurityLatticeElements]>()
.addLeqOrder(Bottom, [UserInput, NetworkInput, FileInput])
.addLeqOrder(UserInput, Top)
.addLeqOrder(NetworkInput, Top)
.addLeqOrder(FileInput, Top)
.build();

export const securityAnalysis = new TaintAnalysisDefinition('security', securityDomain)
.through([
{
identifier: UserFunctions,
taint: UserInput
},
{
identifier: ['read.table', 'read.csv', 'read.csv2', 'read.delim', 'read.delim2', 'readRDS', 'download.file', 'url', 'GET', 'POST', 'PUT',
'DELETE', 'PATCH', 'HEAD', 'content', 'handle', 'get_callback', 'VERB', 'fread', 'gzcon', 'readlines', 'readLines', 'source', 'load', 'curl_download',
'curl_fetch_memory', 'getURL', 'getForm', 'read_html', 'read_xml', 'html_nodes', 'html_text', 'fromJSON', 'read.xlsx', 'drive_download', 'drive_get',
's3read_using', 's3write_using', 'storage_download', 'AnnotationHub', 'ExperimentHub', 'scan',
'socketConnection', 'request', 'curl'],
taint: NetworkInput,
}
])
.to([
{
identifier: [
'eval', 'evalq',
'system', 'system2', 'shell',
'dyn.load',
'source', 'sys.source',
'.C', '.Fortran', '.External',
'read.table', 'read.csv', 'read.csv2', 'read.delim', 'read.delim2',
'read.rds', 'load', 'unserialize', 'parse', 'parse.file',
'download.file', 'url', 'socketConnection', 'pipe', 'fifo', 'gzfile', 'bzfile', 'xzfile',
'file.create', 'file.append', 'file.remove', 'file.rename', 'file.copy',
'write.table', 'write.csv', 'write.csv2', 'write.delim', 'write.delim2',
'save', 'saveRDS', 'serialize', 'dump', 'save.history', 'save.image',
'pdf'
],
condition: {
argTaints: [{ pos: 0 }],
condition: (_args, [taint]) => (taint === UserInput || taint === NetworkInput || taint === FileInput) ? Bottom : taint
}
}
])
.report('User input potentially flowing to output');
2 changes: 1 addition & 1 deletion src/taint-analysis/taint-visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class TaintInferenceVisitor<Domain extends AnyAbstractDomain> extends Abs
private readonly fnCallMapper: TaintMapper<Domain>;

constructor(domain: Domain, fnCallMapper: TaintMapper<Domain>, visitorConfig: AbsintVisitorConfiguration) {
super(visitorConfig, StateAbstractDomain.top(domain.top()));
super({ ...visitorConfig, ignoreUnsupportedFunctions: false }, StateAbstractDomain.top(domain.top()));
this.domain = domain;
this.fnCallMapper = fnCallMapper;
}
Expand Down