]> git.ipfire.org Git - pakfire.git/commitdiff
linter: Move script interpreter check
authorMichael Tremer <michael.tremer@ipfire.org>
Sat, 26 Oct 2024 17:26:27 +0000 (17:26 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Sat, 26 Oct 2024 17:26:27 +0000 (17:26 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
src/libpakfire/file.c
src/libpakfire/linter-file.c

index 956a1f2be76b0298d7f1ce74600ac4b4d9bf9987..d28a0de874ccf07d5d192dfc030e56052591dfea 100644 (file)
@@ -1789,314 +1789,6 @@ PAKFIRE_EXPORT int pakfire_file_matches(struct pakfire_file* file, const char* p
        return pakfire_path_match(pattern, path);
 }
 
-static int pakfire_file_get_script_interpreter(struct pakfire_file* file, char** interpreter) {
-       FILE* f = NULL;
-       char shebang[1024];
-       char* interp = NULL;
-       char* p = NULL;
-       int r;
-
-       // Check inputs
-       if (!interpreter) {
-               errno = EINVAL;
-               return 1;
-       }
-
-       const mode_t mode = pakfire_file_get_mode(file);
-
-       // Only run for regular files
-       if (!S_ISREG(mode))
-               return 0;
-
-       // Only run for executable files
-       if (!pakfire_file_is_executable(file))
-               return 0;
-
-       const size_t size = pakfire_file_get_size(file);
-
-       // Nothing to do if the file is empty
-       if (!size)
-               return 0;
-
-       // Open the file
-       f = pakfire_file_open(file);
-       if (!f) {
-               r = 1;
-               goto ERROR;
-       }
-
-       // Fill the buffer with the first couple of bytes of the file
-       ssize_t bytes_read = fread(shebang, 1, sizeof(shebang), f);
-
-       // Handle any reading errors
-       if (bytes_read < 0) {
-               ERROR(file->ctx, "Could not read from file %s: %m\n",
-                       pakfire_file_get_path(file));
-               r = 1;
-               goto ERROR;
-       }
-
-       // We did not read enough data
-       if (bytes_read < 2) {
-               r = 1;
-               goto ERROR;
-       }
-
-       if (strncmp("#!", shebang, 2) == 0) {
-               DEBUG(file->ctx, "%s is a script\n", pakfire_file_get_path(file));
-
-               // Find the end of the first line (to be able to perform string operations)
-               p = memchr(shebang, '\n', sizeof(shebang));
-               if (!p) {
-                       ERROR(file->ctx, "%s: First line seems to be too long\n",
-                               pakfire_file_get_path(file));
-                       errno = ENOBUFS;
-                       r = 1;
-                       goto ERROR;
-               }
-
-               // Terminate the string
-               *p = '\0';
-
-               // Find the beginning of the interpreter
-               interp = shebang + 2;
-
-               // Consume any space between #! and the path
-               while (*interp && isspace(*interp))
-                       interp++;
-
-               p = interp;
-
-               // Find the end of the command (cuts off any options)
-               while (*p) {
-                       if (isspace(*p)) {
-                               *p = '\0';
-                               break;
-                       }
-
-                       p++;
-               }
-
-               // Copy the interpreter to the heap
-               *interpreter = strdup(interp);
-       }
-
-       // Success
-       r = 0;
-
-ERROR:
-       if (f)
-               fclose(f);
-
-       return r;
-}
-
-static int pakfire_file_fix_interpreter(struct pakfire_file* file, const char* interpreter) {
-       FILE* f = NULL;
-       int r;
-
-       char* buffer = NULL;
-       size_t l = 0;
-       char* line = NULL;
-       size_t length = 0;
-       char* p = NULL;
-
-       char command[PATH_MAX];
-       const char* args = NULL;
-
-       const char* path = pakfire_file_get_path(file);
-
-       DEBUG(file->ctx, "%s: Fixing interpreter %s\n", path, interpreter);
-
-       // Open the file
-       f = pakfire_file_open(file);
-       if (!f) {
-               ERROR(file->ctx, "Could not open %s: %m\n", path);
-               r = -errno;
-               goto ERROR;
-       }
-
-       for (unsigned int lineno = 0;; lineno++) {
-               r = getline(&line, &length, f);
-               if (r < 0) {
-                       // We finished reading when we reach the end
-                       if (feof(f))
-                               break;
-
-                       ERROR(file->ctx, "Could not read line from %s: %m\n", path);
-                       goto ERROR;
-               }
-
-               switch (lineno) {
-                       case 0:
-                               // Check if the first line starts with #!
-                               if (!pakfire_string_startswith(line, "#!")) {
-                                       r = -EINVAL;
-                                       goto ERROR;
-                               }
-
-                               // Remove the trailing newline
-                               pakfire_string_rstrip(line);
-
-                               // Start at the beginning of the line (after shebang)
-                               p = line + 2;
-
-                               // Consume any whitespace
-                               while (*p && isspace(*p))
-                                       p++;
-
-                               // Consume the old interpreter
-                               while (*p && !isspace(*p))
-                                       p++;
-
-                               // Consume any whitespace
-                               while (*p && isspace(*p))
-                                       p++;
-
-                               // The actual interpreter begins here
-                               interpreter = p;
-
-                               // Find the end of the interpreter
-                               while (*p && !isspace(*p))
-                                       p++;
-
-                               // Terminate the interpreter
-                               *p++ = '\0';
-
-                               // Any arguments begin here (if we didn't consume the entire string, yet)
-                               if (*p)
-                                       args = p;
-
-                               DEBUG(file->ctx, "%s: Found command %s (%s)\n", path, interpreter, args);
-
-                               // Find the real path
-                               r = pakfire_which(file->pakfire, command, interpreter);
-                               if (r) {
-                                       ERROR(file->ctx, "%s: Could not resolve %s: %m\n", path, interpreter);
-                                       goto ERROR;
-                               }
-
-                               // If we could not resolve the command, this file has an invalid interpreter
-                               if (!*command) {
-                                       ERROR(file->ctx, "%s: Could not find path for command %s\n", path, interpreter);
-
-                                       file->issues |= PAKFIRE_FILE_INVALID_INTERPRETER;
-                                       r = 0;
-                                       goto ERROR;
-                               }
-
-                               // Write the new first line to the buffer
-                               r = asprintf(&buffer, "#!%s", command);
-                               if (r < 0)
-                                       goto ERROR;
-
-                               // Append arguments if any
-                               if (args) {
-                                       r = asprintf(&buffer, "%s %s", buffer, args);
-                                       if (r < 0)
-                                               goto ERROR;
-                               }
-
-                               // Terminate the line
-                               r = asprintf(&buffer, "%s\n", buffer);
-                               if (r < 0)
-                                       goto ERROR;
-
-                               // Store length of the buffer
-                               l = r;
-
-                               // Process the next line
-                               break;
-
-                       default:
-                               // Append the line to the buffer
-                               r = asprintf(&buffer, "%s%s", buffer, line);
-                               if (r < 0)
-                                       goto ERROR;
-
-                               // Store length of the buffer
-                               l = r;
-
-                               break;
-               }
-       }
-
-       // Go back to the beginning of the file
-       rewind(f);
-
-       // Truncate the existing content
-       r = ftruncate(fileno(f), 0);
-       if (r) {
-               ERROR(file->ctx, "Could not truncate %s: %m\n", path);
-               r = -errno;
-               goto ERROR;
-       }
-
-       // Write back the buffer
-       if (buffer) {
-               size_t bytes_written = fwrite(buffer, 1, l, f);
-               if (bytes_written < l) {
-                       ERROR(file->ctx, "%s: Could not write the payload: %m\n", path);
-                       r = -errno;
-                       goto ERROR;
-               }
-       }
-
-       // Success!
-       r = 0;
-
-ERROR:
-       if (buffer)
-               free(buffer);
-       if (line)
-               free(line);
-       if (f)
-               fclose(f);
-
-       return r;
-}
-
-static int pakfire_file_check_interpreter(struct pakfire_file* file) {
-       char* interpreter = NULL;
-       int r;
-
-       // Fetch the script interpreter
-       r = pakfire_file_get_script_interpreter(file, &interpreter);
-       if (r)
-               return r;
-
-       // If there is no result, the file is not a script
-       if (!interpreter)
-               return 0;
-
-       DEBUG(file->ctx, "%s: Interpreter: %s\n",
-               pakfire_file_get_path(file), interpreter);
-
-       // Paths must be absolute
-       if (*interpreter != '/')
-               file->issues |= PAKFIRE_FILE_INVALID_INTERPRETER;
-
-       // Check if the interpreter is in /usr/local
-       else if (pakfire_string_startswith(interpreter, "/usr/local/"))
-               file->issues |= PAKFIRE_FILE_INVALID_INTERPRETER;
-
-       // We don't support "env", but will automatically fix it
-       else if (strcmp(interpreter, "/usr/bin/env") == 0) {
-               r = pakfire_file_fix_interpreter(file, interpreter);
-               if (r) {
-                       ERROR(file->ctx, "%s: Could not fix interpreter: %m\n",
-                               pakfire_file_get_path(file));
-                       goto ERROR;
-               }
-       }
-
-ERROR:
-       if (interpreter)
-               free(interpreter);
-
-       return r;
-}
-
 int pakfire_file_check(struct pakfire_file* file, int* issues) {
        int r;
 
@@ -2107,11 +1799,6 @@ int pakfire_file_check(struct pakfire_file* file, int* issues) {
                if (r)
                        file->issues |= PAKFIRE_FILE_FHS_ERROR;
 
-               // Perform interpreter check
-               r = pakfire_file_check_interpreter(file);
-               if (r)
-                       return r;
-
                // All checks done
                file->check_done = 1;
        }
index 76180f1059e9094748faff1fc09bacd9dcdeaff5..f0f51a856ad1c673945af3a16c5355010e32420f 100644 (file)
@@ -20,6 +20,7 @@
 
 #include <errno.h>
 #include <stdlib.h>
+#include <sys/mman.h>
 
 // libelf
 #include <gelf.h>
@@ -45,6 +46,12 @@ struct pakfire_linter_file {
        // File Descriptor
        int fd;
 
+       // The length of the mapped file
+       size_t length;
+
+       // Mapped Data
+       void* data;
+
        // Path
        const char* path;
 };
@@ -97,6 +104,14 @@ int pakfire_linter_file_create(struct pakfire_linter_file** lfile,
                goto ERROR;
        }
 
+       // Store the length
+       l->length = lseek(l->fd, 0, SEEK_END);
+       if (l->length <= 0) {
+               ERROR(l->ctx, "Could not determine the length of the file: %m\n");
+               r = -errno;
+               goto ERROR;
+       }
+
        // Return the pointer
        *lfile = pakfire_linter_file_ref(l);
 
@@ -108,6 +123,14 @@ ERROR:
 }
 
 static void pakfire_linter_file_free(struct pakfire_linter_file* lfile) {
+       int r;
+
+       if (lfile->data) {
+               r = munmap(lfile->data, lfile->length);
+               if (r < 0)
+                       ERROR(lfile->ctx, "Could not unmmap %s: %m\n", lfile->path);
+       }
+
        if (lfile->linter)
                pakfire_linter_unref(lfile->linter);
        if (lfile->file)
@@ -132,6 +155,23 @@ struct pakfire_linter_file* pakfire_linter_file_unref(struct pakfire_linter_file
        return NULL;
 }
 
+/*
+       Maps the file into memory
+*/
+static const char* pakfire_linter_file_map(struct pakfire_linter_file* lfile) {
+       // Map the data if not already done so
+       if (!lfile->data) {
+               lfile->data = mmap(NULL, lfile->length, PROT_READ, MAP_PRIVATE, lfile->fd, 0);
+               if (lfile->data == MAP_FAILED) {
+                       ERROR(lfile->ctx, "Could not mmap() %s: %m\n", lfile->path);
+
+                       lfile->data = NULL;
+               }
+       }
+
+       return (const char*)lfile->data;
+}
+
 static int pakfire_linter_file_check_caps(struct pakfire_linter_file* lfile) {
        // Files cannot have capabilities but not be executable
        if (!pakfire_file_is_executable(lfile->file) && pakfire_file_has_caps(lfile->file))
@@ -140,6 +180,117 @@ static int pakfire_linter_file_check_caps(struct pakfire_linter_file* lfile) {
        return 0;
 }
 
+#define pakfire_linter_file_get_script_interpreter(lfile, interpreter) \
+       __pakfire_linter_file_get_script_interpreter(lfile, interpreter, sizeof(interpreter))
+
+static int __pakfire_linter_file_get_script_interpreter(struct pakfire_linter_file* lfile,
+               char* interpreter, size_t length) {
+       const char* data = NULL;
+       char shebang[PATH_MAX];
+       char* eol = NULL;
+       char* p = NULL;
+       int r;
+
+       // Check inputs
+       if (!interpreter)
+               return -EINVAL;
+
+       // Only run for executable files
+       if (!pakfire_file_is_executable(lfile->file))
+               return 0;
+
+       // If the file is shorter than four bytes there is nothing to do here
+       if (lfile->length <= 4)
+               return 0;
+
+       // Map the file into memory
+       data = pakfire_linter_file_map(lfile);
+       if (!data)
+               return -errno;
+
+       // The file must start with #!
+       if (data[0] == '#' && data[1] == '!') {
+               // Scan for the end of the first line
+               eol = memchr(data, '\n', lfile->length);
+               if (!eol) {
+                       ERROR(lfile->ctx, "Could not find end-of-line in %s: %m\n", lfile->path);
+                       return -errno;
+               }
+
+               int l = eol - data;
+
+               // Copy the line into a buffer
+               r = pakfire_string_format(shebang, "%.*s", l, data);
+               if (r < 0)
+                       return r;
+
+               // Remove #!
+               memmove(shebang, shebang + 2, l);
+               l -= 2;
+
+               // Set p to the beginning of the buffer
+               p = shebang;
+
+               // Consume any space between #! and the path
+               while (*p && isspace(*p))
+                       memmove(shebang, shebang + 1, --l);
+
+               // Find the end of the command (cuts off any options)
+               while (*p) {
+                       if (isspace(*p)) {
+                               *p = '\0';
+                               break;
+                       }
+
+                       p++;
+               }
+
+               // Copy whatever is left to the output buffer
+               r = __pakfire_string_set(interpreter, length, shebang);
+               if (r < 0)
+                       return r;
+
+               // Return non-zero if we found something
+               return 1;
+       }
+
+       return 0;
+}
+
+static int pakfire_linter_check_script_interpreter(struct pakfire_linter_file* lfile) {
+       char interpreter[PATH_MAX];
+       int r;
+
+       // Fetch the interpreter
+       r = pakfire_linter_file_get_script_interpreter(lfile, interpreter);
+       if (r <= 0)
+               return r;
+
+       // Check if the interpreter is absolute
+       if (!pakfire_string_startswith(interpreter, "/")) {
+               r = pakfire_linter_file_error(lfile,
+                       "Interpreter must be absolute (is '%s')", interpreter);
+               if (r < 0)
+                       return r;
+
+       // Interpreter cannot be in /usr/local
+       } else if (pakfire_path_match(interpreter, "/usr/local/**")) {
+               r = pakfire_linter_file_error(lfile, "Interpreter cannot be in /usr/local");
+               if (r < 0)
+                       return r;
+
+       // /usr/bin/env is not allowed
+       } else if (strcmp(interpreter, "/usr/bin/env") == 0) {
+               r = pakfire_linter_file_error(lfile, "Interpreter cannot be /usr/bin/env");
+               if (r < 0)
+                       return r;
+       }
+
+       // XXX Check if the interpreter is provides by something
+
+       return 0;
+}
+
 static int pakfire_linter_file_init_libelf(struct pakfire_linter_file* lfile) {
        // Initialize libelf
        if (elf_version(EV_CURRENT) == EV_NONE) {
@@ -893,6 +1044,11 @@ int pakfire_linter_file_lint(struct pakfire_linter_file* lfile) {
        if (r < 0)
                return r;
 
+       // Check script interpreter
+       r = pakfire_linter_check_script_interpreter(lfile);
+       if (r < 0)
+               return r;
+
        // Skip firmware files
        if (pakfire_file_matches(lfile->file, "/usr/lib/firmware/**"))
                return 0;