infra: add vscode extension poc (#10592)

Add initial layout or a vscode extension.

Several commands included in the extension, including commands for
- oss-fuzz initialization
- creating new project integrations
- generating coverage reports
- building projects from arbitrary locations in the filesystem
- reproducing crashes easily
- instant CIFuzz integration
- creating fuzzing templates for rapid prototyping
- ...

Many ideas can be put into the vscode extension, for example:
- support some form of e.g. sync with introspector.oss-fuzz.com -- for
example where the plugin will check "has progress been made relative to
what is currently at oss-fuzz". We could extend `helper.py` with some
form of command called "check_progress` which will run a small build
pipeline + coverage and check if the coverage performs better than what
is currently achieved. I think there's more to explore in this space.
- connect to auto-fuzz:
https://github.com/ossf/fuzz-introspector/tree/main/tools/auto-fuzz
- in general improve the extension UI, as currently it's only based on
commands.

---------

Signed-off-by: David Korczynski <david@adalogics.com>
This commit is contained in:
DavidKorczynski 2023-08-28 10:51:45 +01:00 committed by GitHub
parent 920bb40321
commit 07b80397c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 10068 additions and 1 deletions

2
.gitignore vendored
View File

@ -1,4 +1,4 @@
.vscode/
/.vscode/
*.pyc
/build/
*~

View File

@ -0,0 +1,8 @@
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
insert_final_newline = true

View File

@ -0,0 +1 @@
build/

View File

@ -0,0 +1,3 @@
{
"extends": "./node_modules/gts/"
}

6
tools/vscode-extension/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
out
build
dist
node_modules
.vscode-test/
*.vsix

View File

@ -0,0 +1,18 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
module.exports = {
...require('gts/.prettierrc.json')
}

View File

@ -0,0 +1,7 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"dbaeumer.vscode-eslint"
]
}

View File

@ -0,0 +1,34 @@
// A launch configuration that compiles the extension and then opens it inside a new window
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
],
"outFiles": [
"${workspaceFolder}/out/**/*.js"
],
"preLaunchTask": "${defaultBuildTask}"
},
{
"name": "Extension Tests",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionTestsPath=${workspaceFolder}/out/test/suite/index"
],
"outFiles": [
"${workspaceFolder}/out/test/**/*.js"
],
"preLaunchTask": "${defaultBuildTask}"
}
]
}

View File

@ -0,0 +1,11 @@
// Place your settings in this file to overwrite default and user settings.
{
"files.exclude": {
"out": false // set this to true to hide the "out" folder with the compiled JS files
},
"search.exclude": {
"out": true // set this to false to include "out" folder in search results
},
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
"typescript.tsc.autoDetect": "off"
}

View File

@ -0,0 +1,20 @@
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "watch",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

View File

@ -0,0 +1,10 @@
.vscode/**
.vscode-test/**
src/**
.gitignore
.yarnrc
vsc-extension-quickstart.md
**/tsconfig.json
**/.eslintrc.json
**/*.map
**/*.ts

View File

