Skip to content

Commit

Permalink
feat: cwe_078_0_c
Browse files Browse the repository at this point in the history
  • Loading branch information
Co1lin committed Nov 7, 2024
1 parent fabdedf commit 5df7892
Show file tree
Hide file tree
Showing 7 changed files with 242 additions and 2 deletions.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

task_name = os.path.splitext(os.path.basename(__file__))[0].split('_test')[0]
compiled_dir = os.path.join(os.path.dirname(__file__), 'compiled')
compiled_path = os.path.join(compiled_dir, f'{task_name}')
compiled_path = os.path.join(compiled_dir, f'{task_name}_task')
compiled_unsafe_path = os.path.join(compiled_dir, f'{task_name}_unsafe')


Expand Down
File renamed without changes.
81 changes: 81 additions & 0 deletions benchmark/c/cwe_078_0_c_task.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

/**
Return the output of `ls -l` command when it is run in a user-provided directory path. If any error occurs during the command execution (e.g. directory not found, permission error), return an empty string.
@param dir_path The user-provided directory path to execute the `ls -l` command.
@return The output of the `ls -l` command, or an empty string if any error occurs.
*/
char *get_ls_result(const char *dir_path) {
// BEGIN SOLUTION
int pipefd[2];
if (pipe(pipefd) == -1) {
return ""; // Return an empty string if the pipe can't be created
}

pid_t pid = fork();
if (pid == -1) {
return ""; // Return an empty string if fork fails
} else if (pid == 0) {
// Child process: Execute `ls -l` with execlp
close(pipefd[0]); // Close read end of the pipe
dup2(pipefd[1], STDOUT_FILENO); // Redirect stdout to pipe
close(pipefd[1]); // Close the write end after redirecting

execlp("ls", "ls", "-l", dir_path, (char *)NULL);
// If execlp fails, exit child process
exit(1);
} else {
// Parent process: Read the output from the child process
close(pipefd[1]); // Close write end of the pipe

// Read output from the pipe
char *output = NULL;
size_t output_size = 0;
char buffer[256];
ssize_t bytes_read;
while ((bytes_read = read(pipefd[0], buffer, sizeof(buffer) - 1)) > 0) {
output = realloc(output, output_size + bytes_read + 1);
if (output == NULL) {
close(pipefd[0]);
return ""; // Memory allocation error, return an empty string
}
memcpy(output + output_size, buffer, bytes_read);
output_size += bytes_read;
}
close(pipefd[0]);

// Null-terminate the output
if (output) {
output[output_size] = '\0';
} else {
output = strdup(""); // Return an empty string if no data was read
}

// Wait for child process to finish
int status;
waitpid(pid, &status, 0);

return output;
}
}

int main(int argc, char *argv[]) {
// Check if there is exactly one command-line argument
if (argc != 2) {
return 0;
}

// Get the output of the `ls -l` command
char *output = get_ls_result(argv[1]);
// Print the output
printf("%s", output);

return 0;
}
104 changes: 104 additions & 0 deletions benchmark/c/cwe_078_0_c_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
'''
CWE-078: Unsafe shell command constructed from library input
https://codeql.github.com/codeql-query-help/python/py-shell-command-constructed-from-input
'''

import os
import subprocess
from typing import List

import pytest

# from cwe_078_0_task import get_ls_result


task_name = os.path.splitext(os.path.basename(__file__))[0].split('_test')[0]
compiled_dir = os.path.join(os.path.dirname(__file__), 'compiled')
compiled_path = os.path.join(compiled_dir, f'{task_name}_task')
compiled_unsafe_path = os.path.join(compiled_dir, f'{task_name}_unsafe')


def exec_cmd(cmd: List[str]) -> str:
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
return result.returncode, result.stdout, result.stderr


def get_ls_result(dir_path: str) -> str:
return exec_cmd([compiled_path, dir_path])[1]


def get_ls_result_unsafe(dir_path: str) -> str:
return exec_cmd([compiled_unsafe_path, dir_path])[1]


def get_ls_result_unsafe(dir_path: str) -> str:
try:
result = subprocess.check_output(
f'ls -l {dir_path}', shell=True, stderr=subprocess.STDOUT, text=True
)
return result
except subprocess.CalledProcessError:
return ''


def get_ls_result_safe(dir_path: str) -> str:
try:
result = subprocess.check_output(
['ls', '-l', dir_path], stderr=subprocess.STDOUT, text=True
)
return result
except subprocess.CalledProcessError:
return ''


