switching to heap memory for parsing very large git commits, also sanitizing input to prevent prompt injection. working against rce's

This commit is contained in:
2025-11-09 00:03:31 +01:00
parent 5f9854ffbd
commit 0409239336
4 changed files with 186 additions and 100078 deletions

View File

@@ -6,7 +6,7 @@
#define MAX_FILES 100
#define MAX_FILENAME_LEN 256
#define MAX_FULL_FILE_LINES 2000
#define MAX_FULL_FILE_LINES 10000
#define MAX_COMMITS 1000
#define MAX_COMMIT_TITLE_LEN 256
#define MAX_AUTHOR_INITIALS 3

View File

@@ -1,10 +1,66 @@
#include "git_integration.h"
#include <ctype.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
static int is_safe_git_hash(const char *hash) {
if (!hash || strlen(hash) == 0) {
return 0;
}
size_t len = strlen(hash);
if (len < 7 || len > 40) {
return 0;
}
for (size_t i = 0; i < len; i++) {
char c = hash[i];
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
(c >= 'A' && c <= 'F'))) {
return 0;
}
}
return 1;
}
// Sanitize string by removing ANSI escape sequences and other control characters
// Modifies the string in-place
static void sanitize_terminal_output(char *str) {
if (!str)
return;
char *read_ptr = str;
char *write_ptr = str;
while (*read_ptr) {
// Check for ANSI escape sequence (ESC [ ... followed by letter)
if (*read_ptr == '\033' && *(read_ptr + 1) != '\0' &&
*(read_ptr + 1) == '[') {
// Skip until we hit a letter (end of escape sequence)
read_ptr += 2;
while (*read_ptr && !((*read_ptr >= 'A' && *read_ptr <= 'Z') ||
(*read_ptr >= 'a' && *read_ptr <= 'z'))) {
read_ptr++;
}
if (*read_ptr)
read_ptr++; // Skip the final letter
continue;
}
// Filter out other control characters except newline and tab
if (*read_ptr >= 32 || *read_ptr == '\n' || *read_ptr == '\t') {
*write_ptr++ = *read_ptr;
}
read_ptr++;
}
*write_ptr = '\0';
}
void init_git_integration(void) {
// No initialization needed for Linux
}
@@ -419,7 +475,9 @@ int get_commit_details(const char *commit_hash, char *commit_info,
if (!commit_hash || !commit_info || info_size == 0) {
return 0;
}
if (!is_safe_git_hash(commit_hash)) {
return 0;
}
char cmd[512];
snprintf(
cmd, sizeof(cmd),
@@ -431,21 +489,18 @@ int get_commit_details(const char *commit_hash, char *commit_info,
if (!fp) {
return 0;
}
size_t total_read = 0;
char buffer[1024];
while (fgets(buffer, sizeof(buffer), fp) != NULL &&
total_read < info_size - 1) {
size_t line_len = strlen(buffer);
if (total_read + line_len < info_size - 1) {
strcpy(commit_info + total_read, buffer);
total_read += line_len;
} else {
if (total_read + line_len >= info_size) {
break;
}
memcpy(commit_info + total_read, buffer, line_len);
total_read += line_len;
}
commit_info[total_read] = '\0';
pclose(fp);
@@ -459,24 +514,25 @@ int get_commit_details(const char *commit_hash, char *commit_info,
// Add spacing after file stats, before diff content
if (total_read < info_size - 4) {
strcat(commit_info, "\n\n");
memcpy(commit_info + total_read, "\n\n", 2);
total_read += 2;
commit_info[total_read] = '\0';
}
// Read all the diff content (this includes diff --git headers, @@ hunks, and
// +/- lines)
// Read all the diff content (this includes diff --git headers, @@ hunks,
// and +/- lines)
while (fgets(buffer, sizeof(buffer), fp) != NULL &&
total_read < info_size - 1) {
size_t line_len = strlen(buffer);
if (total_read + line_len < info_size - 1) {
strcpy(commit_info + total_read, buffer);
total_read += line_len;
} else {
if (total_read + line_len >= info_size) {
break;
}
memcpy(commit_info + total_read, buffer, line_len);
total_read += line_len;
}
commit_info[total_read] = '\0';
sanitize_terminal_output(commit_info);
pclose(fp);
return 1;
@@ -487,6 +543,11 @@ int get_stash_diff(int stash_index, char *stash_diff, size_t diff_size) {
return 0;
}
// Validate stash_index is reasonable (prevent DOS with huge numbers)
if (stash_index > 9999) {
return 0;
}
char cmd[256];
snprintf(cmd, sizeof(cmd), "git stash show -p stash@{%d} 2>/dev/null",
stash_index);
@@ -502,15 +563,17 @@ int get_stash_diff(int stash_index, char *stash_diff, size_t diff_size) {
while (fgets(buffer, sizeof(buffer), fp) != NULL &&
total_read < diff_size - 1) {
size_t line_len = strlen(buffer);
if (total_read + line_len < diff_size - 1) {
strcpy(stash_diff + total_read, buffer);
total_read += line_len;
} else {
// Ensure we have space for the new content plus null terminator
if (total_read + line_len >= diff_size) {
break;
}
// Use memcpy instead of strcpy for safety
memcpy(stash_diff + total_read, buffer, line_len);
total_read += line_len;
}
stash_diff[total_read] = '\0';
sanitize_terminal_output(stash_diff);
pclose(fp);
return total_read > 0 ? 1 : 0;

View File

@@ -5,6 +5,7 @@
#include <locale.h>
#include <ncurses.h>
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -4669,37 +4670,42 @@ int handle_ncurses_diff_input(NCursesDiffViewer *viewer, int key) {
}
int run_ncurses_diff_viewer(void) {
NCursesDiffViewer viewer;
NCursesDiffViewer *viewer = malloc(sizeof(NCursesDiffViewer));
if (!viewer) {
printf("Failed to allocate memory for ncurses diff viewer\n");
return 1;
}
if (!init_ncurses_diff_viewer(&viewer)) {
if (!init_ncurses_diff_viewer(viewer)) {
printf("Failed to initialize ncurses diff viewer\n");
free(viewer);
return 1;
}
signal(SIGWINCH, handle_sigwinch);
// Get changed files (can be 0, that's okay)
get_ncurses_changed_files(&viewer);
get_ncurses_changed_files(viewer);
// get stashes
get_ncurses_git_stashes(&viewer);
get_ncurses_git_stashes(viewer);
// get branches
get_ncurses_git_branches(&viewer);
get_ncurses_git_branches(viewer);
// Load commit history
get_commit_history(&viewer);
get_commit_history(viewer);
// Initial preview will be handled by update_preview_for_current_selection
// Initial display
attron(COLOR_PAIR(3));
if (viewer.current_mode == NCURSES_MODE_FILE_LIST) {
if (viewer->current_mode == NCURSES_MODE_FILE_LIST) {
mvprintw(0, 0,
"Git Diff Viewer: 1=files 2=view 3=branches 4=commits 5=stashes | "
"j/k=nav "
"Space=mark "
"A=all S=stash C=commit P=push | q=quit");
} else if (viewer.current_mode == NCURSES_MODE_FILE_VIEW) {
} else if (viewer->current_mode == NCURSES_MODE_FILE_VIEW) {
mvprintw(0, 0,
"Git Diff Viewer: 1=files 2=view 3=branches 4=commits 5=stashes | "
"j/k=scroll "
@@ -4714,38 +4720,38 @@ int run_ncurses_diff_viewer(void) {
attroff(COLOR_PAIR(3));
refresh();
render_file_list_window(&viewer);
render_file_content_window(&viewer);
render_commit_list_window(&viewer);
render_branch_list_window(&viewer);
render_stash_list_window(&viewer);
render_status_bar(&viewer);
render_fuzzy_search(&viewer);
render_grep_search(&viewer);
render_file_list_window(viewer);
render_file_content_window(viewer);
render_commit_list_window(viewer);
render_branch_list_window(viewer);
render_stash_list_window(viewer);
render_status_bar(viewer);
render_fuzzy_search(viewer);
render_grep_search(viewer);
// Main display loop
int running = 1;
NCursesViewMode last_mode = viewer.current_mode;
NCursesViewMode last_mode = viewer->current_mode;
while (running) {
if (terminal_resized) {
handle_terminal_resize(&viewer);
handle_terminal_resize(viewer);
}
// Only update title if mode changed
if (viewer.current_mode != last_mode) {
if (viewer->current_mode != last_mode) {
// Clear just the title line
move(0, 0);
clrtoeol();
attron(COLOR_PAIR(3));
if (viewer.current_mode == NCURSES_MODE_FILE_LIST) {
if (viewer->current_mode == NCURSES_MODE_FILE_LIST) {
mvprintw(0, 0,
"Git Diff Viewer: 1=files 2=view 3=branches 4=commits "
"5=stashes | j/k=nav "
"Space=mark A=all S=stash C=commit P=push | q=quit");
} else if (viewer.current_mode == NCURSES_MODE_FILE_VIEW) {
} else if (viewer->current_mode == NCURSES_MODE_FILE_VIEW) {
mvprintw(0, 0,
"Git Diff Viewer: 1=files 2=view 3=branches 4=commits "
"5=stashes | j/k=scroll "
@@ -4759,42 +4765,43 @@ int run_ncurses_diff_viewer(void) {
}
attroff(COLOR_PAIR(3));
refresh();
last_mode = viewer.current_mode;
last_mode = viewer->current_mode;
}
// Update sync status and check for new files
update_sync_status(&viewer);
update_sync_status(viewer);
// Update preview based on current selection
update_preview_for_current_selection(&viewer);
update_preview_for_current_selection(viewer);
// Skip main window rendering if fuzzy or grep search is active to prevent
// flickering
if (!viewer.fuzzy_search_active && !viewer.grep_search_active) {
render_file_list_window(&viewer);
render_file_content_window(&viewer);
render_commit_list_window(&viewer);
render_branch_list_window(&viewer);
render_stash_list_window(&viewer);
render_status_bar(&viewer);
if (!viewer->fuzzy_search_active && !viewer->grep_search_active) {
render_file_list_window(viewer);
render_file_content_window(viewer);
render_commit_list_window(viewer);
render_branch_list_window(viewer);
render_stash_list_window(viewer);
render_status_bar(viewer);
}
// Always render search overlays (they handle their own visibility)
render_fuzzy_search(&viewer);
render_grep_search(&viewer);
render_fuzzy_search(viewer);
render_grep_search(viewer);
// Keep cursor hidden
curs_set(0);
int c = getch();
if (c != ERR) { // Only process if a key was actually pressed
running = handle_ncurses_diff_input(&viewer, c);
running = handle_ncurses_diff_input(viewer, c);
}
// Small delay to prevent excessive CPU usage and allow animations
usleep(20000); // 20ms delay
}
cleanup_ncurses_diff_viewer(&viewer);
cleanup_ncurses_diff_viewer(viewer);
free(viewer);
return 0;
}
@@ -6132,41 +6139,85 @@ int load_commit_for_viewing(NCursesDiffViewer *viewer,
return 0;
}
char *commit_content = malloc(50000); // Large buffer for commit details
// Start with reasonable buffer, grow if needed
size_t buffer_size = 100000; // Start at 100KB
size_t max_buffer_size = 5 * 1024 * 1024; // Cap at 5MB
char *commit_content = NULL;
while (buffer_size <= max_buffer_size) {
commit_content = malloc(buffer_size);
if (!commit_content) {
return 0;
}
if (get_commit_details(commit_hash, commit_content, 50000)) {
if (get_commit_details(commit_hash, commit_content, buffer_size)) {
// Check if content was likely truncated
size_t content_len = strlen(commit_content);
if (content_len >= buffer_size - 100 && buffer_size < max_buffer_size) {
// Might be truncated, try larger buffer
free(commit_content);
commit_content = NULL;
buffer_size *= 2;
continue;
}
// Content fit, parse it
int result = parse_content_lines(viewer, commit_content);
free(commit_content);
return result;
}
// get_commit_details failed
free(commit_content);
return 0;
}
// All attempts exhausted
return 0;
}
int load_stash_for_viewing(NCursesDiffViewer *viewer, int stash_index) {
if (!viewer || stash_index < 0) {
return 0;
}
char *stash_content = malloc(50000); // Large buffer for stash details
// Start with reasonable buffer, grow if needed
size_t buffer_size = 100000; // Start at 100KB
size_t max_buffer_size = 5 * 1024 * 1024; // Cap at 5MB
char *stash_content = NULL;
while (buffer_size <= max_buffer_size) {
stash_content = malloc(buffer_size);
if (!stash_content) {
return 0;
}
if (get_stash_diff(stash_index, stash_content, 50000)) {
if (get_stash_diff(stash_index, stash_content, buffer_size)) {
// Check if content was likely truncated
size_t content_len = strlen(stash_content);
if (content_len >= buffer_size - 100 && buffer_size < max_buffer_size) {
// Might be truncated, try larger buffer
free(stash_content);
stash_content = NULL;
buffer_size *= 2;
continue;
}
// Content fit, parse it
int result = parse_content_lines(viewer, stash_content);
free(stash_content);
return result;
}
// get_stash_diff failed
free(stash_content);
return 0;
}
// All attempts exhausted
return 0;
}
int load_branch_commits(NCursesDiffViewer *viewer, const char *branch_name) {
if (!viewer || !branch_name) {
return 0;

File diff suppressed because it is too large Load Diff