@ -0,0 +1,5 @@
# OSS-Fuzz visual studio code extension
Use this extension to build, run and introspector fuzzers by way of the [OSS-Fuzz](https://github.com/google/oss-fuzz) infrastructure.
This extension also provides capabilities to augment the IDE with information from OSS-Fuzz cloud database, showing details given by [Fuzz Introspector](https://github.com/ossf/fuzz-introspector).

7212
tools/vscode-extension/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,131 @@
{
"name": "oss-fuzz",
"displayName": "OssFuzz",
"description": "",
"version": "0.0.2",
"publisher": "OSS-Fuzz maintainers",
"engines": {
"vscode": "^1.76.0"
},
"categories": [
"Other"
],
"extensionDependencies": [
"mindaro-dev.file-downloader"
],
"activationEvents": [],
"main": "./build/src/extension.js",
"contributes": {
"configurations": [
{
"id": "OSS-Fuzz-Base",
"title": "Base path to OSS-Fuzz",
"properties": {
"oss-fuzz.OSS.Path": {
"type": "string"
}
}
}
],
"viewsWelcome": [
{
"view": "workbench.explorer.emptyView",
"contents": "You can have paragraphs of text here. You can have [links](https://code.visualstudio.com) to external sources or [internal commands](command:welcome-view-content-sample.hello).\nUse new lines to have new paragraphs.\nPlace a link alone in a paragraph to make it a button\n[Hello](command:welcome-view-content-sample.hello)"
}
],
"commands": [
{
"command": "oss-fuzz.SetUp",
"title": "OSS-Fuzz: Set up OSS-Fuzz"
},
{
"command": "oss-fuzz.RunFuzzer",
"title": "OSS-Fuzz: Run fuzzer from OSS-Fuzz project"
},
{
"command": "oss-fuzz.ListFuzzers",
"title": "OSS-Fuzz: List all fuzzers in a given project"
},
{
"command": "oss-fuzz.SetOSSFuzzPath",
"title": "OSS-Fuzz: Set OSS-Fuzz path"
},
{
"command": "oss-fuzz.RunIntrospector",
"title": "OSS-Fuzz: Run Fuzz Introspector on a project"
},
{
"command": "oss-fuzz.GetCodeCoverage",
"title": "OSS-Fuzz: get code coverage from OSS-Fuzz storage."
},
{
"command": "oss-fuzz.ClearCodeCoverage",
"title": "OSS-Fuzz: clear code coverage"
},
{
"command": "oss-fuzz.CreateOSSFuzzSetup",
"title": "OSS-Fuzz: Create template files for OSS-Fuzz"
},
{
"command": "oss-fuzz.WSBuildFuzzers",
"title": "OSS-Fuzz: Build fuzzers from workspace"
},
{
"command": "oss-fuzz.SetupCIFuzz",
"title": "OSS-Fuzz: Set up CIFuzz"
},
{
"command": "oss-fuzz.testFuzzer",
"title": "OSS-Fuzz: test specific fuzzer"
},
{
"command": "oss-fuzz.testCodeCoverage",
"title": "OSS-Fuzz: run end-to-end test of project and collect code coverage"
},
{
"command": "oss-fuzz.Reproduce",
"title": "OSS-Fuzz: reproduce a testcase"
},
{
"command": "oss-fuzz.SetGlobalProjectName",
"title": "OSS-Fuzz: sets the default project name in the session"
},
{
"command": "oss-fuzz.Redo",
"title": "OSS-Fuzz: Redo the exact same command as previously, with same input"
},
{
"command": "oss-fuzz.Template",
"title": "OSS-Fuzz: Template fuzzer creation"
}
]
},
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc",
"watch": "tsc -watch -p ./",
"pretest": "npm run compile",
"lint": "gts lint",
"clean": "gts clean",
"fix": "gts fix",
"prepare": "npm run compile",
"posttest": "npm run lint"
},
"devDependencies": {
"@types/glob": "^8.1.0",
"@types/mocha": "^10.0.1",
"@types/node": "^14.11.2",
"@types/vscode": "^1.76.0",
"@typescript-eslint/eslint-plugin": "^5.53.0",
"@typescript-eslint/parser": "^5.53.0",
"@vscode/test-electron": "^2.2.3",
"eslint": "^8.34.0",
"glob": "^8.1.0",
"mocha": "^10.2.0",
"typescript": "~4.7.0",
"gts": "^3.1.1"
},
"dependencies": {
"@microsoft/vscode-file-downloader-api": "^1.0.1"
}
}

View File

@ -0,0 +1,70 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
import {println} from './logger';
/**
* Creates a CIFuzz template
* @param language
* @param projectName
* @param secondToRun
* @returns
*/
export function cifuzzGenerator(
language: string,
projectName: string,
secondToRun: Number
) {
println('Exporting cifuzz logic ' + language);
const cifuzzTemplate = `name: CIFuzz
on: [pull_request]
permissions: {}
jobs:
Fuzzing:
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- name: Build Fuzzers
id: build
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
with:
oss-fuzz-project-name: '${projectName}'
language: ${language}
- name: Run Fuzzers
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
with:
oss-fuzz-project-name: '${projectName}'
language: ${language}
fuzz-seconds: ${secondToRun}
output-sarif: true
- name: Upload Crash
uses: actions/upload-artifact@v3
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts
path: ./out/artifacts
- name: Upload Sarif
if: always() && steps.build.outcome == 'success'
uses: github/codeql-action/upload-sarif@v2
with:
# Path to SARIF file relative to the root of the repository
sarif_file: cifuzz-sarif/results.sarif
checkout_path: cifuzz-sarif`;
return cifuzzTemplate;
}

View File

@ -0,0 +1,17 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
export const commandHistory: any[] = [];

View File

@ -0,0 +1,70 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
import * as vscode from 'vscode';
import {println} from '../logger';
import {commandHistory} from '../commandUtils';
import {hasOssFuzzInWorkspace, getOssFuzzWorkspaceProjectName} from '../utils';
import {buildFuzzersFromWorkspace} from '../ossfuzzWrappers';
export async function cmdInputCollectorBuildFuzzersFromWorkspace() {
let ossFuzzProjectName = '';
// First determine if we have a name in the workspace
if (await hasOssFuzzInWorkspace()) {
/**
* The fuzzers are in the workspace, as opposed to e.g. the oss-fuzz dirctory.
*/
ossFuzzProjectName = await getOssFuzzWorkspaceProjectName();
} else {
// If we did not have that, ask the user.
const ossFuzzProjectNameInput = await vscode.window.showInputBox({
value: '',
placeHolder: 'The OSS-Fuzz project name',
});
if (!ossFuzzProjectNameInput) {
println('Did not get a ossFuzzTargetProject');
return false;
}
ossFuzzProjectName = ossFuzzProjectNameInput.toString();
}
// Create an history object
const args = new Object({
projectName: ossFuzzProjectName,
sanitizer: '',
toClean: false,
});
const commandObject = new Object({
commandType: 'oss-fuzz.WSBuildFuzzers',
Arguments: args,
dispatcherFunc: cmdDispatchBuildFuzzersFromWorkspace,
});
console.log('L1: ' + commandHistory.length);
commandHistory.push(commandObject);
await cmdDispatchBuildFuzzersFromWorkspace(args);
return true;
}
async function cmdDispatchBuildFuzzersFromWorkspace(args: any) {
await buildFuzzersFromWorkspace(
args.projectName,
args.sanitizer,
args.toClean
);
}

View File

@ -0,0 +1,21 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
import {setupProjectInitialFiles} from '../projectIntegrationHelper';
export async function createOssFuzzSetup() {
await setupProjectInitialFiles();
}

View File

@ -0,0 +1,71 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
import * as vscode from 'vscode';
import {loadCoverageIntoWorkspace} from '../coverageHelper';
import {println} from '../logger';
import {getApi, FileDownloader} from '@microsoft/vscode-file-downloader-api';
/*
* Displays code coverage from OSS-Fuzz.
*
* Downloads a code coverage report from the OSS-Fuzz online storage, and then overlays
* the relevant source files with the coverage information.
*/
export async function displayCodeCoverageFromOssFuzz(
context: vscode.ExtensionContext
) {
const projectName = await vscode.window.showInputBox({
value: '',
placeHolder: "The project you'd like to get code coverage for.",
});
if (!projectName) {
return;
}
println('Getting code coverage for ' + projectName);
const fileDownloader: FileDownloader = await getApi();
const currentDate = new Date();
const yesterday = new Date(currentDate);
yesterday.setDate(yesterday.getDate() - 1);
const day = yesterday.getDate();
const month = yesterday.getMonth();
const year = yesterday.getFullYear();
try {
const codeCoverageFile: vscode.Uri = await fileDownloader.downloadFile(
vscode.Uri.parse(
'https://storage.googleapis.com/oss-fuzz-coverage/' +
projectName +
'/textcov_reports/' +
year.toString() +
month.toString() +
day.toString() +
'/all_cov.json'
),
'all_cov.json',
context
);
await loadCoverageIntoWorkspace(context, codeCoverageFile);
} catch (err) {
println(
'Could not get the URL. Currently, this feature is only supported for Python projects'
);
return;
}
}

View File

@ -0,0 +1,157 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
import * as vscode from 'vscode';
const fs = require('fs');
import {println} from '../logger';
import {commandHistory} from '../commandUtils';
import {buildFuzzersFromWorkspace, runFuzzerHandler} from '../ossfuzzWrappers';
import {listFuzzersForProject, systemSync} from '../utils';
import {loadCoverageIntoWorkspace} from '../coverageHelper';
import {extensionConfig} from '../config';
/**
* Performs the activities:
* 1) Build a project using address sanitizer
* 2) Run each fuzzer of the project, saving corpus
* 3) Build project using coverage sanitizer
* 4) Collect coverage
* @param context
* @returns
*/
export async function runEndToEndAndGetCoverage(
context: vscode.ExtensionContext
) {
println('Getting code coverage');
const ossFuzzProjectNameInput = await vscode.window.showInputBox({
value: '',
placeHolder: 'The OSS-Fuzz project name',
});
if (!ossFuzzProjectNameInput) {
println('Did not get a ossFuzzTargetProject');
return;
}
const secondsToRunEachFuzzer = await vscode.window.showInputBox({
value: '',
placeHolder: 'Seconds to run each fuzzer',
});
if (!secondsToRunEachFuzzer) {
println('Did not get number of seconds to run each fuzzer');
return;
}
// Create an history object
const args = new Object({
projectName: ossFuzzProjectNameInput.toString(),
secondsToRun: secondsToRunEachFuzzer.toString(),
vsContext: context,
});
const commandObject = new Object({
commandType: 'oss-fuzz.cmdDispatchEndToEndRun',
Arguments: args,
dispatcherFunc: cmdDispatchEndToEndRun,
});
console.log('L1: ' + commandHistory.length);
commandHistory.push(commandObject);
await cmdDispatchEndToEndRun(args);
return;
}
async function cmdDispatchEndToEndRun(args: any) {
await endToEndRun(args.projectName, args.secondsToRun, args.vsContext);
return;
}
async function endToEndRun(
ossFuzzProjectNameInput: string,
secondsToRunEachFuzzer: string,
context: vscode.ExtensionContext
) {
vscode.window.showInformationMessage(
'Building project: ' + ossFuzzProjectNameInput.toString()
);
await buildFuzzersFromWorkspace(ossFuzzProjectNameInput.toString(), '', true);
println('Build projects');
// List all of the fuzzers in the project
const fuzzersInProject = await listFuzzersForProject(
ossFuzzProjectNameInput,
extensionConfig.ossFuzzPepositoryWorkPath
);
// Run all of the fuzzers in the project
println('Fuzzers found in project: ' + fuzzersInProject.toString());
println('Running each of the fuzzers to collect a corpus');
for (const fuzzName of fuzzersInProject) {
println('Running fuzzer: ' + fuzzName);
// Corpus directory
const fuzzerCorpusPath =
extensionConfig.ossFuzzPepositoryWorkPath +
'/build/corpus/' +
ossFuzzProjectNameInput +
'/' +
fuzzName;
await systemSync('mkdir', ['-p', fuzzerCorpusPath]);
await runFuzzerHandler(
ossFuzzProjectNameInput,
fuzzName,
secondsToRunEachFuzzer.toString(),
fuzzerCorpusPath
);
}
// Build with code coverage
println('Building project with coverage sanitizer');
await buildFuzzersFromWorkspace(
ossFuzzProjectNameInput.toString(),
'coverage',
true
);
// Run coverage command
println('Collecting code coverage');
const args: Array<string> = [
extensionConfig.ossFuzzPepositoryWorkPath + '/infra/helper.py',
'coverage',
'--port',
'',
'--no-corpus-download',
ossFuzzProjectNameInput.toString(),
];
await systemSync('python3', args);
println('Load coverage report with the command:');
println(
'python3 -m http.server 8008 --directory /tmp/oss-fuzz/build/out/' +
ossFuzzProjectNameInput.toString() +
'/report/'
);
println('Trying to load code coverage in IDE');
const allCovPath =
extensionConfig.ossFuzzPepositoryWorkPath +
'/build/out/' +
ossFuzzProjectNameInput.toString() +
'/textcov_reports/all_cov.json';
if (fs.existsSync(allCovPath)) {
const generatedCodeCoverageFile = vscode.Uri.file(allCovPath);
await loadCoverageIntoWorkspace(context, generatedCodeCoverageFile);
}
}

View File

@ -0,0 +1,42 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
import * as vscode from 'vscode';
import {listFuzzersForProject} from '../utils';
import {println} from '../logger';
import {extensionConfig} from '../config';
/**
* Lists all the fuzzers for a project.
*/
export async function listFuzzersHandler() {
// Lists all of the fuzzers from a project.
const projectName = await vscode.window.showInputBox({
value: '',
placeHolder: 'Type a project name',
});
if (!projectName) {
console.log('Failed to get project name');
return;
}
println('Listing fuzzers for project ' + projectName);
await listFuzzersForProject(
projectName?.toString(),
extensionConfig.ossFuzzPepositoryWorkPath
);
}

View File

@ -0,0 +1,35 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
import {commandHistory} from '../commandUtils';
/**
* Rerun the latest command
*/
export async function cmdDispatcherRe() {
if (commandHistory.length === 0) {
console.log('command history is empty');
return false;
}
const commandObj: any = commandHistory[commandHistory.length - 1];
console.log('Redoing');
console.log(commandObj.commandType);
await commandObj.dispatcherFunc(commandObj.Arguments);
//await commandObj.dispatcherFunc(commandObj.args);
return true;
}

View File

@ -0,0 +1,168 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
import * as vscode from 'vscode';
const fs = require('fs');
import {println} from '../logger';
import {commandHistory} from '../commandUtils';
import {systemSyncLogIfFailure} from '../utils';
import {buildFuzzersFromWorkspace} from '../ossfuzzWrappers';
import {extensionConfig} from '../config';
const readline = require('readline');
export async function cmdInputCollectorReproduceTestcase() {
// Runs a fuzzer from a given project.
const crashFileInput = await vscode.window.showInputBox({
value: '',
placeHolder: 'The ID of the testcase.',
});
if (!crashFileInput) {
return;
}
// Create an history object and append it to the command history.
const args = new Object({
crashFile: crashFileInput.toString(),
});
const commandObject = new Object({
commandType: 'oss-fuzz.ReproduceFuzzer',
Arguments: args,
dispatcherFunc: cmdDispatchReproduceTestcase,
});
commandHistory.push(commandObject);
await cmdDispatchReproduceTestcase(args);
return true;
}
async function cmdDispatchReproduceTestcase(args: any) {
await reproduceTestcase(args.crashFile);
}
export async function reproduceTestcase(crashInfoFileInput: string) {
println('Reproducing testcase for ' + crashInfoFileInput);
println('Checking directory: ' + extensionConfig.crashesDirectory);
const crashInfoFile =
extensionConfig.crashesDirectory + '/' + crashInfoFileInput + '.info';
println(crashInfoFile);
try {
if (fs.existsSync(crashInfoFile)) {
println('File exists');
} else {
println('Crash file does not exist');
return;
}
} catch (err) {
console.error(err);
return;
}
// At this point the file exists
const r = readline.createInterface({
input: fs.createReadStream(crashInfoFile),
});
let targetProject = 'N/A';
let targetFuzzer = 'N/A';
// Logic for passing the file. This is based off of clusterfuzz monorail reports,
// and the intention is the file needs to be a copy of:
//
// Project: project-name
// Fuzzing Engine: libFuzzer
// Fuzz Target: fuzzer-name
//
// Example:
// The following URL: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=59747
// has the bug information:
// """
// Project: my-fuzzing-project
// Fuzzing Engine: libFuzzer
// Fuzz Target: the-fuzzer-name-fuzz-parseXX
// Job Type: libfuzzer_asan_my-fuzzing-project
// Platform Id: linux
// """
// and a link to a reproducer test case:
// https://oss-fuzz.com/download?testcase_id=5009071179431936
// which, when accessed will download the file
// clusterfuzz-testcase-minimized-flb-it-fuzz-config_map_fuzzer_OSSFUZZ-5009071179431936
//
// To enable reproducing of this issue we need to:
// - 1) Download the crash file and place it in the directory given in config.ts
// and "crashesDirectory" variable.
// - 2) create a file "5009071179431936.info" and paste the information above
// (Project:... Fuzz Target: ...) into the file. This information is
// needed because we need to know project name and fuzzer name in order
// to reproduce the crash.
// - 3) the reproducer can now be reproduced using the reproduce command
// with argument "5009071179431936" as argument.
r.on('line', (text: string) => {
println(text);
if (text.startsWith('Project: ')) {
println('Starts with project');
println(text.split('Project: ').toString());
targetProject = text.split('Project: ')[1];
} else if (text.startsWith('Fuzzing Engine: ')) {
println('Starts with fuzzing engine');
} else if (text.startsWith('Fuzz Target:')) {
println('Starts with Fuzz Target');
targetFuzzer = text.split('Fuzz Target: ')[1];
} else if (text.startsWith('Job Type:')) {
println('Starts with Job Type');
}
});
r.on('close', async () => {
println('Target project: ' + targetProject);
println('Target fuzzer: ' + targetFuzzer);
// Build a fresh version of the project.
const buildResult: boolean = await buildFuzzersFromWorkspace(
targetProject,
'',
true
);
if (!buildResult) {
println('Failed to build fuzzers');
return false;
}
// We have a fresh build of the project, proceed to reproduce the testcase.
const crashInputTestCase =
extensionConfig.crashesDirectory +
'/' +
'clusterfuzz-testcase-minimized-' +
targetFuzzer +
'-' +
crashInfoFileInput;
// Run reproduce command against the target file
// Build the fuzzers using OSS-Fuzz infrastructure.
const cmdToExec = 'python3';
const args = [
extensionConfig.ossFuzzPepositoryWorkPath + '/infra/helper.py',
'reproduce',
targetProject,
targetFuzzer,
crashInputTestCase,
];
if (!(await systemSyncLogIfFailure(cmdToExec, args))) {
println('Failed to reproduce testcase');
}
return true;
});
}

View File

@ -0,0 +1,83 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
import * as vscode from 'vscode';
import {println} from '../logger';
import {commandHistory} from '../commandUtils';
import {runFuzzerHandler} from '../ossfuzzWrappers';
export async function cmdInputCollectorRunSpecificFuzzer() {
let projectNameArg = '';
let fuzzerName = '';
let secondsToRun = '';
// Runs a fuzzer from a given project.
const projectNameFromPrompt = await vscode.window.showInputBox({
value: '',
placeHolder: 'Type a project name',
});
if (!projectNameFromPrompt) {
println('Failed to get project name');
return;
}
projectNameArg = projectNameFromPrompt.toString();
const fuzzerNameFromPrompt = await vscode.window.showInputBox({
value: '',
placeHolder: 'Type a fuzzer name',
});
if (!fuzzerNameFromPrompt) {
println('Failed to get fuzzer name');
return;
}
fuzzerName = fuzzerNameFromPrompt.toString();
const secondsToRunInp = await vscode.window.showInputBox({
value: '',
placeHolder: 'Type the number of seconds to run the fuzzer',
});
if (!secondsToRunInp) {
return;
}
secondsToRun = secondsToRunInp.toString();
// Create an history object
const args = new Object({
projectName: projectNameArg,
fuzzerName: fuzzerName,
secondsToRun: secondsToRun,
fuzzerCorpusPath: '',
});
const commandObject = new Object({
commandType: 'oss-fuzz.RunFuzzer',
Arguments: args,
dispatcherFunc: cmdDispatchRunFuzzerHandler,
});
console.log('L1: ' + commandHistory.length);
commandHistory.push(commandObject);
await cmdDispatchRunFuzzerHandler(args);
return true;
}
async function cmdDispatchRunFuzzerHandler(args: any) {
await runFuzzerHandler(
args.projectName,
args.fuzzerName,
args.secondsToRun,
args.fuzzerCorpusPath
);
return;
}

View File

@ -0,0 +1,59 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
import * as vscode from 'vscode';
import {println} from '../logger';
import {extensionConfig} from '../config';
// Set the oss-fuzz path.
export async function setOssFuzzPath() {
println('Setting path');
const newOssFuzzPath = await vscode.window.showInputBox({
value: '',
placeHolder: 'Type path',
});
if (!newOssFuzzPath) {
println('Failed getting path');
return;
}
const fpathh = vscode.Uri.file(newOssFuzzPath);
let isValid = false;
try {
if (await vscode.workspace.fs.readDirectory(fpathh)) {
println('Is a directory');
const helperPathURI = vscode.Uri.file(
newOssFuzzPath + '/infra/helper.py'
);
if (await vscode.workspace.fs.readFile(helperPathURI)) {
println('Found helper file');
isValid = true;
}
isValid = true;
} else {
isValid = false;
}
} catch {
isValid = false;
}
if (isValid) {
extensionConfig.ossFuzzPepositoryWorkPath = newOssFuzzPath;
} else {
println('Not setting OSS-Fuzz path');
}
}

View File

@ -0,0 +1,87 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
import * as vscode from 'vscode';
import {println} from '../logger';
import {determineWorkspaceLanguage} from '../utils';
import {cifuzzGenerator} from '../cifuzz';
export async function setupCIFuzzHandler() {
const workspaceFolder = vscode.workspace.workspaceFolders;
if (!workspaceFolder) {
return false;
}
const wsPath = workspaceFolder[0].uri.fsPath; // gets the path of the first workspace folder
/**
* Go through GitHub workflows to find potential traces of CIFuzz
*/
const githubWorkflowsPath = vscode.Uri.file(wsPath + '/.github/workflows');
try {
await vscode.workspace.fs.readDirectory(githubWorkflowsPath);
} catch {
println('Did not find a workflows path.');
return false;
}
for (const [name, type] of await vscode.workspace.fs.readDirectory(
githubWorkflowsPath
)) {
// Skip directories.
if (type === 2) {
continue;
}
// Read the files.
println('Is a file');
const workflowFile = vscode.Uri.file(wsPath + '/.github/workflows/' + name);
const doc = await vscode.workspace.openTextDocument(workflowFile);
if (doc.getText().includes('cifuzz')) {
println('Found existing CIFuzz, will not continue.');
return false;
}
}
println('Did not find CIFuzz, creating one.');
const projectName = await vscode.window.showInputBox({
value: '',
placeHolder: 'OSS-Fuzz project name',
});
if (!projectName) {
println('Failed to get project name');
return false;
}
/*
* There is no CIFuzz found, so we create one.
*/
// Determine the language of the workspace.
const targetLanguage = await determineWorkspaceLanguage();
println('Target language: ' + targetLanguage);
// Generate a CIFuzz workflow text.
const cifuzzWorkflowText = cifuzzGenerator(targetLanguage, projectName, 30);
// Create the CIFuzz .yml file and write the contents to it to path
// .github/workflows/cifuzz.yml
const cifuzzYml = vscode.Uri.file(wsPath + '/.github/workflows/cifuzz.yml');
const wsedit = new vscode.WorkspaceEdit();
wsedit.createFile(cifuzzYml, {ignoreIfExists: true});
wsedit.insert(cifuzzYml, new vscode.Position(0, 0), cifuzzWorkflowText);
vscode.workspace.applyEdit(wsedit);
return true;
}

View File

@ -0,0 +1,53 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
import {println} from '../logger';
import {extensionConfig} from '../config';
import {isPathValidOssFuzzPath} from '../ossfuzzWrappers';
import {systemSync} from '../utils';
/**
* Function for setting up oss-fuzz. This clones the relevant directory
* and sets the oss-fuzz variable accordingly.
*/
export async function setUpOssFuzzHandler() {
println('Setting up oss-fuzz in /tmp/');
// First check if we already have an OSS-Fuzz path
const tmpOssFuzzRepositoryPath = '/tmp/oss-fuzz';
if ((await isPathValidOssFuzzPath(tmpOssFuzzRepositoryPath)) === true) {
println('OSS-Fuzz already exists in /tmp/oss-fuzz');
extensionConfig.ossFuzzPepositoryWorkPath = tmpOssFuzzRepositoryPath;
return;
}
const cmdToExec = 'git';
const args: Array<string> = [
'clone',
'https://github.com/google/oss-fuzz',
tmpOssFuzzRepositoryPath,
];
const [res, output] = await systemSync(cmdToExec, args);
if (res === false) {
println('Failed to clone oss-fuzz');
println(output);
return;
}
println('Finished cloning oss-fuzz');
extensionConfig.ossFuzzPepositoryWorkPath = tmpOssFuzzRepositoryPath;
}

View File

@ -0,0 +1,357 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
/**
* Command for generating template fuzzers. This is a short-cut for rapid
* prototyping as well as an archive for inspiration.
*/
import * as vscode from 'vscode';
import {println} from '../logger';
const cLangSimpleStringFuzzer = `#include <stdint.h>
#include <string.h>
#include <stdlib.h>
int
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
char *new_str = (char *)malloc(size+1);
if (new_str == NULL){
return 0;
}
memcpy(new_str, data, size);
new_str[size] = '\\0';
// Insert fuzzer contents here
// fuzz data in new_str
// end of fuzzer contents
free(new_str);
return 0;
}`;
const cLangFileInputFuzzer = `int
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
char filename[256];
sprintf(filename, "/tmp/libfuzzer.%d", getpid());
// Create a file on the filesystem with fuzzer data in it
FILE *fp = fopen(filename, "wb");
if (!fp) {
return 0;
}
fwrite(data, size, 1, fp);
fclose(fp);
// Fuzzer logic here. Use the file as a source of data.
// Fuzzer logic end
// Clean up the file.
unlink(filename);
return 0;
}`;
const cLangBareTemplateFuzzer = `int
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
return 0;
}`;
const cppLangBareTemplateFuzzer = `extern "C" int
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
return 0;
}`;
const cppLangStdStringTemplateFuzzer = `extern "C" int
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
std::string input(reinterpret_cast<const char*>(data), size);
return 0;
}`;
const cppLangFDPTemplateFuzzer = `#include <fuzzer/FuzzedDataProvider.h>
#include <string>
extern "C" int
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
FuzzedDataProvider fdp(data, size);
// Extract higher level data types used for fuzzing, e.g.
// int ran_int = fdp.ConsumeIntegralInRange<int>(1, 1024);
// std::string s = fdp.ConsumeRandomLengthString();
return 0;
}`;
const cppLangFileInputFuzzer = `extern "C" int
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
char filename[256];
sprintf(filename, "/tmp/libfuzzer.%d", getpid());
FILE *fp = fopen(filename, "wb");
if (!fp) {
return 0;
}
fwrite(data, size, 1, fp);
fclose(fp);
// Fuzzer logic here
// Fuzzer logic end
unlink(filename);
}`;
const pythonLangBareTemplate = `import sys
import atheris
def TestOneInput(fuzz_bytes):
return
def main():
atheris.Setup(sys.argv, TestOneInput)
atheris.Fuzz()
if __name__ == "__main__":
main()`;
const pythonLangFileInputFuzzer = `import sys
import atheris
@atheris.instrument_func
def TestOneInput(data):
# Write fuzz data to a file
with open('/tmp/fuzz_input.b') as f:
f.write(data)
# Use '/tmp/fuzz_input.b' as input to file handling logic.
def main():
atheris.instrument_all()
atheris.Setup(sys.argv, TestOneInput)
atheris.Fuzz()
if __name__ == "__main__":
main()`;
const pythonLongFdpTemplate = `import sys
import atheris
def TestOneInput(fuzz_bytes):
fdp = atheris.FuzzedDataProvider(fuzz_bytes)
return
def main():
atheris.Setup(sys.argv, TestOneInput)
atheris.Fuzz()
if __name__ == "__main__":
main()`;
const javaLangBareTemplate = `import com.code_intelligence.jazzer.api.FuzzedDataProvider;
public class SampleFuzzer {
public static void fuzzerTestOneInput(FuzzedDataProvider fdp) {
// Use fdp to create arbitrary types seeded with fuzz data
}
}
`;
/**
* C templates
*/
async function cTemplates() {
let template = '';
const result = await vscode.window.showQuickPick(
['Bare template', 'Null-terminated string input', 'File input'],
{
placeHolder: 'Pick which template',
}
);
vscode.window.showInformationMessage(`Got: ${result}`);
if (result === 'Null-terminated string input') {
template = cLangSimpleStringFuzzer;
} else if (result === 'File input') {
template = cLangFileInputFuzzer;
} else if (result === 'Bare template') {
template = cLangBareTemplateFuzzer;
} else {
template = 'empty';
}
const workspaceFolder = vscode.workspace.workspaceFolders;
if (!workspaceFolder) {
return;
}
const wsPath = workspaceFolder[0].uri.fsPath; // gets the path of the first workspace folder
const cifuzzYml = vscode.Uri.file(wsPath + '/oss-fuzz-template.c');
const wsedit = new vscode.WorkspaceEdit();
wsedit.createFile(cifuzzYml, {ignoreIfExists: true});
wsedit.insert(cifuzzYml, new vscode.Position(0, 0), template);
vscode.workspace.applyEdit(wsedit);
return;
}
/**
* CPP templates
*/
async function cppTemplates() {
let template = '';
const result = await vscode.window.showQuickPick(
[
'Bare template',
'Simple CPP string',
'File input fuzzer',
'Fuzzed data provider',
],
{
placeHolder: 'Pick which template',
}
);
vscode.window.showInformationMessage(`Got: ${result}`);
if (result === 'Bare template') {
template = cppLangBareTemplateFuzzer;
} else if (result === 'Simple CPP string') {
template = cppLangStdStringTemplateFuzzer;
} else if (result === 'File input fuzzer') {
template = cppLangFileInputFuzzer;
} else if (result === 'Fuzzed data provider') {
template = cppLangFDPTemplateFuzzer;
} else {
template = 'empty';
}
const workspaceFolder = vscode.workspace.workspaceFolders;
if (!workspaceFolder) {
return;
}
const wsPath = workspaceFolder[0].uri.fsPath; // gets the path of the first workspace folder
const cifuzzYml = vscode.Uri.file(wsPath + '/oss-fuzz-template.cpp');
const wsedit = new vscode.WorkspaceEdit();
wsedit.createFile(cifuzzYml, {ignoreIfExists: true});
wsedit.insert(cifuzzYml, new vscode.Position(0, 0), template);
vscode.workspace.applyEdit(wsedit);
return;
}
/**
* Python templates
*/
async function pythonTepmlates() {
let template = '';
const result = await vscode.window.showQuickPick(
['Bare template', 'Fuzzed Data Provider', 'File input fuzzer'],
{
placeHolder: 'Pick which template',
}
);
vscode.window.showInformationMessage(`Got: ${result}`);
if (result === 'Fuzzed Data Provider') {
template = pythonLongFdpTemplate;
} else if (result === 'Bare template') {
template = pythonLangBareTemplate;
} else if (result === 'File input fuzzer') {
template = pythonLangFileInputFuzzer;
} else {
template = 'empty';
}
const workspaceFolder = vscode.workspace.workspaceFolders;
if (!workspaceFolder) {
return;
}
const wsPath = workspaceFolder[0].uri.fsPath; // gets the path of the first workspace folder
const cifuzzYml = vscode.Uri.file(wsPath + '/oss-fuzz-template.py');
const wsedit = new vscode.WorkspaceEdit();
wsedit.createFile(cifuzzYml, {ignoreIfExists: true});
wsedit.insert(cifuzzYml, new vscode.Position(0, 0), template);
vscode.workspace.applyEdit(wsedit);
return;
}
/**
* Java templates
*/
async function javaTemplates() {
let template = '';
const result = await vscode.window.showQuickPick(['Bare template'], {
placeHolder: 'Pick which template',
});
vscode.window.showInformationMessage(`Got: ${result}`);
if (result === 'Bare template') {
template = javaLangBareTemplate;
} else {
template = 'empty';
}
const workspaceFolder = vscode.workspace.workspaceFolders;
if (!workspaceFolder) {
return;
}
const wsPath = workspaceFolder[0].uri.fsPath; // gets the path of the first workspace folder
const cifuzzYml = vscode.Uri.file(wsPath + '/oss-fuzz-template.java');
const wsedit = new vscode.WorkspaceEdit();
wsedit.createFile(cifuzzYml, {ignoreIfExists: true});
wsedit.insert(cifuzzYml, new vscode.Position(0, 0), template);
vscode.workspace.applyEdit(wsedit);
return;
}
export async function cmdDispatcherTemplate(context: vscode.ExtensionContext) {
println('Creating template');
const options: {
[key: string]: (context: vscode.ExtensionContext) => Promise<void>;
} = {
C: cTemplates,
CPP: cppTemplates,
Python: pythonTepmlates,
Java: javaTemplates,
};
const quickPick = vscode.window.createQuickPick();
quickPick.items = Object.keys(options).map(label => ({label}));
quickPick.onDidChangeSelection(selection => {
if (selection[0]) {
options[selection[0].label](context).catch(console.error);
}
});
quickPick.onDidHide(() => quickPick.dispose());
quickPick.placeholder = 'Pick language';
quickPick.show();
return;
}

View File

@ -0,0 +1,85 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
import * as vscode from 'vscode';
import {println} from '../logger';
import {runFuzzerHandler, buildFuzzersFromWorkspace} from '../ossfuzzWrappers';
import {commandHistory} from '../commandUtils';
import {extensionConfig} from '../config';
/**
* Does an end-to-end test of a project/fuzzer. This is done by
* first building the project and then running the fuzzer.
* @param context
* @returns
*/
export async function cmdInputCollectorTestFuzzer() {
// Get the project name and fuzzer name to test.
const ossFuzzProjectNameInput = await vscode.window.showInputBox({
value: '',
placeHolder: 'The OSS-Fuzz project name',
});
if (!ossFuzzProjectNameInput) {
println('Did not get a ossFuzzTargetProject');
return;
}
println('Project name: ' + ossFuzzProjectNameInput);
// Get the fuzzer to run
const fuzzerNameInput = await vscode.window.showInputBox({
value: '',
placeHolder: 'Type a fuzzer name',
});
if (!fuzzerNameInput) {
println('Failed to get fuzzer name');
return;
}
// Create the args object for the dispatcher
const args = new Object({
projectName: ossFuzzProjectNameInput.toString(),
fuzzerName: fuzzerNameInput.toString(),
});
// Create a dispatcher object.
const commandObject = new Object({
commandType: 'oss-fuzz.TestFuzzer',
Arguments: args,
dispatcherFunc: cmdDispatchTestFuzzerHandler,
});
commandHistory.push(commandObject);
await cmdDispatchTestFuzzerHandler(args);
}
async function cmdDispatchTestFuzzerHandler(args: any) {
// Build the project
if (!(await buildFuzzersFromWorkspace(args.projectName, '', false))) {
println('Build projects');
return;
}
// Run the fuzzer for 10 seconds
println('Running fuzzer');
await runFuzzerHandler(
args.projectName,
args.fuzzerName,
extensionConfig.numberOfSecondsForTestRuns.toString(),
''
);
return;
}

View File

@ -0,0 +1,37 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
import {println} from './logger';
export class ExtensionConfig {
/** Path to the repository that will be used. */
ossFuzzPepositoryWorkPath: string = '/tmp/oss-fuzz';
/** The directory where crash info is stored. */
crashesDirectory = process.env.HOME + '/oss-fuzz-crashes';
/** Number of seconds used for running quick test fuzzers */
numberOfSecondsForTestRuns = 20;
async printConfig() {
println('Config:');
println('- OSS-Fuzz repository path: ' + this.ossFuzzPepositoryWorkPath);
println('- Crashes directory: ' + this.crashesDirectory);
println('- numberOfSecondsForTestRuns: ' + this.numberOfSecondsForTestRuns);
}
}
export const extensionConfig = new ExtensionConfig();

View File

@ -0,0 +1,224 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
import * as vscode from 'vscode';
import {Uri} from 'vscode';
import {println} from './logger';
const path = require('path');
let isCodeCoverageEnabled = false;
// create a decorator type that we use to decorate small numbers
const codeCoveredLineDecorationType =
vscode.window.createTextEditorDecorationType({
backgroundColor: '#184916',
overviewRulerColor: 'blue',
overviewRulerLane: vscode.OverviewRulerLane.Right,
light: {
// this color will be used in light color themes
borderColor: 'darkblue',
},
dark: {
// this color will be used in dark color themes
borderColor: 'lightblue',
},
});
const missingLineDecorationType = vscode.window.createTextEditorDecorationType({
backgroundColor: '#6C2B34',
overviewRulerColor: 'blue',
overviewRulerLane: vscode.OverviewRulerLane.Right,
light: {
// this color will be used in light color themes
borderColor: 'darkblue',
},
dark: {
// this color will be used in dark color themes
borderColor: 'lightblue',
},
});
/**
*
* @param context Adds visualisation to the editor based on reading a code coverage file.
* @param codeCoverageFile
*/
export async function loadCoverageIntoWorkspace(
context: vscode.ExtensionContext,
codeCoverageFile: Uri
) {
isCodeCoverageEnabled = true;
const doc3 = await vscode.workspace.openTextDocument(codeCoverageFile);
const jsonCodeCoverageObj3 = JSON.parse(doc3.getText());
const codeCoverageMappingWithCoverage = new Map();
const codeCoverageMapMissingCoverage = new Map();
Object.entries(jsonCodeCoverageObj3['files']).forEach(entry => {
const [key, value] = entry;
println(key);
const filename = path.parse(key).base;
println('Filename base: ' + filename);
const objectDictionary: any = value as any;
const linesWithCodeCoverage: unknown[] = [];
println(objectDictionary['executed_lines']);
Object.entries(objectDictionary['executed_lines']).forEach(entryInner => {
const lineNumber = entryInner[1];
//println("executed line: " + lineNumber);
linesWithCodeCoverage.push(lineNumber);
});
codeCoverageMappingWithCoverage.set(filename, linesWithCodeCoverage);
const linesMissingCodeCoverage: unknown[] = [];
Object.entries(objectDictionary['missing_lines']).forEach(entryInner => {
const lineNumber = entryInner[1];
//println("executed line: " + line_numb);
linesMissingCodeCoverage.push(lineNumber);
});
codeCoverageMapMissingCoverage.set(filename, linesMissingCodeCoverage);
});
println('=========>');
println('Enabling code coverage decorator');
println('decorator sample is activated');
let timeout: NodeJS.Timer | undefined = undefined;
// create a decorator type that we use to decorate large numbers
let activeEditor = vscode.window.activeTextEditor;
function updateDecorations(
linesWithCodeCoverage: any,
linesWithoNoCodeCoverage: any
) {
if (!isCodeCoverageEnabled) {
return;
}
if (!activeEditor) {
return;
}
println('Filename');
println(activeEditor.document.fileName);
// Current file opened in the editor.
const nameOfCurrentFile = path.parse(activeEditor.document.fileName).base;
println('Base filename: ' + nameOfCurrentFile);
println('Done filename');
const lineNumbersWithCoverage: vscode.DecorationOptions[] = [];
const missingLineNumbers: vscode.DecorationOptions[] = [];
if (linesWithCodeCoverage.has(nameOfCurrentFile)) {
println('Has this file');
const elemWithCov = linesWithCodeCoverage.get(nameOfCurrentFile);
for (let idx = 0; idx < elemWithCov.length; idx++) {
const lineNo = elemWithCov[idx];
println('Setting up: ' + lineNo);
lineNumbersWithCoverage.push({
range: new vscode.Range(lineNo - 1, 0, lineNo, 0),
});
}
const elemNoCov = linesWithoNoCodeCoverage.get(nameOfCurrentFile);
for (let idx = 0; idx < elemNoCov.length; idx++) {
const lineNo = elemNoCov[idx];
println('Setting up: ' + lineNo);
missingLineNumbers.push({
range: new vscode.Range(lineNo - 1, 0, lineNo, 0),
});
}
} else {
println('Does not have this file');
}
activeEditor.setDecorations(
codeCoveredLineDecorationType,
lineNumbersWithCoverage
);
activeEditor.setDecorations(missingLineDecorationType, missingLineNumbers);
//activeEditor.setDecorations(largeNumberDecorationType, largeNumbers);
}
function triggerUpdateDecorations(
throttle = false,
covMap: any,
covMisMap: any
) {
if (timeout) {
clearTimeout(timeout);
timeout = undefined;
}
if (throttle) {
//timeout = setTimeout(updateDecorations, 500);
updateDecorations(covMap, covMisMap);
} else {
updateDecorations(covMap, covMisMap);
}
}
if (activeEditor) {
triggerUpdateDecorations(
false,
codeCoverageMappingWithCoverage,
codeCoverageMapMissingCoverage
);
}
vscode.window.onDidChangeActiveTextEditor(
editor => {
activeEditor = editor;
if (editor) {
triggerUpdateDecorations(
false,
codeCoverageMappingWithCoverage,
codeCoverageMapMissingCoverage
);
}
},
null,
context.subscriptions
);
vscode.workspace.onDidChangeTextDocument(
event => {
if (activeEditor && event.document === activeEditor.document) {
triggerUpdateDecorations(
true,
codeCoverageMappingWithCoverage,
codeCoverageMapMissingCoverage
);
}
},
null,
context.subscriptions
);
}
/**
* Removes the values from the mappings used to track code coverage. As a
* result, the visualisation disappears.
*/
export async function clearCoverage() {
// Set global indicator.
const activeEditor = vscode.window.activeTextEditor;
isCodeCoverageEnabled = false;
if (activeEditor) {
activeEditor.setDecorations(codeCoveredLineDecorationType, []);
activeEditor.setDecorations(missingLineDecorationType, []);
}
}

View File

@ -0,0 +1,164 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
import * as vscode from 'vscode';
import {clearCoverage} from './coverageHelper';
import {println} from './logger';
// Import the command dispatcher functions
import {cmdInputCollectorRunSpecificFuzzer} from './commands/cmdRunFuzzer';
import {cmdInputCollectorBuildFuzzersFromWorkspace} from './commands/cmdBuildFuzzerFromWorkspace';
import {cmdDispatcherRe} from './commands/cmdRedo';
import {setupCIFuzzHandler} from './commands/cmdSetupCIFuzz';
import {cmdInputCollectorTestFuzzer} from './commands/cmdTestFuzzer';
import {displayCodeCoverageFromOssFuzz} from './commands/cmdDisplayCoverage';
import {createOssFuzzSetup} from './commands/cmdCreateOSSFuzzSetup';
import {runEndToEndAndGetCoverage} from './commands/cmdEndToEndCoverage';
import {listFuzzersHandler} from './commands/cmdListFuzzers';
import {cmdInputCollectorReproduceTestcase} from './commands/cmdReproduceTestcase';
import {cmdDispatcherTemplate} from './commands/cmdTemplate';
import {setUpOssFuzzHandler} from './commands/cmdSetupOSSFuzz';
import {setOssFuzzPath} from './commands/cmdSetOSSFuzzPath';
import {extensionConfig} from './config';
/**
* Extension entrypoint. Activate the extension and register the commands.
*/
export function activate(context: vscode.ExtensionContext) {
console.log('Activating extension)');
extensionConfig.printConfig();
println('OSS-Fuzz extension is now active!');
// Command registration
context.subscriptions.push(
vscode.commands.registerCommand('oss-fuzz.SetUp', async () => {
println('CMD start: SetUp');
await setUpOssFuzzHandler();
println('CMD end: SetUp');
})
);
context.subscriptions.push(
vscode.commands.registerCommand('oss-fuzz.RunFuzzer', async () => {
println('CMD start: Run Fuzzer');
//await runFuzzerHandler('', '', '', '');
cmdInputCollectorRunSpecificFuzzer();
println('CMD end: Run Fuzzer');
})
);
context.subscriptions.push(
vscode.commands.registerCommand('oss-fuzz.ListFuzzers', async () => {
println('CMD start: ListFuzzers');
await listFuzzersHandler();
println('CMD end: ListFuzzers');
})
);
context.subscriptions.push(
vscode.commands.registerCommand('oss-fuzz.SetOSSFuzzPath', async () => {
println('CMD start: SetOSSFuzzPath');
await setOssFuzzPath();
println('CMD end: SetOSSFuzzPath');
})
);
context.subscriptions.push(
vscode.commands.registerCommand('oss-fuzz.GetCodeCoverage', async () => {
println('CMD start: GetCodeCoverage');
await displayCodeCoverageFromOssFuzz(context);
println('CMD end: GetCodeCoverage');
})
);
context.subscriptions.push(
vscode.commands.registerCommand('oss-fuzz.ClearCodeCoverage', async () => {
println('CMD start: ClearCodeCoverage');
await clearCoverage();
println('CMD end: ClearCodeCoverage');
})
);
context.subscriptions.push(
vscode.commands.registerCommand('oss-fuzz.CreateOSSFuzzSetup', async () => {
println('CMD start: CreateOSSFuzzSetup');
await createOssFuzzSetup();
println('CMD end: CreateOSSFuzzSetup');
})
);
context.subscriptions.push(
vscode.commands.registerCommand('oss-fuzz.WSBuildFuzzers', async () => {
println('CMD start: WSBuildFuzzers3');
await cmdInputCollectorBuildFuzzersFromWorkspace();
println('CMD end: WSBuildFuzzers4');
})
);
context.subscriptions.push(
vscode.commands.registerCommand('oss-fuzz.SetupCIFuzz', async () => {
println('CMD start: SetupCIFuzz');
await setupCIFuzzHandler();
println('CMD end: SetupCIFuzz');
})
);
context.subscriptions.push(
vscode.commands.registerCommand('oss-fuzz.testFuzzer', async () => {
println('CMD start: testFuzzer');
await cmdInputCollectorTestFuzzer();
println('CMD end: testFizzer');
})
);
context.subscriptions.push(
vscode.commands.registerCommand('oss-fuzz.testCodeCoverage', async () => {
println('CMD start: testCodeCoverage');
await runEndToEndAndGetCoverage(context);
println('CMD end: testCodeCoverage');
})
);
context.subscriptions.push(
vscode.commands.registerCommand('oss-fuzz.Reproduce', async () => {
println('CMD start: Reproduce');
await cmdInputCollectorReproduceTestcase();
println('CMD end: Reproduce');
})
);
context.subscriptions.push(
vscode.commands.registerCommand('oss-fuzz.Redo', async () => {
println('CMD start: Re');
await cmdDispatcherRe();
println('CMD end: Re');
})
);
context.subscriptions.push(
vscode.commands.registerCommand('oss-fuzz.Template', async () => {
println('CMD start: remplate');
await cmdDispatcherTemplate(context);
println('CMD end: template');
})
);
}
// This method is called when your extension is deactivated
export function deactivate() {
println('Deactivating the extension');
}

View File

@ -0,0 +1,27 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
import * as vscode from 'vscode';
export const vscodeOutputChannel =
vscode.window.createOutputChannel('oss-fuzz');
export function println(line: string) {
vscodeOutputChannel.appendLine(line);
}
export function debugPrintln(line: string) {
console.log(line);
}

View File

@ -0,0 +1,318 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
const fs = require('fs');
import * as vscode from 'vscode';
import {
hasOssFuzzInWorkspace,
getOssFuzzWorkspaceProjectName,
listFuzzersForProject,
systemSyncLogIfFailure,
} from './utils';
import {println} from './logger';
import {extensionConfig} from './config';
/**
* Builds the fuzzers for a given workspace.
*
* There are two options:
* 1) The fuzzers are build using the OSS-Fuzz set up in the folder
* 2) The fuzzers are build using the workspace and then copies that over.
*/
export async function buildFuzzersFromWorkspace(
projectNameArg: string,
sanitizer: string,
toClean: boolean
) {
// println('Building fuzzers locally2');
// Check if there is an OSS-Fuzz set up, and exit if not.
if (
(await isPathValidOssFuzzPath(
extensionConfig.ossFuzzPepositoryWorkPath
)) === false
) {
println('No valid oss-fuzz path');
return false;
}
const workspaceFolder = vscode.workspace.workspaceFolders;
if (!workspaceFolder) {
println('No workspace folder, exiting');
return false;
}
let ossFuzzProjectName = '';
if (await hasOssFuzzInWorkspace()) {
/**
* The fuzzers are in the workspace, as opposed to e.g. the oss-fuzz dirctory.
*/
ossFuzzProjectName = await getOssFuzzWorkspaceProjectName();
/**
* The workspace has an OSS-Fuzz directory. We use this for the build.
* This is done by copying over the relevant files to the oss-fuzz repository
* folder. Notice that we will do a forceful copy overwriting the existing
* project foler if it exists.
*/
println('Found project folder: ' + ossFuzzProjectName);
// Copy over the workspace oss-fuzz set up to the oss-fuzz folder.
let cmdToExec = 'cp';
let args: Array<string> = [
'-rfT',
workspaceFolder[0].uri.path + '/OSS-Fuzz/' + ossFuzzProjectName,
extensionConfig.ossFuzzPepositoryWorkPath +
'/projects/' +
ossFuzzProjectName +
'/',
];
if (!(await systemSyncLogIfFailure(cmdToExec, args))) {
println('Failed to copy project');
return false;
}
// Build the fuzzers using OSS-Fuzz infrastructure.
cmdToExec = 'python3';
args = [
extensionConfig.ossFuzzPepositoryWorkPath + '/infra/helper.py',
'build_fuzzers',
];
println('DECIDING ABOUT SANITIZER');
if (sanitizer !== '') {
println('ADDING CODE COVERAGE SANITIZER');
args.push('--sanitizer=' + sanitizer);
}
if (toClean) {
args.push('--clean');
}
args.push(ossFuzzProjectName);
println('Building fuzzers');
if (!(await systemSyncLogIfFailure(cmdToExec, args))) {
println('Failed to build fuzzers');
return false;
}
} else {
ossFuzzProjectName = projectNameArg;
const targetOssFuzzProject = vscode.Uri.file(
extensionConfig.ossFuzzPepositoryWorkPath +
'/projects/' +
ossFuzzProjectName
);
// Check if the folder exists.
let projectHasOssFuzzFolder = false;
try {
await vscode.workspace.fs.readDirectory(targetOssFuzzProject);
projectHasOssFuzzFolder = true;
} catch {
projectHasOssFuzzFolder = false;
}
/**
* The workspace does not have a OSS-Fuzz specific folder but has
* a folder in the OSS-Fuzz/projects/* directory. As such, we build
* the project using that build.sh set up, but, instead of cloning
* the repository we mount the workspace root onto what would normally
* be cloned.
*/
if (projectHasOssFuzzFolder) {
// println('Found a target directory');
// Build the fuzzers using OSS-Fuzz infrastructure.
// First, Set up a temporary workpath that will be cleanup after
const wsPath = workspaceFolder[0].uri.fsPath; // gets the path of the first workspace folder
const cmdToExec2 = 'cp';
const temporaryProjectPath =
extensionConfig.ossFuzzPepositoryWorkPath +
'/projects/' +
ossFuzzProjectName +
'/temporary-project';
const args2: Array<string> = [
'-rfT',
wsPath.toString(),
temporaryProjectPath,
];
if (!(await systemSyncLogIfFailure(cmdToExec2, args2))) {
println('Failed to build fuzzers');
return false;
}
//const wsPath = workspaceFolder[0].uri.fsPath; // gets the path of the first workspace folder
const temporaryDockerPath =
extensionConfig.ossFuzzPepositoryWorkPath +
'/projects/' +
ossFuzzProjectName +
'/Dockerfile';
const temporaryDockerPath2 =
extensionConfig.ossFuzzPepositoryWorkPath +
'/projects/' +
ossFuzzProjectName +
'/Dockerfile2';
const args3: Array<string> = [temporaryDockerPath, temporaryDockerPath2];
if (!(await systemSyncLogIfFailure('cp', args3))) {
println('Failed to copy Dockerfile');
return false;
}
// Append COPY command to Dockerfile
fs.appendFileSync(
temporaryDockerPath,
'COPY temporary-project /src/' + ossFuzzProjectName
);
// Second, build the actual fuzzers using the temporarily created project path for mount.
const cmdToExec = 'python3';
const args = [
extensionConfig.ossFuzzPepositoryWorkPath + '/infra/helper.py',
'build_fuzzers', // command
];
// Add sanitizer if needed.
if (sanitizer !== '') {
args.push('--sanitizer=' + sanitizer);
}
// Add clean flag if needed.
if (toClean) {
args.push('--clean');
}
args.push(ossFuzzProjectName);
/*
Previously we used OSS-Fuzz logic that supports mounting paths for getting
the workspace into the Dockerfile.
This approach, however, has limitations in that most builds will modify
the contents of the folder they're working in. This can cause issues and also
make it not possible to build several versions of the project with changing
sanitizers in a sequence. As such, we disbanded.
*/
println('Building fuzzers');
if (!(await systemSyncLogIfFailure(cmdToExec, args))) {
println('Failed to copy Dockerfile');
// Move back the modified Dockerfile
const args5: Array<string> = [
temporaryDockerPath2,
temporaryDockerPath,
];
if (!(await systemSyncLogIfFailure('mv', args5))) {
println('Failed to copy back Dockerfile');
return false;
}
return false;
}
// Move back the modified Dockerfile
const args5: Array<string> = [temporaryDockerPath2, temporaryDockerPath];
if (!(await systemSyncLogIfFailure('mv', args5))) {
println('Failed to copy back Dockerfile');
return false;
}
} else {
println('OSS-Fuzz does not have the relevant project folder');
return false;
}
}
// If we go to here we successfully build the project. Give information.
vscode.window.showInformationMessage('Successfully build project');
// List the fuzzers build
await listFuzzersForProject(
ossFuzzProjectName,
extensionConfig.ossFuzzPepositoryWorkPath
);
return true;
}
/**
* Runs the fuzzer for a given project.
*/
export async function runFuzzerHandler(
projectNameArg: string,
fuzzerNameArg: string,
secondsToRunArg: string,
fuzzerCorpusPath: string
) {
// Check there is a valid OSS-Fuzz path. If not, bail out.
if (
(await isPathValidOssFuzzPath(
extensionConfig.ossFuzzPepositoryWorkPath
)) === false
) {
println('Missing valid OSS-Fuzz path.');
return;
}
// The fuzzer is run by way of OSS-Fuzz's helper.py so we use python3 to launch
// this script.
const cmdToExec = 'python3';
// Set the arguments correctly. The ordering here is important for compatibility
// with the underlying argparse used by OSS-Fuzz helper.py.
const args: Array<string> = [
extensionConfig.ossFuzzPepositoryWorkPath + '/infra/helper.py',
'run_fuzzer',
];
if (fuzzerCorpusPath !== '') {
args.push('--corpus-dir');
args.push(fuzzerCorpusPath);
}
args.push(projectNameArg);
args.push(fuzzerNameArg);
args.push('--');
args.push('-max_total_time=' + secondsToRunArg);
println(
'Running fuzzer' +
fuzzerNameArg +
' from project ' +
projectNameArg +
' for ' +
secondsToRunArg +
' seconds.'
);
// Run the actual command
if (!(await systemSyncLogIfFailure(cmdToExec, args))) {
println('Failed to run fuzzer');
return false;
}
return true;
}
// Validates if a directory is a valid oss-fuzz path.
export async function isPathValidOssFuzzPath(path: string) {
try {
if (await vscode.workspace.fs.readDirectory(vscode.Uri.file(path))) {
// println('Is a directory');
// const helperPath = vscode.Uri.file(path + '/infra/helper.py');
const helperPath = path + '/infra/helper.py';
//console.log('Checking ' + helperPath.toString());
if (fs.existsSync(helperPath.toString())) {
return true;
}
}
} catch {
/* empty */
}
return false;
}

View File

@ -0,0 +1,206 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
import * as vscode from 'vscode';
import path = require('path');
import {println} from './logger';
export async function setupProjectInitialFiles() {
const wsedit = new vscode.WorkspaceEdit();
const workspaceFolder = vscode.workspace.workspaceFolders;
const projectGithubRepository = await vscode.window.showInputBox({
value: '',
placeHolder: 'Github repository for the project.',
});
if (!projectGithubRepository) {
return;
}
const projectNameFromRepo = path.parse(projectGithubRepository).base;
println('Derived project name: ' + projectNameFromRepo);
const pythonFiles = await vscode.workspace.findFiles('**/*.py');
const cppFiles = await vscode.workspace.findFiles('**/*.c++');
const cfiles = await vscode.workspace.findFiles('**/*.c');
const rustFiles = await vscode.workspace.findFiles('**/*.rust');
const golangFiles = await vscode.workspace.findFiles('**/*.go');
println('Number of python files: ' + pythonFiles.length);
println('Number of C++ files: ' + cppFiles.length);
println('Number of C files: ' + cfiles.length);
println('Number of rustFiles files: ' + rustFiles.length);
println('Number of golangFiles files: ' + golangFiles.length);
const maxCount = Math.max(
pythonFiles.length,
cppFiles.length,
cfiles.length,
rustFiles.length,
golangFiles.length
);
let target = '';
if (maxCount === pythonFiles.length) {
target = 'python';
} else {
target = 'not implemented';
}
println('Target language: ' + target);
if (workspaceFolder) {
const wsPath = workspaceFolder[0].uri.fsPath; // gets the path of the first workspace folder
const ossfuzzDockerFilepath = vscode.Uri.file(
wsPath + '/OSS-Fuzz/' + projectNameFromRepo + '/Dockerfile'
);
vscode.window.showInformationMessage(ossfuzzDockerFilepath.toString());
wsedit.createFile(ossfuzzDockerFilepath, {ignoreIfExists: true});
const todaysDate = new Date();
const currentYear = todaysDate.getFullYear();
if (target === 'python') {
const dockerfileTemplate = `# Copyright ${currentYear} Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
################################################################################
FROM gcr.io/oss-fuzz-base/base-builder-python
RUN python3 -m pip install --upgrade pip
RUN git clone --depth 1 ${projectGithubRepository} ${projectNameFromRepo}
WORKDIR ${projectNameFromRepo}
COPY build.sh *.py $SRC/`;
wsedit.insert(
ossfuzzDockerFilepath,
new vscode.Position(0, 0),
dockerfileTemplate
);
const ossfuzzBuildFilepath = vscode.Uri.file(
wsPath + '/OSS-Fuzz/' + projectNameFromRepo + '/build.sh'
);
vscode.window.showInformationMessage(ossfuzzBuildFilepath.toString());
wsedit.createFile(ossfuzzBuildFilepath, {ignoreIfExists: true});
const buildTemplate = `#!/bin/bash -eu
# Copyright ${currentYear} Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
################################################################################
python3 -m pip install .
# Build fuzzers (files prefixed with fuzz_) to $OUT
for fuzzer in $(find $SRC -name 'fuzz_*.py'); do
compile_python_fuzzer $fuzzer
done`;
wsedit.insert(
ossfuzzBuildFilepath,
new vscode.Position(0, 0),
buildTemplate
);
// project.yaml
const projectYamlFilepath = vscode.Uri.file(
wsPath + '/OSS-Fuzz/' + projectNameFromRepo + '/project.yaml'
);
vscode.window.showInformationMessage(projectYamlFilepath.toString());
wsedit.createFile(projectYamlFilepath, {ignoreIfExists: true});
const projectYamlTemplate = `homepage: "${projectGithubRepository}"
language: python
primary_contact: "<primary_contact_email>"
main_repo: "${projectGithubRepository}"
file_github_issue: true
`;
wsedit.insert(
projectYamlFilepath,
new vscode.Position(0, 0),
projectYamlTemplate
);
/* Sample template fuzzer */
const sampleFuzzFile = vscode.Uri.file(
wsPath + '/OSS-Fuzz/' + projectNameFromRepo + '/fuzz_ex1.py'
);
vscode.window.showInformationMessage(projectYamlFilepath.toString());
wsedit.createFile(sampleFuzzFile, {ignoreIfExists: true});
const sampleFuzzFileContents = `import sys
import atheris
with atheris.instrument_imports():
# Import your target modules here to have them
# instrumented by the fuzzer, e.g:
# import MODULE_NAME
pass
@atheris.instrument_func
def TestOneInput(data):
fdp = atheris.FuzzedDataProvider(data)
def main():
# atheris.instrument_all()
atheris.Setup(sys.argv, TestOneInput)
atheris.Fuzz()
if __name__ == "__main__":
main()`;
wsedit.insert(
sampleFuzzFile,
new vscode.Position(0, 0),
sampleFuzzFileContents
);
const readmeFile = vscode.Uri.file(wsPath + '/OSS-Fuzz/' + '/README.md');
vscode.window.showInformationMessage(readmeFile.toString());
wsedit.createFile(readmeFile, {ignoreIfExists: true});
const readmeContents = `# OSS-Fuzz set up
This folder is the OSS-Fuzz set up.
`;
wsedit.insert(readmeFile, new vscode.Position(0, 0), readmeContents);
vscode.workspace.applyEdit(wsedit);
}
vscode.window.showInformationMessage('Created a new file: hello/world.md');
}
}

View File

@ -0,0 +1,239 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
import * as vscode from 'vscode';
const fs = require('fs');
const {spawn} = require('node:child_process');
import {println, debugPrintln} from './logger';
/**
* Checks if the current workspace has a generated OSS-Fuzz folder. This is the
* generated folder from our auto-generation capabilities.
*
* @returns boolean
*/
export async function hasOssFuzzInWorkspace() {
const workspaceFolder = vscode.workspace.workspaceFolders;
if (!workspaceFolder) {
return false;
}
// Identify if the workspace folder has a OSS-Fuzz set up.
const wsPath = workspaceFolder[0].uri.fsPath; // gets the path of the first workspace folder
const ossfuzzDockerFilepath = vscode.Uri.file(wsPath + '/OSS-Fuzz/');
try {
if (await vscode.workspace.fs.readDirectory(ossfuzzDockerFilepath)) {
for (const [name, type] of await vscode.workspace.fs.readDirectory(
ossfuzzDockerFilepath
)) {
// If it's a directory then we know we have the project set up in there.
if (type === 2) {
// We assume this is the project folder for now.
println('Found the relevant directory: ' + name);
return true;
}
}
}
} catch {
/* empty */
}
return false;
}
/**
* Gets the project name of the integrated OSS-Fuzz project.
* @returns string
*/
export async function getOssFuzzWorkspaceProjectName() {
const workspaceFolder = vscode.workspace.workspaceFolders;
if (!workspaceFolder) {
return 'N/A';
}
// Identify if the workspace folder has a OSS-Fuzz set up.
const wsPath = workspaceFolder[0].uri.fsPath; // gets the path of the first workspace folder
const ossfuzzDockerFilepath = vscode.Uri.file(wsPath + '/OSS-Fuzz/');
try {
if (await vscode.workspace.fs.readDirectory(ossfuzzDockerFilepath)) {
for (const [name, type] of await vscode.workspace.fs.readDirectory(
ossfuzzDockerFilepath
)) {
if (type === 2) {
// println('Is a directory');
// We assume this is the project folder for now.
return name;
}
}
}
} catch {
/* empty */
}
return 'N/A';
}
/**
* Lists the fuzzers available in the OSS-Fuzz build project.
*
* @param projectName
* @param ossFuzzRepositoryPath
* @returns
*/
export async function listFuzzersForProject(
projectName: string,
ossFuzzRepositoryPath: string
) {
const projectOssFuzzBuildPath = vscode.Uri.file(
ossFuzzRepositoryPath + '/build/out/' + projectName
);
const fuzzersInProject: Array<string> = [];
for (const [name, type] of await vscode.workspace.fs.readDirectory(
projectOssFuzzBuildPath
)) {
// Is it a file?
if (type === 1) {
const filepath =
ossFuzzRepositoryPath + '/build/out/' + projectName + '/' + name;
const binary = fs.readFileSync(filepath);
// Check if fuzzer entrypoint exists in file. This is similar to how OSS-Fuzz
// checks whether a file is fuzzer or not.
if (binary.lastIndexOf('LLVMFuzzerTestOneInput') !== -1) {
fuzzersInProject.push(name);
}
}
}
println('Successfully build the project.');
println('The fuzzers in project');
for (const fuzzName of fuzzersInProject) {
println(fuzzName);
}
return fuzzersInProject;
}
/**
* Helper functions for identifying the primary programming language of the workspace.
*
* This is achieved by identifying the suffix of files and then sorting
* based on those with most of a given language supported by OSS-Fuzz.
*
* @returns
*/
export async function determineWorkspaceLanguage() {
const pythonFiles = await vscode.workspace.findFiles('**/*.py');
const cppFiles = await vscode.workspace.findFiles('**/*.c++');
const cfiles = await vscode.workspace.findFiles('**/*.c');
const rustFiles = await vscode.workspace.findFiles('**/*.rust');
const golangFiles = await vscode.workspace.findFiles('**/*.go');
println('Number of python files: ' + pythonFiles.length);
println('Number of C++ files: ' + cppFiles.length);
println('Number of C files: ' + cfiles.length);
println('Number of rustFiles files: ' + rustFiles.length);
println('Number of golangFiles files: ' + golangFiles.length);
const maxCount = Math.max(
pythonFiles.length,
cppFiles.length,
cfiles.length,
rustFiles.length,
golangFiles.length
);
let target = '';
if (maxCount === pythonFiles.length) {
target = 'python';
} else if (maxCount === cppFiles.length) {
target = 'c++';
} else if (maxCount === cfiles.length) {
target = 'c';
} else if (maxCount === rustFiles.length) {
target = 'rust';
} else if (maxCount === golangFiles.length) {
target = 'golang';
} else {
target = 'not implemented';
}
println('Target language: ' + target);
return target;
}
/**
* Helper method to execute commands on the system.
*/
export async function systemSync(cmd: string, args: Array<string | undefined>) {
debugPrintln('Running command');
debugPrintln(cmd);
debugPrintln(args.toString());
debugPrintln('<<<<<<<<<<<<');
// Launch the command
const command = spawn(cmd, args);
// Callbacks for output events, to capture stdout and stderr live.
command.stdout.on('data', (x: {toString: () => string}) => {
println(x.toString());
});
command.stderr.on('data', (x: {toString: () => string}) => {
println(x.toString());
});
// Monitor for child exit.
let hasChildExited = 0;
let childExitCode = 0;
command.on('exit', (code: any, signal: any) => {
// println('child process exited with ' + `code ${code} and signal ${signal}`);
childExitCode = code;
hasChildExited = 1;
});
// Block until the child process has exited.
const snooze = (ms: number) =>
new Promise(resolve => setTimeout(resolve, ms));
let idx = 0;
const maxSeconds = 1800;
debugPrintln('Child exited: ' + hasChildExited);
// I think we can convert the following loop to a Promise wrapping the command
// exeuction. TODO(David).
while (hasChildExited === 0 && idx < maxSeconds) {
idx += 1;
await snooze(1000);
}
// Command execution is done, return appropriately if success/error.
if (childExitCode !== 0) {
println('Command execution errored');
return [false, command.toString()];
}
// println('Succes');
return [true, command.toString()];
}
export async function systemSyncLogIfFailure(
cmd: string,
args: Array<string | undefined>
): Promise<boolean> {
const [res, cmdMsg] = await systemSync(cmd, args);
if (res === false) {
println(cmdMsg);
return false;
}
return true;
}

View File

@ -0,0 +1,11 @@
{
"extends": "./node_modules/gts/tsconfig-google.json",
"compilerOptions": {
"rootDir": ".",
"outDir": "build"
},
"include": [
"src/**/*.ts",
"test/**/*.ts"
]
}