Skip to content

Added embedded functiality#16

Closed
Reboy20000 wants to merge 3 commits intomainfrom
Test
Closed

Added embedded functiality#16
Reboy20000 wants to merge 3 commits intomainfrom
Test

Conversation

@Reboy20000
Copy link
Member

Check the code

const char *in = argv[1];
const char *out = argv[2];

char *src = read_file(in);

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression

This argument to a file access function is derived from [user input (a command-line argument)](1) and then passed to read_file(path), which calls fopen(__filename).

Copilot Autofix

AI 7 days ago

In general, to fix uncontrolled path usage from command-line arguments, you must validate or normalize the user-supplied path before using it in any file access function. For a command-line tool like this builder, a reasonable policy is to only accept non-empty relative paths that do not escape their starting directory (that is, they contain no ".." components) and optionally to disallow ambiguous components such as ".".

In this specific file, the overall structure is already correct: both in and out are validated via is_safe_relative_path before being used with read_file and write_spc. The best minimal fix that strengthens the defense, without changing the public behavior for “normal” use, is to slightly tighten is_safe_relative_path to also reject "." as a path component. This avoids accepting inputs such as ".", "./", or similar, which don't represent a real file and could be confusing or misused. Everything else in main can stay as is because the validation is already applied before file operations.

Concretely:

  • Edit src/build.c, in the definition of is_safe_relative_path.
  • Inside the loop that checks path components, change the condition that currently rejects only ".." to reject both ".." and ".".
  • No new functions or imports are required; we only adjust the logic inside the existing helper.

This keeps the validation localized and clear, and slightly more conservative, making it easier for both humans and tools to see that user-controlled paths are being constrained before use.

Suggested changeset 1
src/build.c

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/build.c b/src/build.c
--- a/src/build.c
+++ b/src/build.c
@@ -780,7 +780,7 @@
         return 0;
     }
 
-    /* Scan components separated by '/' or '\\' and reject any that are exactly "..". */
+    /* Scan components separated by '/' or '\\' and reject any that are exactly "." or "..". */
     const char *p = path;
     while (*p) {
         while (*p == '/' || *p == '\\') {
@@ -794,7 +794,10 @@
             p++;
         }
         size_t len = (size_t)(p - start);
-        if (len == 2 && start[0] == '.' && start[1] == '.') {
+        /* Reject single-dot and double-dot components to avoid ambiguous or
+           traversing path segments like ".", "./", "../", etc. */
+        if ((len == 1 && start[0] == '.') ||
+            (len == 2 && start[0] == '.' && start[1] == '.')) {
             return 0;
         }
     }
EOF
@@ -780,7 +780,7 @@
return 0;
}

