Skip to content

Commit

Permalink
applicabilityScan: add "indirect-cve-whitelist" to scanner YAML confi…
Browse files Browse the repository at this point in the history
…guration

Add support for applicability scanning of indirect (transitive) CVEs.
This is done by sending a separate list of detected indirect CVEs (indirect-cve-whitelist) to the applicability scanner YAML configuration file.
  • Loading branch information
srmish-jfrog committed Nov 27, 2023
1 parent 68be517 commit a689bde
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 18 deletions.
57 changes: 41 additions & 16 deletions src/main/scanLogic/scanRunners/applicabilityScan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface ApplicabilityScanArgs extends AnalyzeScanRequest {
grep_disable: boolean;
// Must have at least one item, the CVE to search for in scan
cve_whitelist: string[];
indirect_cve_whitelist: string[];
}

/**
Expand Down Expand Up @@ -67,7 +68,7 @@ export class ApplicabilityRunner extends JasRunner {
/** @override */
public requestsToYaml(...requests: AnalyzeScanRequest[]): string {
let str: string = super.requestsToYaml(...requests);
return str.replace('cve_whitelist', 'cve-whitelist');
return str.replace('cve_whitelist', 'cve-whitelist').replace('indirect_cve_whitelist', 'indirect-cve-whitelist');
}