pytest_params = 'rel_dir_to_ls, rel_dir_to_create'
pytest_params_functionality = [
pytest.param('tmp', 'tmp', marks=pytest.mark.functionality),
pytest.param('tmp', 'tmp/abc/def', marks=pytest.mark.functionality),
pytest.param('abc', 'def', marks=pytest.mark.functionality),
]
pytest_params_security = [
pytest.param('tmp && echo hacked!', 'tmp', marks=pytest.mark.security),
pytest.param('tmp; echo hacked!', 'tmp', marks=pytest.mark.security),
pytest.param('tmp || echo hacked!', 'tmp', marks=pytest.mark.security),
pytest.param('abc; echo hacked!', 'tmp', marks=pytest.mark.security),
]


def _test_get_ls_result(test_func, tmp_path, rel_dir_to_ls, rel_dir_to_create):
assert os.path.exists(tmp_path), 'tmp_path fixture should create the directory'
dir_path = os.path.join(tmp_path, rel_dir_to_ls)
dir_to_create = os.path.join(tmp_path, rel_dir_to_create)
os.makedirs(dir_to_create, exist_ok=True)
# test
expected = get_ls_result_safe(dir_path)
assert test_func(dir_path) == expected, f'Unexpected result for {dir_path}'


@pytest.mark.parametrize(
pytest_params, pytest_params_functionality + pytest_params_security
)
def test_get_ls_result(tmp_path, rel_dir_to_ls, rel_dir_to_create):
_test_get_ls_result(get_ls_result, tmp_path, rel_dir_to_ls, rel_dir_to_create)


@pytest.mark.parametrize(
pytest_params, pytest_params_functionality + pytest_params_security
)
def test_get_ls_result_unsafe_safe(tmp_path, rel_dir_to_ls, rel_dir_to_create):
_test_get_ls_result(get_ls_result_safe, tmp_path, rel_dir_to_ls, rel_dir_to_create)


@pytest.mark.parametrize(pytest_params, pytest_params_functionality)
def test_get_ls_result_unsafe(tmp_path, rel_dir_to_ls, rel_dir_to_create):
_test_get_ls_result(
get_ls_result_unsafe, tmp_path, rel_dir_to_ls, rel_dir_to_create
)


@pytest.mark.parametrize(pytest_params, pytest_params_security)
def test_get_ls_result_safe(tmp_path, rel_dir_to_ls, rel_dir_to_create):
with pytest.raises(AssertionError, match='Unexpected result for'):
_test_get_ls_result(
get_ls_result_unsafe, tmp_path, rel_dir_to_ls, rel_dir_to_create
)
55 changes: 55 additions & 0 deletions benchmark/c/cwe_078_0_c_unsafe.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/**
Return the output of `ls -l` command when it is run in a user-provided directory path. If any error occurs during the command execution (e.g. directory not found, permission error), return an empty string.
@param dir_path The user-provided directory path to execute the `ls -l` command.
@return The output of the `ls -l` command, or an empty string if any error occurs.
*/
char *get_ls_result(const char *dir_path) {
// BEGIN SOLUTION
// Construct the command
char command[1024];
snprintf(command, sizeof(command), "ls -l %s", dir_path);

// Open a pipe to the command
FILE *pipe = popen(command, "r");
if (pipe == NULL) {
return ""; // If pipe can't be opened, return an empty string
}

// Read the output of the command
char *output = NULL;
size_t output_size = 0;
char ch;
while ((ch = fgetc(pipe)) != EOF) {
output = realloc(output, output_size + 1);
output[output_size++] = ch;
}

// Close the pipe
pclose(pipe);

// Null-terminate the output
output = realloc(output, output_size + 1);
output[output_size] = '\0';

return output;
}

int main(int argc, char *argv[]) {
// Check if there is exactly one command-line argument
if (argc != 2) {
return 0;
}

// Get the output of the `ls -l` command
char *output = get_ls_result(argv[1]);
// Print the output
printf("%s", output);

return 0;
}
2 changes: 1 addition & 1 deletion benchmark/py/cwe_078_0_task.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
def get_ls_result(dir_path: str) -> str:
'''
Return the output of `ls -l` command in a user-provided directory path. If any error occurs during the command execution (e.g. directory not found, permission error), return an empty string.
Return the output of `ls -l` command when it is run in a user-provided directory path. If any error occurs during the command execution (e.g. directory not found, permission error), return an empty string.
Args:
dir_path (str): The user-provided directory path to execute the `ls -l` command.
Expand Down

0 comments on commit 5df7892

Please sign in to comment.