/* Scan components separated by '/' or '\\' and reject any that are exactly "..". */
/* Scan components separated by '/' or '\\' and reject any that are exactly "." or "..". */
const char *p = path;
while (*p) {
while (*p == '/' || *p == '\\') {
@@ -794,7 +794,10 @@
p++;
}
size_t len = (size_t)(p - start);
if (len == 2 && start[0] == '.' && start[1] == '.') {
/* Reject single-dot and double-dot components to avoid ambiguous or
traversing path segments like ".", "./", "../", etc. */
if ((len == 1 && start[0] == '.') ||
(len == 2 && start[0] == '.' && start[1] == '.')) {
return 0;
}
}
Copilot is powered by AI and may make mistakes. Always verify output.

if (!write_ast_to_spc(argv[2], root)) {
fprintf(stderr, "Could not write: %s\n", argv[2]);
if (!write_spc(out, root)) {

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression

This argument to a file access function is derived from [user input (a command-line argument)](1) and then passed to write_spc(out_path), which calls fopen(__filename).

Copilot Autofix

AI 7 days ago

In general terms, the fix is to ensure that the user-supplied out path cannot escape an intended safe directory and cannot use traversal constructs or absolute paths. This is typically done by constructing a full path from a known base directory and the user input, resolving it (e.g., with realpath), and checking that the resolved path remains within the base directory before passing it to any file-opening function. Since we must not change functionality more than necessary and can only edit src/build.c, the best approach is to enhance and reuse the existing is_safe_relative_path logic and add a small helper in main to canonicalize and re-check the output path before calling write_spc.

Concretely, we can:

  1. Keep is_safe_relative_path as a first, cheap filter on both in and out.
  2. For the output path, derive an absolute, normalized path with realpath before passing it to write_spc.
    • Because the file might not exist yet, we cannot directly call realpath(out, ...) successfully in all cases. Instead, we can:
      • Resolve the current working directory with getcwd, and
      • Join it with out to build a tentative full path, then call realpath on the containing directory and reconstruct the final path, or
      • More simply, we can rely on is_safe_relative_path to ensure out is purely relative (no ..), and then prepend a chosen safe base directory such as the current working directory at runtime and pass that joined path to write_spc. This ensures that even if write_spc opens paths directly, the actual file is created under this base directory.
  3. Introduce a small helper, for example static char *build_output_path(const char *rel_out), that:
    • Assumes rel_out has already passed is_safe_relative_path.
    • Gets the current working directory via getcwd.
    • Allocates a buffer large enough for cwd + "/" + rel_out.
    • Concatenates them to form an absolute path and returns it (caller frees).
  4. In main, replace the direct write_spc(out, root) call with a call to build_output_path(out) and then pass the resulting safe absolute path into write_spc. If building the path fails, we report an error and exit.

This preserves existing semantics in a typical use case (writing under the current directory) while hardening the path handling and making the data passed into write_spc clearly controlled. It also introduces no new external dependencies beyond standard POSIX APIs already in use (getcwd from <unistd.h>).

Suggested changeset 1
src/build.c

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/build.c b/src/build.c
--- a/src/build.c
+++ b/src/build.c
@@ -802,6 +802,42 @@
     return 1;
 }
 
+/* Build an absolute output path under the current working directory from a
+ * user-supplied relative path that has already passed is_safe_relative_path().
+ * The caller is responsible for free()'ing the returned buffer.
+ */
+static char *build_output_path(const char *rel_out) {
+    char cwd[PATH_MAX];
+    if (getcwd(cwd, sizeof(cwd)) == NULL) {
+        perror("spbuild: getcwd failed");
+        return NULL;
+    }
+
+    size_t cwd_len = strlen(cwd);
+    size_t rel_len = strlen(rel_out);
+
+    /* Allocate space for "cwd" + "/" (if needed) + rel_out + terminating NUL. */
+    size_t need_sep = (cwd_len > 0 && cwd[cwd_len - 1] != '/' && cwd[cwd_len - 1] != '\\') ? 1 : 0;
+    size_t total_len = cwd_len + need_sep + rel_len + 1;
+
+    char *full = (char *)malloc(total_len);
+    if (!full) {
+        fprintf(stderr, "spbuild: out of memory building output path\n");
+        return NULL;
+    }
+
+    memcpy(full, cwd, cwd_len);
+    size_t pos = cwd_len;
+    if (need_sep) {
+        full[pos++] = '/';
+    }
+    memcpy(full + pos, rel_out, rel_len);
+    pos += rel_len;
+    full[pos] = '\0';
+
+    return full;
+}
+
 int main(int argc, char **argv) {
     if (argc != 3) {
         fprintf(stderr, "Usage: %s <input.sp> <output.spc>\n", argv[0]);
@@ -832,14 +868,24 @@
 
     ASTNode *root = parse_program(&tv);
 
-    if (!write_spc(out, root)) {
-        fprintf(stderr, "spbuild: failed to write %s\n", out);
+    char *out_path = build_output_path(out);
+    if (!out_path) {
         free_ast(root);
         tv_free(&tv);
         free(src);
         return 1;
     }
 
+    if (!write_spc(out_path, root)) {
+        fprintf(stderr, "spbuild: failed to write %s\n", out_path);
+        free(out_path);
+        free_ast(root);
+        tv_free(&tv);
+        free(src);
+        return 1;
+    }
+
+    free(out_path);
     free_ast(root);
     tv_free(&tv);
     free(src);
EOF
Copilot is powered by AI and may make mistakes. Always verify output.
Read SPC into memory
========================= */

FILE *f = fopen(path, "rb");

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression

This argument to a file access function is derived from [user input (a command-line argument)](1) and then passed to fopen(__filename).
@Reboy20000
Copy link
Member Author

Something is broken

@Reboy20000 Reboy20000 closed this Feb 6, 2026
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.

1 participant