From: Michael Tremer Date: Sat, 26 Oct 2024 17:26:27 +0000 (+0000) Subject: linter: Move script interpreter check X-Git-Tag: 0.9.30~812 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=e129ae41946f3b7b3ca6ac9cf753ea0d3c0db135;p=pakfire.git linter: Move script interpreter check Signed-off-by: Michael Tremer --- diff --git a/src/libpakfire/file.c b/src/libpakfire/file.c index 956a1f2be..d28a0de87 100644 --- a/src/libpakfire/file.c +++ b/src/libpakfire/file.c @@ -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; } diff --git a/src/libpakfire/linter-file.c b/src/libpakfire/linter-file.c index 76180f105..f0f51a856 100644 --- a/src/libpakfire/linter-file.c +++ b/src/libpakfire/linter-file.c @@ -20,6 +20,7 @@ #include #include +#include // libelf #include @@ -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;