/** @override */
Expand All @@ -85,7 +86,7 @@ export class ApplicabilityRunner extends JasRunner {
/** @override */
protected logStartScanning(request: ApplicabilityScanArgs): void {
this._logManager.logMessage(
`Scanning directory ' ${request.roots[0]} + ', for ${this._scanType} issues: ${request.cve_whitelist} Skipping folders: ${request.skipped_folders}`,
`Scanning directory ' ${request.roots[0]} + ', for ${this._scanType} issues: ${request.cve_whitelist} indirect issues: ${request.indirect_cve_whitelist} Skipping folders: ${request.skipped_folders}`,
'DEBUG'
);
}
Expand All @@ -94,29 +95,45 @@ export class ApplicabilityRunner extends JasRunner {
* Scan for applicability issues
*/
public async scan(): Promise<void> {
let filteredBundles: Map<FileScanBundle, Set<string>> = this.filterBundlesWithoutIssuesToScan();
let workspaceToBundles: Map<string, Map<FileScanBundle, Set<string>>> = this.mapBundlesForApplicableScanning(filteredBundles);
let filteredBundles: Map<FileScanBundle, [Set<string>, Set<string>]> = this.filterBundlesWithoutIssuesToScan();
let workspaceToBundles: Map<string, Map<FileScanBundle, [Set<string>, Set<string>]>> = this.mapBundlesForApplicableScanning(filteredBundles);
if (workspaceToBundles.size == 0) {
return;
}
let excludePatterns: string[] = AnalyzerUtils.getAnalyzerManagerExcludePatterns(Configuration.getScanExcludePattern());
for (let [workspacePath, bundles] of workspaceToBundles) {
let cveToScan: Set<string> = Utils.combineSets(Array.from(bundles.values()));
// Unpack the direct & indirect CVEs
const directCveSets: Set<string>[] = [];
const indirectCveSets: Set<string>[] = [];
for (const cvesTuple of bundles.values()) {
directCveSets.push(cvesTuple[0]);
indirectCveSets.push(cvesTuple[1]);
}

const cveToScan: Set<string> = Utils.combineSets(directCveSets);
const indirectCveToScan: Set<string> = Utils.combineSets(indirectCveSets);
// Scan workspace for all cve in relevant bundles
let startApplicableTime: number = Date.now();

const request: ApplicabilityScanArgs = {
type: ScanType.AnalyzeApplicability,
roots: [workspacePath],
cve_whitelist: Array.from(cveToScan),
indirect_cve_whitelist: Array.from(indirectCveToScan),
skipped_folders: excludePatterns
} as ApplicabilityScanArgs;

// Merge the direct and indirect CVEs
const mergedBundles: Map<FileScanBundle, Set<string>> = new Map<FileScanBundle, Set<string>>();
for (const [fileScanBundle, cvesTuple] of bundles) {
mergedBundles.set(fileScanBundle, Utils.combineSets(cvesTuple));
}

this.logStartScanning(request);
let response: AnalyzerScanResponse | undefined = await this.executeRequest(this._progressManager.checkCancel, request);
let applicableIssues: ApplicabilityScanResponse = this.convertResponse(response);
if (applicableIssues?.applicableCve) {
this.transferApplicableResponseToBundles(applicableIssues, bundles, startApplicableTime);
this.transferApplicableResponseToBundles(applicableIssues, mergedBundles, startApplicableTime);
}
}
}
Expand All @@ -125,14 +142,15 @@ export class ApplicabilityRunner extends JasRunner {
* Filter bundles without direct cve issues, transform the bundle list to have its relevant cve to scan set.
* @returns Map of bundles to their set of direct cves issues, with at least one for each bundle
*/
private filterBundlesWithoutIssuesToScan(): Map<FileScanBundle, Set<string>> {
let filtered: Map<FileScanBundle, Set<string>> = new Map<FileScanBundle, Set<string>>();
private filterBundlesWithoutIssuesToScan(): Map<FileScanBundle, [Set<string>, Set<string>]> {
let filtered: Map<FileScanBundle, [Set<string>, Set<string>]> = new Map<FileScanBundle, [Set<string>, Set<string>]>();
for (let fileScanBundle of this._bundlesWithIssues) {
if (!(fileScanBundle.dataNode instanceof ProjectDependencyTreeNode)) {
// Filter non dependencies projects
continue;
}
let cvesToScan: Set<string> = new Set<string>();
const cvesToScan: Set<string> = new Set<string>();
const indirectCvesToScan: Set<string> = new Set<string>();
fileScanBundle.dataNode.issues.forEach((issue: IssueTreeNode) => {
if (!(issue instanceof CveTreeNode) || !issue.cve?.cve) {
return;
Expand All @@ -141,14 +159,16 @@ export class ApplicabilityRunner extends JasRunner {
// Other project types should include only CVEs on direct dependencies.
if (this._packageType === PackageType.Python || !issue.parent.indirect) {
cvesToScan.add(issue.cve.cve);
} else {
indirectCvesToScan.add(issue.cve.cve);
}
});
if (cvesToScan.size == 0) {
if (cvesToScan.size == 0 && indirectCvesToScan.size == 0) {
// Nothing to do in bundle
continue;
}

filtered.set(fileScanBundle, cvesToScan);
filtered.set(fileScanBundle, [cvesToScan, indirectCvesToScan]);
}

return filtered;
Expand All @@ -159,17 +179,22 @@ export class ApplicabilityRunner extends JasRunner {
* @param filteredBundles - bundles to map
* @returns mapped bundles to similar workspace
*/
private mapBundlesForApplicableScanning(filteredBundles: Map<FileScanBundle, Set<string>>): Map<string, Map<FileScanBundle, Set<string>>> {
let workspaceToScanBundles: Map<string, Map<FileScanBundle, Set<string>>> = new Map<string, Map<FileScanBundle, Set<string>>>();
private mapBundlesForApplicableScanning(
filteredBundles: Map<FileScanBundle, [Set<string>, Set<string>]>
): Map<string, Map<FileScanBundle, [Set<string>, Set<string>]>> {
let workspaceToScanBundles: Map<string, Map<FileScanBundle, [Set<string>, Set<string>]>> = new Map<
string,
Map<FileScanBundle, [Set<string>, Set<string>]>
>();

for (let [fileScanBundle, cvesToScan] of filteredBundles) {
for (let [fileScanBundle, cvesTuple] of filteredBundles) {
let descriptorIssues: DependencyScanResults = <DependencyScanResults>fileScanBundle.data;
// Map information to similar directory space
let workspacePath: string = AnalyzerUtils.getWorkspacePath(fileScanBundle.dataNode, descriptorIssues.fullPath);
if (!workspaceToScanBundles.has(workspacePath)) {
workspaceToScanBundles.set(workspacePath, new Map<FileScanBundle, Set<string>>());
workspaceToScanBundles.set(workspacePath, new Map<FileScanBundle, [Set<string>, Set<string>]>());
}
workspaceToScanBundles.get(workspacePath)?.set(fileScanBundle, cvesToScan);
workspaceToScanBundles.get(workspacePath)?.set(fileScanBundle, cvesTuple);
this._logManager.logMessage('Adding data from descriptor ' + descriptorIssues.fullPath + ' for cve applicability scan', 'INFO');
}

Expand Down
3 changes: 3 additions & 0 deletions src/test/resources/applicableScan/requestMultipleRoots.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,8 @@ scans:
- CVE-2021-3918
- CVE-2021-3807
- CVE-2022-25878
indirect-cve-whitelist:
- CVE-2021-44228
- CVE-2023-1234
skipped-folders:
- /path/to/skip
2 changes: 2 additions & 0 deletions src/test/resources/applicableScan/requestOneRoot.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ scans:
- /path/to/root
cve-whitelist:
- CVE-2021-3918
indirect-cve-whitelist:
- CVE-2021-44228
skipped-folders: []
7 changes: 5 additions & 2 deletions src/test/tests/applicabilityScan.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,20 @@ describe('Applicability Scan Tests', () => {
expectedYaml: path.join(scanApplicable, 'requestOneRoot.yaml'),
roots: ['/path/to/root'],
cves: ['CVE-2021-3918'],
indirect_cves: ['CVE-2021-44228'],
skip: []
},
{
name: 'Multiple roots',
expectedYaml: path.join(scanApplicable, 'requestMultipleRoots.yaml'),
roots: ['/path/to/root', '/path/to/other'],
cves: ['CVE-2021-3918', 'CVE-2021-3807', 'CVE-2022-25878'],
indirect_cves: ['CVE-2021-44228', 'CVE-2023-1234'],
skip: ['/path/to/skip']
}
].forEach(test => {
it('Check generated Yaml request for - ' + test.name, () => {
let request: ApplicabilityScanArgs = getApplicabilityScanRequest(test.roots, test.cves, test.skip);
let request: ApplicabilityScanArgs = getApplicabilityScanRequest(test.roots, test.cves, test.indirect_cves, test.skip);
let actualYaml: string = path.join(tempFolder, test.name);
fs.writeFileSync(actualYaml, getDummyRunner([], PackageType.Unknown).requestsToYaml(request));
assert.deepEqual(
Expand Down Expand Up @@ -345,12 +347,13 @@ describe('Applicability Scan Tests', () => {
});
});

function getApplicabilityScanRequest(roots: string[], cves: string[], skipFolders: string[]): ApplicabilityScanArgs {
function getApplicabilityScanRequest(roots: string[], cves: string[], indirect_cves: string[], skipFolders: string[]): ApplicabilityScanArgs {
return {
type: 'analyze-applicability',
output: '/path/to/output.json',
roots: roots,
cve_whitelist: cves,
indirect_cve_whitelist: indirect_cves,
skipped_folders: skipFolders
} as ApplicabilityScanArgs;
}
Expand Down

0 comments on commit a689bde

Please sign in to comment.