Skip to content

fix: prevent stack overflow and /tmp race in dlt-convert workspace (#793)#833

Open
aki1770-del wants to merge 1 commit into
COVESA:masterfrom
aki1770-del:fix/dlt-convert-stack-overflow-793
Open

fix: prevent stack overflow and /tmp race in dlt-convert workspace (#793)#833
aki1770-del wants to merge 1 commit into
COVESA:masterfrom
aki1770-del:fix/dlt-convert-stack-overflow-793

Conversation

@aki1770-del
Copy link
Copy Markdown

Summary

Fixes two bugs in dlt-convert.c reported in issue #793:

Bug 1 — Stack overflow via inflated argc

dlt-convert creates a workspace directory at DLT_CONVERT_WS (/tmp/dlt_convert_workspace/) and builds a dynamic argv[] array from its contents. The count n is read from the directory (attacker-controllable via /tmp world-write). The computed argc = optind + (n - 2) can exceed the original argc, causing argv[index] = tmp_filename to write past the original stack-allocated argv bounds — CWE-121 (stack-based buffer overflow).

Fix: record original_argc before workspace processing; cap the computed argc at original_argc.

Bug 2 — /tmp race: empty_dir() never called on EEXIST path

On mkdir(DLT_CONVERT_WS) returning EEXIST, the code calls stat(DLT_CONVERT_WS, &st) but st was zero-initialized and never populated by the preceding mkdir. S_ISDIR(st.st_mode) always evaluates false on a zeroed st_mode, so empty_dir() is never invoked — stale files accumulate and feed the inflated argc count. CWE-377 (insecure temporary file).

Fix: add explicit stat() call on the EEXIST path before checking S_ISDIR; remove the dead S_ISDIR check in the fresh-mkdir branch (newly created directory is always a directory).

Changes

src/console/dlt-convert.c — 1 file, +9/-4 lines

  • Add int original_argc = argc; before workspace processing
  • EEXIST path: call stat(DLT_CONVERT_WS, &st) explicitly, then guard empty_dir() with real S_ISDIR result
  • Fresh-mkdir path: call empty_dir() unconditionally (directory was just created)
  • After argc inflation: if (argc > original_argc) argc = original_argc;

Safety framing

  • CWE-121 (stack-based buffer overflow)
  • CWE-377 (insecure temporary file)
  • CWE-20 (improper input validation)
  • AUTOSAR SWS_DLT_01000, ISO 26262-6 §9.4.4d

Closes #793

Signed-off-by: Akihiko Komada aki1770@gmail.com

…OVESA#793)

Two related bugs in the -t (compressed file) path of dlt-convert:

1. Workspace not cleared when directory already exists (CWE-377):
   mkdir() failing with EEXIST did not call empty_dir(). The struct stat
   was zero-initialised but never populated via stat(), so S_ISDIR() was
   always false and empty_dir() was never reached. An attacker who
   pre-creates /tmp/dlt_convert_workspace/ and fills it with files
   controls the workspace contents before dlt-convert runs.

   Fix: call stat() before S_ISDIR() in the EEXIST path so the check is
   meaningful; always call empty_dir() in the fresh-mkdir path (removing
   the now-dead fprintf branch).

2. argc inflated past original argv bounds (CWE-121, stack overflow):
   argc = optind + (n - 2) can exceed the original argc when n is large
   (e.g. attacker planted many files). The subsequent loop assigns
   argv[index] = tmp_filename for indices beyond the original argv
   allocation, writing past the stack-allocated argument vector.

   Fix: save original_argc before the tflag block and cap the inflated
   argc at that value.

CWE-377 (Insecure Temporary File), CWE-121 (Stack-based Buffer Overflow),
CWE-20 (Improper Input Validation).

Signed-off-by: Akihiko Komada <aki1770@gmail.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses two security/robustness bugs in dlt-convert’s -t (tar workspace) path by hardening workspace directory handling under /tmp and preventing argc from being inflated beyond the original argv bounds.

Changes:

  • Track original_argc and clamp the computed argc after scanning the workspace directory.
  • Fix the EEXIST mkdir path by calling stat() before checking S_ISDIR and cleaning the workspace.
  • Always call empty_dir() on the freshly created workspace directory.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/console/dlt-convert.c
Comment on lines +362 to +365
/* Directory exists — clear any pre-existing contents (including
* attacker-planted files) before we use it as a workspace. */
if (stat(DLT_CONVERT_WS, &st) == 0 && S_ISDIR(st.st_mode))
empty_dir(DLT_CONVERT_WS);
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new EEXIST handling uses stat() and then calls empty_dir() when S_ISDIR is true. Because stat() follows symlinks, an attacker can pre-create /tmp/dlt_convert_workspace as a symlink to another directory; running dlt-convert could then delete files outside of /tmp. Use lstat() and explicitly reject symlinks (and ideally verify ownership/permissions) before cleaning or using the workspace path.

Suggested change
/* Directory existsclear any pre-existing contents (including
* attacker-planted files) before we use it as a workspace. */
if (stat(DLT_CONVERT_WS, &st) == 0 && S_ISDIR(st.st_mode))
empty_dir(DLT_CONVERT_WS);
/* Directory existsensure it is a safe, non-symlink directory
* owned by the current user before clearing its contents. */
if (lstat(DLT_CONVERT_WS, &st) != 0) {
fprintf(stderr,"ERROR: Cannot stat existing temp dir %s!\n", DLT_CONVERT_WS);
if (ovalue) {
close(ohandle);
ohandle = -1;
}
return -1;
}
if (S_ISLNK(st.st_mode) || !S_ISDIR(st.st_mode)) {
fprintf(stderr,"ERROR: Temp path %s is not a directory or is a symlink!\n", DLT_CONVERT_WS);
if (ovalue) {
close(ohandle);
ohandle = -1;
}
return -1;
}
if (st.st_uid != geteuid() || (st.st_mode & 0777) != 0700) {
fprintf(stderr,"ERROR: Temp dir %s has unsafe owner or permissions!\n", DLT_CONVERT_WS);
if (ovalue) {
close(ohandle);
ohandle = -1;
}
return -1;
}
empty_dir(DLT_CONVERT_WS);

Copilot uses AI. Check for mistakes.
Comment thread src/console/dlt-convert.c
Comment on lines +362 to +365
/* Directory exists — clear any pre-existing contents (including
* attacker-planted files) before we use it as a workspace. */
if (stat(DLT_CONVERT_WS, &st) == 0 && S_ISDIR(st.st_mode))
empty_dir(DLT_CONVERT_WS);
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If mkdir() returns EEXIST and stat() fails or the path is not a directory, the code currently proceeds and later runs tar/cp/scandir against DLT_CONVERT_WS. This should fail fast with a clear error (and close ohandle) rather than continuing with an invalid workspace path.

Suggested change
/* Directory existsclear any pre-existing contents (including
* attacker-planted files) before we use it as a workspace. */
if (stat(DLT_CONVERT_WS, &st) == 0 && S_ISDIR(st.st_mode))
empty_dir(DLT_CONVERT_WS);
/* Directory existsverify it is a directory and usable, then
* clear any pre-existing contents (including attacker-planted
* files) before we use it as a workspace. */
if (stat(DLT_CONVERT_WS, &st) != 0) {
fprintf(stderr,"ERROR: Cannot stat existing temp path %s!\n", DLT_CONVERT_WS);
if (ovalue) {
close(ohandle);
ohandle = -1;
}
return -1;
}
if (!S_ISDIR(st.st_mode)) {
fprintf(stderr,"ERROR: Temp path %s exists but is not a directory!\n", DLT_CONVERT_WS);
if (ovalue) {
close(ohandle);
ohandle = -1;
}
return -1;
}
empty_dir(DLT_CONVERT_WS);

Copilot uses AI. Check for mistakes.
Comment thread src/console/dlt-convert.c
Comment on lines 401 to +405
/* do not include ./ and ../ in the files */
argc = optind + (n - 2);
/* Guard: never exceed the original argv bounds. */
if (argc > original_argc)
argc = original_argc;
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clamping argc to original_argc prevents the overflow but also changes behavior: archives that expand into more than (original_argc - optind) files will have the extra extracted files silently ignored. Instead of mutating/clamping argc, consider iterating over the scandir() results directly or allocating a new argv-like array on the heap sized to the discovered file count.

Suggested change
/* do not include ./ and ../ in the files */
argc = optind + (n - 2);
/* Guard: never exceed the original argv bounds. */
if (argc > original_argc)
argc = original_argc;
/* do not include ./ and ../ in the files; n includes "." and ".." */
if (n > 2) {
int new_argc = optind + (n - 2);
if (new_argc > original_argc) {
char **new_argv;
int i;
new_argv = (char **)malloc((size_t)new_argc * sizeof(char *));
if (new_argv == NULL) {
fprintf(stderr,
"ERROR: Out of memory while expanding argument list for %s!\n",
DLT_CONVERT_WS);
if (ovalue) {
close(ohandle);
ohandle = -1;
}
return -1;
}
/* Preserve existing arguments. */
for (i = 0; i < original_argc; i++) {
new_argv[i] = argv[i];
}
/* Initialize any newly added slots. They will be populated as needed. */
for (i = original_argc; i < new_argc; i++) {
new_argv[i] = NULL;
}
argv = new_argv;
argc = new_argc;
} else {
argc = new_argc;
}
}

Copilot uses AI. Check for mistakes.
Comment thread src/console/dlt-convert.c
Comment on lines 351 to +405
if (tflag) {
/* Prepare the temp dir to untar compressed files */
if (mkdir(DLT_CONVERT_WS, 0700) != 0) {
if (errno != EEXIST) {
fprintf(stderr,"ERROR: Cannot create temp dir %s!\n", DLT_CONVERT_WS);
if (ovalue) {
close(ohandle);
ohandle = -1;
}
return -1;
}
/* Directory exists — clear any pre-existing contents (including
* attacker-planted files) before we use it as a workspace. */
if (stat(DLT_CONVERT_WS, &st) == 0 && S_ISDIR(st.st_mode))
empty_dir(DLT_CONVERT_WS);
}
else {
if (S_ISDIR(st.st_mode))
empty_dir(DLT_CONVERT_WS);
else
fprintf(stderr, "ERROR: %s is not a directory", DLT_CONVERT_WS);
empty_dir(DLT_CONVERT_WS);
}

for (index = optind; index < argc; index++) {
/* Check extension of input file
* If it is a compressed file, uncompress it
*/
const char *file_ext = get_filename_ext(argv[index]);
if (file_ext && strcmp(file_ext, DLT_EXTENSION) != 0) {
syserr = dlt_execute_command(NULL, "tar", "xf", argv[index], "-C", DLT_CONVERT_WS, NULL);
if (syserr != 0)
fprintf(stderr, "ERROR: Failed to uncompress %s to %s with error [%d]\n",
argv[index], DLT_CONVERT_WS, WIFEXITED(syserr));
}
else {
syserr = dlt_execute_command(NULL, "cp", argv[index], DLT_CONVERT_WS, NULL);
if (syserr != 0)
fprintf(stderr, "ERROR: Failed to copy %s to %s with error [%d]\n",
argv[index], DLT_CONVERT_WS, WIFEXITED(syserr));
}

}

n = scandir(DLT_CONVERT_WS, &files, NULL, alphasort);
if (n == -1) {
fprintf(stderr,"ERROR: Cannot scan temp dir %s!\n", DLT_CONVERT_WS);
if (ovalue) {
close(ohandle);
ohandle = -1;
}
return -1;
}

/* do not include ./ and ../ in the files */
argc = optind + (n - 2);
/* Guard: never exceed the original argv bounds. */
if (argc > original_argc)
argc = original_argc;
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are existing integration tests that run dlt-convert, but none appear to cover the -t (tar workspace) path. Adding a regression test for an archive with multiple extracted .dlt files (and for an attacker-controlled pre-existing workspace path) would help prevent reintroducing the overflow/tmp issues.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] stack-overflow in dlt-convert.c

2 participants