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;
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;
}
#include <errno.h>
#include <stdlib.h>
+#include <sys/mman.h>
// libelf
#include <gelf.h>
// File Descriptor
int fd;
+ // The length of the mapped file
+ size_t length;
+
+ // Mapped Data
+ void* data;
+
// Path
const char* path;
};
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);
}
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)
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))
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) {
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;