diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3f2544281..0df0e6b8d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -16,6 +16,10 @@ add_subdirectory(xdg-basedir) set(AUXILIARY_FILES_DESTINATION "lib/appimagekit" CACHE STRING "Target install directory for mksquashfs") +add_library(md5 md5.c md5.h) +target_include_directories(md5 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + + add_library(libappimage SHARED ${PROJECT_SOURCE_DIR}/include/appimage/appimage.h shared.c @@ -23,6 +27,7 @@ add_library(libappimage SHARED notify.c elf.c appimagetool_shared.c + hexlify.c ) set_target_properties(libappimage PROPERTIES PREFIX "") @@ -63,6 +68,7 @@ add_library(libappimage_static STATIC notify.c elf.c appimagetool_shared.c + hexlify.c ) set_target_properties(libappimage_static PROPERTIES PREFIX "") diff --git a/src/appimagetool_shared.c b/src/appimagetool_shared.c index 6b74ef26d..aba816b1f 100644 --- a/src/appimagetool_shared.c +++ b/src/appimagetool_shared.c @@ -187,18 +187,3 @@ bool appimage_type2_digest_md5(const char* path, char* digest) { return true; } - -char* appimage_hexlify(const char* bytes, const size_t numBytes) { - // first of all, allocate the new string - // a hexadecimal representation works like "every byte will be represented by two chars" - // additionally, we need to null-terminate the string - char* hexlified = (char*) calloc((2 * numBytes + 1), sizeof(char)); - - for (size_t i = 0; i < numBytes; i++) { - char buffer[3]; - sprintf(buffer, "%02x", (unsigned char) bytes[i]); - strcat(hexlified, buffer); - } - - return hexlified; -} diff --git a/src/build-runtime.cmake b/src/build-runtime.cmake index 774121ac7..1c77b8fd2 100644 --- a/src/build-runtime.cmake +++ b/src/build-runtime.cmake @@ -92,10 +92,11 @@ add_custom_command( # add the runtime as a normal executable # CLion will recognize it as a normal executable, one can simply step into the code -add_executable(runtime ${CMAKE_CURRENT_BINARY_DIR}/runtime.4.o elf.c notify.c getsection.c) +add_executable(runtime ${CMAKE_CURRENT_BINARY_DIR}/runtime.4.o elf.c notify.c getsection.c hexlify.c) # CMake gets confused by the .o object, therefore we need to tell it that it shall link everything using the C compiler set_property(TARGET runtime PROPERTY LINKER_LANGUAGE C) -target_link_libraries(runtime PRIVATE squashfuse dl xz libzlib pthread) +target_link_libraries(runtime PRIVATE squashfuse dl xz libzlib pthread md5) +target_include_directories(runtime PRIVATE ${PROJECT_SOURCE_DIR}/include) if(BUILD_DEBUG) message(WARNING "Debug build, not stripping runtime to allow debugging using gdb etc.") diff --git a/src/hexlify.c b/src/hexlify.c new file mode 100644 index 000000000..113a595ba --- /dev/null +++ b/src/hexlify.c @@ -0,0 +1,18 @@ +#include +#include +#include + +char* appimage_hexlify(const char* bytes, const size_t numBytes) { + // first of all, allocate the new string + // a hexadecimal representation works like "every byte will be represented by two chars" + // additionally, we need to null-terminate the string + char* hexlified = (char*) calloc((2 * numBytes + 1), sizeof(char)); + + for (size_t i = 0; i < numBytes; i++) { + char buffer[3]; + sprintf(buffer, "%02x", (unsigned char) bytes[i]); + strcat(hexlified, buffer); + } + + return hexlified; +} diff --git a/src/md5.c b/src/md5.c new file mode 100644 index 000000000..bb3cf18d8 --- /dev/null +++ b/src/md5.c @@ -0,0 +1,335 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// WjCryptLib_Md5 +// +// Implementation of MD5 hash function. Originally written by Alexander Peslyak. Modified by WaterJuice retaining +// Public Domain license. +// +// This is free and unencumbered software released into the public domain - June 2013 waterjuice.org +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// IMPORTS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "md5.h" +#include + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// INTERNAL FUNCTIONS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// F, G, H, I +// +// The basic MD5 functions. F and G are optimised compared to their RFC 1321 definitions for architectures that lack +// an AND-NOT instruction, just like in Colin Plumb's implementation. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#define F( x, y, z ) ( (z) ^ ((x) & ((y) ^ (z))) ) +#define G( x, y, z ) ( (y) ^ ((z) & ((x) ^ (y))) ) +#define H( x, y, z ) ( (x) ^ (y) ^ (z) ) +#define I( x, y, z ) ( (y) ^ ((x) | ~(z)) ) + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// STEP +// +// The MD5 transformation for all four rounds. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#define STEP( f, a, b, c, d, x, t, s ) \ + (a) += f((b), (c), (d)) + (x) + (t); \ + (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \ + (a) += (b); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// TransformFunction +// +// This processes one or more 64-byte data blocks, but does NOT update the bit counters. There are no alignment +// requirements. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +static +void* +TransformFunction + ( + Md5Context* ctx, + void const* data, + uintmax_t size + ) +{ + uint8_t* ptr; + uint32_t a; + uint32_t b; + uint32_t c; + uint32_t d; + uint32_t saved_a; + uint32_t saved_b; + uint32_t saved_c; + uint32_t saved_d; + + #define GET(n) (ctx->block[(n)]) + #define SET(n) (ctx->block[(n)] = \ + ((uint32_t)ptr[(n)*4 + 0] << 0 ) \ + | ((uint32_t)ptr[(n)*4 + 1] << 8 ) \ + | ((uint32_t)ptr[(n)*4 + 2] << 16) \ + | ((uint32_t)ptr[(n)*4 + 3] << 24) ) + + ptr = (uint8_t*)data; + + a = ctx->a; + b = ctx->b; + c = ctx->c; + d = ctx->d; + + do + { + saved_a = a; + saved_b = b; + saved_c = c; + saved_d = d; + + // Round 1 + STEP( F, a, b, c, d, SET(0), 0xd76aa478, 7 ) + STEP( F, d, a, b, c, SET(1), 0xe8c7b756, 12 ) + STEP( F, c, d, a, b, SET(2), 0x242070db, 17 ) + STEP( F, b, c, d, a, SET(3), 0xc1bdceee, 22 ) + STEP( F, a, b, c, d, SET(4), 0xf57c0faf, 7 ) + STEP( F, d, a, b, c, SET(5), 0x4787c62a, 12 ) + STEP( F, c, d, a, b, SET(6), 0xa8304613, 17 ) + STEP( F, b, c, d, a, SET(7), 0xfd469501, 22 ) + STEP( F, a, b, c, d, SET(8 ), 0x698098d8, 7 ) + STEP( F, d, a, b, c, SET(9 ), 0x8b44f7af, 12 ) + STEP( F, c, d, a, b, SET(10 ), 0xffff5bb1, 17 ) + STEP( F, b, c, d, a, SET(11 ), 0x895cd7be, 22 ) + STEP( F, a, b, c, d, SET(12 ), 0x6b901122, 7 ) + STEP( F, d, a, b, c, SET(13 ), 0xfd987193, 12 ) + STEP( F, c, d, a, b, SET(14 ), 0xa679438e, 17 ) + STEP( F, b, c, d, a, SET(15 ), 0x49b40821, 22 ) + + // Round 2 + STEP( G, a, b, c, d, GET(1), 0xf61e2562, 5 ) + STEP( G, d, a, b, c, GET(6), 0xc040b340, 9 ) + STEP( G, c, d, a, b, GET(11), 0x265e5a51, 14 ) + STEP( G, b, c, d, a, GET(0), 0xe9b6c7aa, 20 ) + STEP( G, a, b, c, d, GET(5), 0xd62f105d, 5 ) + STEP( G, d, a, b, c, GET(10), 0x02441453, 9 ) + STEP( G, c, d, a, b, GET(15), 0xd8a1e681, 14 ) + STEP( G, b, c, d, a, GET(4), 0xe7d3fbc8, 20 ) + STEP( G, a, b, c, d, GET(9), 0x21e1cde6, 5 ) + STEP( G, d, a, b, c, GET(14), 0xc33707d6, 9 ) + STEP( G, c, d, a, b, GET(3), 0xf4d50d87, 14 ) + STEP( G, b, c, d, a, GET(8), 0x455a14ed, 20 ) + STEP( G, a, b, c, d, GET(13), 0xa9e3e905, 5 ) + STEP( G, d, a, b, c, GET(2), 0xfcefa3f8, 9 ) + STEP( G, c, d, a, b, GET(7), 0x676f02d9, 14 ) + STEP( G, b, c, d, a, GET(12), 0x8d2a4c8a, 20 ) + + // Round 3 + STEP( H, a, b, c, d, GET(5), 0xfffa3942, 4 ) + STEP( H, d, a, b, c, GET(8), 0x8771f681, 11 ) + STEP( H, c, d, a, b, GET(11), 0x6d9d6122, 16 ) + STEP( H, b, c, d, a, GET(14), 0xfde5380c, 23 ) + STEP( H, a, b, c, d, GET(1), 0xa4beea44, 4 ) + STEP( H, d, a, b, c, GET(4), 0x4bdecfa9, 11 ) + STEP( H, c, d, a, b, GET(7), 0xf6bb4b60, 16 ) + STEP( H, b, c, d, a, GET(10), 0xbebfbc70, 23 ) + STEP( H, a, b, c, d, GET(13), 0x289b7ec6, 4 ) + STEP( H, d, a, b, c, GET(0), 0xeaa127fa, 11 ) + STEP( H, c, d, a, b, GET(3), 0xd4ef3085, 16 ) + STEP( H, b, c, d, a, GET(6), 0x04881d05, 23 ) + STEP( H, a, b, c, d, GET(9), 0xd9d4d039, 4 ) + STEP( H, d, a, b, c, GET(12), 0xe6db99e5, 11 ) + STEP( H, c, d, a, b, GET(15), 0x1fa27cf8, 16 ) + STEP( H, b, c, d, a, GET(2), 0xc4ac5665, 23 ) + + // Round 4 + STEP( I, a, b, c, d, GET(0), 0xf4292244, 6 ) + STEP( I, d, a, b, c, GET(7), 0x432aff97, 10 ) + STEP( I, c, d, a, b, GET(14), 0xab9423a7, 15 ) + STEP( I, b, c, d, a, GET(5), 0xfc93a039, 21 ) + STEP( I, a, b, c, d, GET(12), 0x655b59c3, 6 ) + STEP( I, d, a, b, c, GET(3), 0x8f0ccc92, 10 ) + STEP( I, c, d, a, b, GET(10), 0xffeff47d, 15 ) + STEP( I, b, c, d, a, GET(1), 0x85845dd1, 21 ) + STEP( I, a, b, c, d, GET(8), 0x6fa87e4f, 6 ) + STEP( I, d, a, b, c, GET(15), 0xfe2ce6e0, 10 ) + STEP( I, c, d, a, b, GET(6), 0xa3014314, 15 ) + STEP( I, b, c, d, a, GET(13), 0x4e0811a1, 21 ) + STEP( I, a, b, c, d, GET(4), 0xf7537e82, 6 ) + STEP( I, d, a, b, c, GET(11), 0xbd3af235, 10 ) + STEP( I, c, d, a, b, GET(2), 0x2ad7d2bb, 15 ) + STEP( I, b, c, d, a, GET(9), 0xeb86d391, 21 ) + + a += saved_a; + b += saved_b; + c += saved_c; + d += saved_d; + + ptr += 64; + } while( size -= 64 ); + + ctx->a = a; + ctx->b = b; + ctx->c = c; + ctx->d = d; + + #undef GET + #undef SET + + return ptr; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// EXPORTED FUNCTIONS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Md5Initialise +// +// Initialises an MD5 Context. Use this to initialise/reset a context. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void +Md5Initialise + ( + Md5Context* Context // [out] + ) +{ + Context->a = 0x67452301; + Context->b = 0xefcdab89; + Context->c = 0x98badcfe; + Context->d = 0x10325476; + + Context->lo = 0; + Context->hi = 0; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Md5Update +// +// Adds data to the MD5 context. This will process the data and update the internal state of the context. Keep on +// calling this function until all the data has been added. Then call Md5Finalise to calculate the hash. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void +Md5Update + ( + Md5Context* Context, // [in out] + void const* Buffer, // [in] + uint32_t BufferSize // [in] + ) +{ + uint32_t saved_lo; + uint32_t used; + uint32_t free; + + saved_lo = Context->lo; + if( (Context->lo = (saved_lo + BufferSize) & 0x1fffffff) < saved_lo ) + { + Context->hi++; + } + Context->hi += (uint32_t)( BufferSize >> 29 ); + + used = saved_lo & 0x3f; + + if( used ) + { + free = 64 - used; + + if( BufferSize < free ) + { + memcpy( &Context->buffer[used], Buffer, BufferSize ); + return; + } + + memcpy( &Context->buffer[used], Buffer, free ); + Buffer = (uint8_t*)Buffer + free; + BufferSize -= free; + TransformFunction(Context, Context->buffer, 64); + } + + if( BufferSize >= 64 ) + { + Buffer = TransformFunction( Context, Buffer, BufferSize & ~(unsigned long)0x3f ); + BufferSize &= 0x3f; + } + + memcpy( Context->buffer, Buffer, BufferSize ); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Md5Finalise +// +// Performs the final calculation of the hash and returns the digest (16 byte buffer containing 128bit hash). After +// calling this, Md5Initialised must be used to reuse the context. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void +Md5Finalise + ( + Md5Context* Context, // [in out] + MD5_HASH* Digest // [in] + ) +{ + uint32_t used; + uint32_t free; + + used = Context->lo & 0x3f; + + Context->buffer[used++] = 0x80; + + free = 64 - used; + + if(free < 8) + { + memset( &Context->buffer[used], 0, free ); + TransformFunction( Context, Context->buffer, 64 ); + used = 0; + free = 64; + } + + memset( &Context->buffer[used], 0, free - 8 ); + + Context->lo <<= 3; + Context->buffer[56] = (uint8_t)( Context->lo ); + Context->buffer[57] = (uint8_t)( Context->lo >> 8 ); + Context->buffer[58] = (uint8_t)( Context->lo >> 16 ); + Context->buffer[59] = (uint8_t)( Context->lo >> 24 ); + Context->buffer[60] = (uint8_t)( Context->hi ); + Context->buffer[61] = (uint8_t)( Context->hi >> 8 ); + Context->buffer[62] = (uint8_t)( Context->hi >> 16 ); + Context->buffer[63] = (uint8_t)( Context->hi >> 24 ); + + TransformFunction( Context, Context->buffer, 64 ); + + Digest->bytes[0] = (uint8_t)( Context->a ); + Digest->bytes[1] = (uint8_t)( Context->a >> 8 ); + Digest->bytes[2] = (uint8_t)( Context->a >> 16 ); + Digest->bytes[3] = (uint8_t)( Context->a >> 24 ); + Digest->bytes[4] = (uint8_t)( Context->b ); + Digest->bytes[5] = (uint8_t)( Context->b >> 8 ); + Digest->bytes[6] = (uint8_t)( Context->b >> 16 ); + Digest->bytes[7] = (uint8_t)( Context->b >> 24 ); + Digest->bytes[8] = (uint8_t)( Context->c ); + Digest->bytes[9] = (uint8_t)( Context->c >> 8 ); + Digest->bytes[10] = (uint8_t)( Context->c >> 16 ); + Digest->bytes[11] = (uint8_t)( Context->c >> 24 ); + Digest->bytes[12] = (uint8_t)( Context->d ); + Digest->bytes[13] = (uint8_t)( Context->d >> 8 ); + Digest->bytes[14] = (uint8_t)( Context->d >> 16 ); + Digest->bytes[15] = (uint8_t)( Context->d >> 24 ); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Md5Calculate +// +// Combines Md5Initialise, Md5Update, and Md5Finalise into one function. Calculates the MD5 hash of the buffer. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void +Md5Calculate + ( + void const* Buffer, // [in] + uint32_t BufferSize, // [in] + MD5_HASH* Digest // [in] + ) +{ + Md5Context context; + + Md5Initialise( &context ); + Md5Update( &context, Buffer, BufferSize ); + Md5Finalise( &context, Digest ); +} diff --git a/src/md5.h b/src/md5.h new file mode 100644 index 000000000..98eab9ac7 --- /dev/null +++ b/src/md5.h @@ -0,0 +1,96 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// WjCryptLib_Md5 +// +// Implementation of MD5 hash function. Originally written by Alexander Peslyak. Modified by WaterJuice retaining +// Public Domain license. +// +// This is free and unencumbered software released into the public domain - June 2013 waterjuice.org +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// IMPORTS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// TYPES +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Md5Context - This must be initialised using Md5Initialised. Do not modify the contents of this structure directly. +typedef struct +{ + uint32_t lo; + uint32_t hi; + uint32_t a; + uint32_t b; + uint32_t c; + uint32_t d; + uint8_t buffer[64]; + uint32_t block[16]; +} Md5Context; + +#define MD5_HASH_SIZE ( 128 / 8 ) + +typedef struct +{ + uint8_t bytes [MD5_HASH_SIZE]; +} MD5_HASH; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC FUNCTIONS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Md5Initialise +// +// Initialises an MD5 Context. Use this to initialise/reset a context. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void +Md5Initialise + ( + Md5Context* Context // [out] + ); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Md5Update +// +// Adds data to the MD5 context. This will process the data and update the internal state of the context. Keep on +// calling this function until all the data has been added. Then call Md5Finalise to calculate the hash. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void +Md5Update + ( + Md5Context* Context, // [in out] + void const* Buffer, // [in] + uint32_t BufferSize // [in] + ); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Md5Finalise +// +// Performs the final calculation of the hash and returns the digest (16 byte buffer containing 128bit hash). After +// calling this, Md5Initialised must be used to reuse the context. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void +Md5Finalise + ( + Md5Context* Context, // [in out] + MD5_HASH* Digest // [in] + ); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Md5Calculate +// +// Combines Md5Initialise, Md5Update, and Md5Finalise into one function. Calculates the MD5 hash of the buffer. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void +Md5Calculate + ( + void const* Buffer, // [in] + uint32_t BufferSize, // [in] + MD5_HASH* Digest // [in] + ); diff --git a/src/runtime.c b/src/runtime.c index 0e151d007..25e382782 100644 --- a/src/runtime.c +++ b/src/runtime.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -45,9 +46,11 @@ #include #include #include +#include #include "elf.h" #include "getsection.h" +#include "md5.h" #ifndef ENABLE_DLOPEN #define ENABLE_DLOPEN @@ -55,8 +58,6 @@ #include "squashfuse_dlopen.h" #include "appimage/appimage.h" -#include - //#include "notify.c" extern int notify(char *title, char *body, int timeout); struct stat st; @@ -165,7 +166,7 @@ char* getArg(int argc, char *argv[],char chr) /* mkdir -p implemented in C, needed for https://github.com/AppImage/AppImageKit/issues/333 * https://gist.github.com/JonathonReinhart/8c0d90191c38af2dcadb102c4e202950 */ int -mkdir_p(const char *path) +mkdir_p(const char* const path) { /* Adapted from http://stackoverflow.com/a/2336245/119527 */ const size_t len = strlen(path); @@ -210,7 +211,8 @@ print_help(const char *appimage_path) // TODO: "--appimage-list List content from embedded filesystem image\n" printf( "AppImage options:\n\n" - " --appimage-extract Extract content from embedded filesystem image\n" + " --appimage-extract [] Extract content from embedded filesystem image\n" + " If pattern is passed, only extract matching files\n" " --appimage-help Print this help\n" " --appimage-mount Mount embedded filesystem image and print\n" " mount point and wait for kill with Ctrl-C\n" @@ -266,9 +268,217 @@ portable_option(const char *arg, const char *appimage_path, const char *name) } } -int -main (int argc, char *argv[]) -{ +bool extract_appimage(const char* const appimage_path, const char* const _prefix, const char* const _pattern, const bool overwrite) { + sqfs_err err = SQFS_OK; + sqfs_traverse trv; + sqfs fs; + char prefixed_path_to_extract[1024]; + + // local copy we can modify safely + // allocate 1 more byte than we would need so we can add a trailing slash if there is none yet + char* prefix = malloc(strlen(_prefix) + 2); + strcpy(prefix, _prefix); + + // sanitize prefix + if (prefix[strlen(prefix) - 1] != '/') + strcat(prefix, "/"); + + if (access(prefix, F_OK) == -1) { + if (mkdir_p(prefix) == -1) { + perror("mkdir_p error"); + return false; + } + } + + if ((err = sqfs_open_image(&fs, appimage_path, (size_t) fs_offset))) { + fprintf(stderr, "Failed to open squashfs image\n"); + return false; + }; + + // track duplicate inodes for hardlinks + char** created_inode = calloc(fs.sb.inodes, sizeof(char*)); + if (created_inode == NULL) { + fprintf(stderr, "Failed allocating memory to track hardlinks\n"); + return false; + } + + if ((err = sqfs_traverse_open(&trv, &fs, sqfs_inode_root(&fs)))) { + fprintf(stderr, "sqfs_traverse_open error\n"); + free(created_inode); + return false; + } + + bool rv = true; + + while (sqfs_traverse_next(&trv, &err)) { + if (!trv.dir_end) { + if (_pattern == NULL || fnmatch(_pattern, trv.path, FNM_FILE_NAME) == 0) { + // fprintf(stderr, "trv.path: %s\n", trv.path); + // fprintf(stderr, "sqfs_inode_id: %lu\n", trv.entry.inode); + sqfs_inode inode; + if (sqfs_inode_get(&fs, &inode, trv.entry.inode)) { + fprintf(stderr, "sqfs_inode_get error\n"); + rv = false; + break; + } + // fprintf(stderr, "inode.base.inode_type: %i\n", inode.base.inode_type); + // fprintf(stderr, "inode.xtra.reg.file_size: %lu\n", inode.xtra.reg.file_size); + strcpy(prefixed_path_to_extract, ""); + strcat(strcat(prefixed_path_to_extract, prefix), trv.path); + fprintf(stderr, "%s\n", prefixed_path_to_extract); + if (inode.base.inode_type == SQUASHFS_DIR_TYPE || inode.base.inode_type == SQUASHFS_LDIR_TYPE) { + // fprintf(stderr, "inode.xtra.dir.parent_inode: %ui\n", inode.xtra.dir.parent_inode); + // fprintf(stderr, "mkdir_p: %s/\n", prefixed_path_to_extract); + if (access(prefixed_path_to_extract, F_OK) == -1) { + if (mkdir_p(prefixed_path_to_extract) == -1) { + perror("mkdir_p error"); + rv = false; + break; + } + } + } else if (inode.base.inode_type == SQUASHFS_REG_TYPE || inode.base.inode_type == SQUASHFS_LREG_TYPE) { + // if we've already created this inode, then this is a hardlink + char* existing_path_for_inode = created_inode[inode.base.inode_number - 1]; + if (existing_path_for_inode != NULL) { + unlink(prefixed_path_to_extract); + if (link(existing_path_for_inode, prefixed_path_to_extract) == -1) { + fprintf(stderr, "Couldn't create hardlink from \"%s\" to \"%s\": %s\n", + prefixed_path_to_extract, existing_path_for_inode, strerror(errno)); + rv = false; + break; + } else { + continue; + } + } else { + struct stat st; + if (!overwrite && stat(prefixed_path_to_extract, &st) == 0 && st.st_size == inode.xtra.reg.file_size) { + fprintf(stderr, "File exists and file size matches, skipping\n"); + continue; + } + + // track the path we extract to for this inode, so that we can `link` if this inode is found again + created_inode[inode.base.inode_number - 1] = strdup(prefixed_path_to_extract); + // fprintf(stderr, "Extract to: %s\n", prefixed_path_to_extract); + if (private_sqfs_stat(&fs, &inode, &st) != 0) + die("private_sqfs_stat error"); + // Read the file in chunks + off_t bytes_already_read = 0; + sqfs_off_t bytes_at_a_time = 64 * 1024; + FILE* f; + f = fopen(prefixed_path_to_extract, "w+"); + if (f == NULL) { + perror("fopen error"); + rv = false; + break; + } + while (bytes_already_read < inode.xtra.reg.file_size) { + char buf[bytes_at_a_time]; + if (sqfs_read_range(&fs, &inode, (sqfs_off_t) bytes_already_read, &bytes_at_a_time, buf)) { + perror("sqfs_read_range error"); + fclose(f); + rv = false; + break; + } + // fwrite(buf, 1, bytes_at_a_time, stdout); + fwrite(buf, 1, bytes_at_a_time, f); + bytes_already_read = bytes_already_read + bytes_at_a_time; + } + fclose(f); + chmod(prefixed_path_to_extract, st.st_mode); + if (!rv) + break; + } + } else if (inode.base.inode_type == SQUASHFS_SYMLINK_TYPE) { + size_t size; + sqfs_readlink(&fs, &inode, NULL, &size); + char buf[size]; + int ret = sqfs_readlink(&fs, &inode, buf, &size); + if (ret != 0) { + perror("symlink error"); + rv = false; + break; + } + // fprintf(stderr, "Symlink: %s to %s \n", prefixed_path_to_extract, buf); + unlink(prefixed_path_to_extract); + ret = symlink(buf, prefixed_path_to_extract); + if (ret != 0) + fprintf(stderr, "WARNING: could not create symlink\n"); + } else { + fprintf(stderr, "TODO: Implement inode.base.inode_type %i\n", inode.base.inode_type); + } + // fprintf(stderr, "\n"); + + if (!rv) + break; + } + } + } + for (int i = 0; i < fs.sb.inodes; i++) { + free(created_inode[i]); + } + free(created_inode); + + if (err != SQFS_OK) { + fprintf(stderr, "sqfs_traverse_next error\n"); + rv = false; + } + sqfs_traverse_close(&trv); + sqfs_fd_close(fs.fd); + + return rv; +} + +int rm_recursive_callback(const char* path, const struct stat* stat, const int type, struct FTW* ftw) { + (void) stat; + (void) ftw; + + switch (type) { + case FTW_NS: + case FTW_DNR: + fprintf(stderr, "%s: ftw error: %s\n", + path, strerror(errno)); + return 1; + + case FTW_D: + // ignore directories at first, will be handled by FTW_DP + break; + + case FTW_F: + case FTW_SL: + case FTW_SLN: + if (remove(path) != 0) { + fprintf(stderr, "Failed to remove %s: %s\n", path, strerror(errno)); + return false; + } + break; + + + case FTW_DP: + if (rmdir(path) != 0) { + fprintf(stderr, "Failed to remove directory %s: %s\n", path, strerror(errno)); + return false; + } + break; + + default: + fprintf(stderr, "Unexpected fts_info\n"); + return 1; + } + + return 0; +}; + +bool rm_recursive(const char* const path) { + // FTW_DEPTH: perform depth-first search to make sure files are deleted before the containing directories + // FTW_MOUNT: prevent deletion of files on other mounted filesystems + // FTW_PHYS: do not follow symlinks, but report symlinks as such; this way, the symlink targets, which might point + // to locations outside path will not be deleted accidentally (attackers might abuse this) + int rv = nftw(path, &rm_recursive_callback, 0, FTW_DEPTH | FTW_MOUNT | FTW_PHYS); + + return rv == 0; +} + +int main(int argc, char *argv[]) { char appimage_path[PATH_MAX]; char argv0_path[PATH_MAX]; char * arg; @@ -352,130 +562,117 @@ main (int argc, char *argv[]) exit(0); } - /* Exract the AppImage */ arg=getArg(argc,argv,'-'); + + /* extract the AppImage */ if(arg && strcmp(arg,"appimage-extract")==0) { - sqfs_err err = SQFS_OK; - sqfs_traverse trv; - sqfs fs; - char *pattern; - char *prefix; - char prefixed_path_to_extract[1024]; - char **created_inode; - - prefix = "squashfs-root/"; - - if(access(prefix, F_OK ) == -1 ) { - if (mkdir_p(prefix) == -1) { - perror("mkdir_p error"); - exit(EXIT_FAILURE); - } - } + char* pattern; - if(argc == 3){ + // default use case: use standard prefix + if (argc == 2) { + pattern = NULL; + } else if (argc == 3) { pattern = argv[2]; - if (pattern[0] == '/') pattern++; // Remove leading '/' + } else { + fprintf(stderr, "Unexpected argument count: %d\n", argc - 1); + fprintf(stderr, "Usage: %s --appimage-extract []\n", argv0_path); + exit(1); } - if ((err = sqfs_open_image(&fs, appimage_path, fs_offset))) + if (!extract_appimage(appimage_path, "squashfs-root/", pattern, true)) { exit(1); + } - // track duplicate inodes for hardlinks - created_inode = malloc(fs.sb.inodes * sizeof(char *)); - if(created_inode != NULL) { - memset(created_inode, 0, fs.sb.inodes * sizeof(char *)); - } else { - fprintf(stderr, "Failed allocating memory to track hardlinks.\n"); + exit(0); + } + + if (arg && strcmp(arg, "appimage-extract-and-run") == 0) { + char temp_base[PATH_MAX] = "/tmp"; + + const char* const TMPDIR = getenv("TMPDIR"); + if (TMPDIR != NULL) + strcpy(temp_base, getenv("TMPDIR")); + + char* hexlified_digest; + + // calculate MD5 hash of file, and use it to make extracted directory name "content-aware" + // see https://github.com/AppImage/AppImageKit/issues/841 for more information + { + FILE* f = fopen(appimage_path, "rb"); + if (f == NULL) { + perror("Failed to open AppImage file"); + exit(1); + } + + Md5Context ctx; + Md5Initialise(&ctx); + + char buf[4096]; + for (size_t bytes_read; (bytes_read = fread(buf, sizeof(char), sizeof(buf), f)); bytes_read > 0) { + Md5Update(&ctx, buf, (uint32_t) bytes_read); + } + + MD5_HASH digest; + Md5Finalise(&ctx, &digest); + + hexlified_digest = appimage_hexlify(digest.bytes, sizeof(digest.bytes)); + } + + char* prefix = malloc(strlen(temp_base) + 20 + strlen(hexlified_digest) + 2); + strcpy(prefix, temp_base); + strcat(prefix, "/appimage_extracted_"); + strcat(prefix, hexlified_digest); + free(hexlified_digest); + + if (!extract_appimage(appimage_path, prefix, NULL, false)) { + fprintf(stderr, "Failed to extract AppImage\n"); exit(1); } - if ((err = sqfs_traverse_open(&trv, &fs, sqfs_inode_root(&fs)))) - die("sqfs_traverse_open error"); - while (sqfs_traverse_next(&trv, &err)) { - if (!trv.dir_end) { - if((argc != 3) || (fnmatch (pattern, trv.path, FNM_FILE_NAME) == 0)){ - // fprintf(stderr, "trv.path: %s\n", trv.path); - // fprintf(stderr, "sqfs_inode_id: %lu\n", trv.entry.inode); - sqfs_inode inode; - if (sqfs_inode_get(&fs, &inode, trv.entry.inode)) - die("sqfs_inode_get error"); - // fprintf(stderr, "inode.base.inode_type: %i\n", inode.base.inode_type); - // fprintf(stderr, "inode.xtra.reg.file_size: %lu\n", inode.xtra.reg.file_size); - strcpy(prefixed_path_to_extract, ""); - strcat(strcat(prefixed_path_to_extract, prefix), trv.path); - fprintf(stderr, "%s\n", prefixed_path_to_extract); - if(inode.base.inode_type == SQUASHFS_DIR_TYPE || inode.base.inode_type == SQUASHFS_LDIR_TYPE){ - // fprintf(stderr, "inode.xtra.dir.parent_inode: %ui\n", inode.xtra.dir.parent_inode); - // fprintf(stderr, "mkdir_p: %s/\n", prefixed_path_to_extract); - if(access(prefixed_path_to_extract, F_OK ) == -1 ) { - if (mkdir_p(prefixed_path_to_extract) == -1) { - perror("mkdir_p error"); - exit(EXIT_FAILURE); - } - } - } else if(inode.base.inode_type == SQUASHFS_REG_TYPE || inode.base.inode_type == SQUASHFS_LREG_TYPE){ - // if we've already created this inode, then this is a hardlink - char* existing_path_for_inode = created_inode[inode.base.inode_number - 1]; - if(existing_path_for_inode != NULL) { - unlink(prefixed_path_to_extract); - if(link(existing_path_for_inode, prefixed_path_to_extract) == -1) { - fprintf(stderr, "Couldn't create hardlink from \"%s\" to \"%s\": %s\n", prefixed_path_to_extract, existing_path_for_inode, strerror(errno)); - exit(EXIT_FAILURE); - } else { - continue; - } - } - // track the path we extract to for this inode, so that we can `link` if this inode is found again - created_inode[inode.base.inode_number - 1] = strdup(prefixed_path_to_extract); - // fprintf(stderr, "Extract to: %s\n", prefixed_path_to_extract); - if(private_sqfs_stat(&fs, &inode, &st) != 0) - die("private_sqfs_stat error"); - // Read the file in chunks - off_t bytes_already_read = 0; - sqfs_off_t bytes_at_a_time = 64*1024; - FILE * f; - f = fopen (prefixed_path_to_extract, "w+"); - if (f == NULL) - die("fopen error"); - while (bytes_already_read < inode.xtra.reg.file_size) - { - char buf[bytes_at_a_time]; - if (sqfs_read_range(&fs, &inode, (sqfs_off_t) bytes_already_read, &bytes_at_a_time, buf)) - die("sqfs_read_range error"); - // fwrite(buf, 1, bytes_at_a_time, stdout); - fwrite(buf, 1, bytes_at_a_time, f); - bytes_already_read = bytes_already_read + bytes_at_a_time; - } - fclose(f); - chmod (prefixed_path_to_extract, st.st_mode); - } else if(inode.base.inode_type == SQUASHFS_SYMLINK_TYPE){ - size_t size; - sqfs_readlink(&fs, &inode, NULL, &size); - char buf[size]; - int ret = sqfs_readlink(&fs, &inode, buf, &size); - if (ret != 0) - die("symlink error"); - // fprintf(stderr, "Symlink: %s to %s \n", prefixed_path_to_extract, buf); - unlink(prefixed_path_to_extract); - ret = symlink(buf, prefixed_path_to_extract); - if (ret != 0) - fprintf(stderr, "WARNING: could not create symlink\n"); - } else { - fprintf(stderr, "TODO: Implement inode.base.inode_type %i\n", inode.base.inode_type); - } - // fprintf(stderr, "\n"); + int pid; + if ((pid = fork()) == -1) { + int error = errno; + fprintf(stderr, "fork() failed: %s\n", strerror(error)); + exit(1); + } else if (pid == 0) { + const char apprun_fname[] = "AppRun"; + char* apprun_path = malloc(strlen(prefix) + 1 + strlen(apprun_fname) + 1); + strcpy(apprun_path, prefix); + strcat(apprun_path, "/"); + strcat(apprun_path, apprun_fname); + + // create copy of argument list without the --appimage-extract-and-run parameter + char* new_argv[argc]; + int new_argc = 0; + new_argv[new_argc++] = strdup(apprun_path); + for (int i = 1; i < argc; ++i) { + if (strcmp(argv[i], "--appimage-extract-and-run") != 0) { + new_argv[new_argc++] = strdup(argv[i]); } } + new_argv[new_argc] = NULL; + + execv(apprun_path, new_argv); + + int error = errno; + fprintf(stderr, "Failed to run %s: %s\n", apprun_path, strerror(error)); + + free(apprun_path); } - if (err) - die("sqfs_traverse_next error"); - for (int i = 0; i < fs.sb.inodes; i++) { - free(created_inode[i]); + + int rv = waitpid(pid, NULL, 0); + + if (getenv("NO_CLEANUP") == NULL) { + if (!rm_recursive(prefix)) { + fprintf(stderr, "Failed to clean up cache directory\n"); + rv = false; + } } - free(created_inode); - sqfs_traverse_close(&trv); - sqfs_fd_close(fs.fd); - exit(0); + + // template == prefix, must be freed only once + free(prefix); + + exit(rv ? 0 : 1); } if(arg && strcmp(arg,"appimage-version")==0) { @@ -520,7 +717,7 @@ main (int argc, char *argv[]) int dir_fd, res; char mount_dir[64]; - int namelen = strlen(basename(argv[0])); + size_t namelen = strlen(basename(argv[0])); if(namelen>6){ namelen=6; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 285598537..8fbb0483d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -29,6 +29,7 @@ if(BUILD_TESTING) ${PROJECT_SOURCE_DIR}/src/elf.c ${PROJECT_SOURCE_DIR}/src/getsection.c ${PROJECT_SOURCE_DIR}/src/appimagetool_shared.c + ${PROJECT_SOURCE_DIR}/src/hexlify.c ) target_link_libraries(test_shared