@@ -64,9 +64,28 @@ extern int sqfs_opt_proc(void* data, const char* arg, int key, struct fuse_args*
6464#include <libgen.h>
6565#include <dirent.h>
6666#include <ctype.h>
67+ #include <sched.h>
68+ #include <sys/mount.h>
69+ #include <sys/prctl.h>
70+ #include <sys/syscall.h>
71+ #include <linux/capability.h>
6772
6873const char * fusermountPath = NULL ;
6974
75+ // Capability structures for namespace support
76+ #define _LINUX_CAPABILITY_VERSION_3 0x20080522
77+
78+ struct cap_header {
79+ uint32_t version ;
80+ int pid ;
81+ };
82+
83+ struct cap_data {
84+ uint32_t effective ;
85+ uint32_t permitted ;
86+ uint32_t inheritable ;
87+ };
88+
7089typedef struct {
7190 uint32_t lo ;
7291 uint32_t hi ;
@@ -414,6 +433,162 @@ int appimage_print_binary(char* fname, unsigned long offset, unsigned long lengt
414433 return 0 ;
415434}
416435
436+ // Restore capabilities after entering user namespace
437+ void restore_capabilities (void ) {
438+ struct cap_header caps = {
439+ .version = _LINUX_CAPABILITY_VERSION_3 ,
440+ .pid = 0
441+ };
442+ struct cap_data cap_data [2 ] = {{0 , 0 , 0 }, {0 , 0 , 0 }};
443+
444+ if (syscall (SYS_capget , & caps , & cap_data ) == 0 ) {
445+ FILE * f = fopen ("/proc/sys/kernel/cap_last_cap" , "r" );
446+ uint32_t last_cap = 39 ; // default fallback
447+ if (f != NULL ) {
448+ if (fscanf (f , "%u" , & last_cap ) != 1 ) {
449+ last_cap = 39 ;
450+ }
451+ fclose (f );
452+ }
453+
454+ uint64_t all_caps = (1ULL << (last_cap + 1 )) - 1 ;
455+ cap_data [0 ].effective = (uint32_t )(all_caps & 0xFFFFFFFF );
456+ cap_data [0 ].permitted = (uint32_t )(all_caps & 0xFFFFFFFF );
457+ cap_data [0 ].inheritable = (uint32_t )(all_caps & 0xFFFFFFFF );
458+ cap_data [1 ].effective = (uint32_t )((all_caps >> 32 ) & 0xFFFFFFFF );
459+ cap_data [1 ].permitted = (uint32_t )((all_caps >> 32 ) & 0xFFFFFFFF );
460+ cap_data [1 ].inheritable = (uint32_t )((all_caps >> 32 ) & 0xFFFFFFFF );
461+
462+ syscall (SYS_capset , & caps , & cap_data );
463+
464+ for (uint32_t cap = 0 ; cap <= last_cap ; cap ++ ) {
465+ prctl (PR_CAP_AMBIENT , PR_CAP_AMBIENT_RAISE , cap , 0 , 0 );
466+ }
467+ } else {
468+ fprintf (stderr , "Warning: failed to get capabilities: %s\n" , strerror (errno ));
469+ }
470+ }
471+
472+ // Make all mounts private in the mount namespace
473+ bool try_make_mount_private (void ) {
474+ if (mount ("none" , "/" , NULL , MS_REC | MS_PRIVATE , NULL ) == 0 ) {
475+ return true;
476+ }
477+ return false;
478+ }
479+
480+ // Check if we're already in a user and mount namespace
481+ bool is_in_user_and_mount_namespace (void ) {
482+ FILE * f = fopen ("/proc/self/uid_map" , "r" );
483+ if (f == NULL ) {
484+ return false;
485+ }
486+
487+ char line [256 ];
488+ bool result = false;
489+
490+ if (fgets (line , sizeof (line ), f ) != NULL ) {
491+ // Parse the uid_map: "target_uid host_uid count"
492+ uint32_t target_uid , host_uid , count ;
493+ if (sscanf (line , "%u %u %u" , & target_uid , & host_uid , & count ) == 3 ) {
494+ // If count is less than full range (4294967295), we're in a user namespace
495+ if (count < 4294967295 ) {
496+ result = try_make_mount_private ();
497+ }
498+ }
499+ }
500+
501+ fclose (f );
502+ return result ;
503+ }
504+
505+ // Try to create user and mount namespaces
506+ bool try_unshare (uid_t uid , gid_t gid , const char * unshare_uid , const char * unshare_gid , bool verbose ) {
507+ uid_t target_uid = uid ;
508+ gid_t target_gid = gid ;
509+
510+ if (unshare_uid != NULL && unshare_uid [0 ] != '\0' ) {
511+ char * endptr ;
512+ long val = strtol (unshare_uid , & endptr , 10 );
513+ if (endptr != unshare_uid && * endptr == '\0' && val >= 0 ) {
514+ target_uid = (uid_t )val ;
515+ }
516+ }
517+
518+ if (unshare_gid != NULL && unshare_gid [0 ] != '\0' ) {
519+ char * endptr ;
520+ long val = strtol (unshare_gid , & endptr , 10 );
521+ if (endptr != unshare_gid && * endptr == '\0' && val >= 0 ) {
522+ target_gid = (gid_t )val ;
523+ }
524+ }
525+
526+ int flags = CLONE_NEWUSER | CLONE_NEWNS ;
527+ if (unshare (flags ) == 0 ) {
528+ // Disable setgroups
529+ FILE * f = fopen ("/proc/self/setgroups" , "w" );
530+ if (f != NULL ) {
531+ fputs ("deny" , f );
532+ fclose (f );
533+ }
534+
535+ // Write uid_map
536+ char uid_map [64 ];
537+ snprintf (uid_map , sizeof (uid_map ), "%u %u 1" , target_uid , uid );
538+ f = fopen ("/proc/self/uid_map" , "w" );
539+ if (f == NULL ) {
540+ fprintf (stderr , "Failed to open /proc/self/uid_map: %s\n" , strerror (errno ));
541+ return false;
542+ }
543+ if (fputs (uid_map , f ) == EOF ) {
544+ fprintf (stderr , "Failed to write uid_map: %s\n" , strerror (errno ));
545+ fclose (f );
546+ return false;
547+ }
548+ fclose (f );
549+
550+ // Write gid_map
551+ char gid_map [64 ];
552+ snprintf (gid_map , sizeof (gid_map ), "%u %u 1" , target_gid , gid );
553+ f = fopen ("/proc/self/gid_map" , "w" );
554+ if (f == NULL ) {
555+ fprintf (stderr , "Failed to open /proc/self/gid_map: %s\n" , strerror (errno ));
556+ return false;
557+ }
558+ if (fputs (gid_map , f ) == EOF ) {
559+ fprintf (stderr , "Failed to write gid_map: %s\n" , strerror (errno ));
560+ fclose (f );
561+ return false;
562+ }
563+ fclose (f );
564+
565+ restore_capabilities ();
566+
567+ if (!try_make_mount_private ()) {
568+ fprintf (stderr , "Warning: failed to make mount private: %s\n" , strerror (errno ));
569+ }
570+
571+ if (verbose ) {
572+ fprintf (stderr , "Successfully created user and mount namespaces\n" );
573+ }
574+
575+ return true;
576+ }
577+
578+ fprintf (stderr , "Failed to create user and mount namespaces: %s\n" , strerror (errno ));
579+ return false;
580+ }
581+
582+ // Check if a binary is setuid root and executable
583+ bool is_suid_exe (const char * path ) {
584+ struct stat sb ;
585+ if (stat (path , & sb ) == -1 ) {
586+ return false;
587+ }
588+ // Check if owned by root, has SUID bit, and is executable
589+ return (sb .st_uid == 0 && (sb .st_mode & S_ISUID ) != 0 && (sb .st_mode & (S_IXUSR | S_IXGRP | S_IXOTH )) != 0 );
590+ }
591+
417592char * find_fusermount (bool verbose ) {
418593 char * fusermount_base = "fusermount" ;
419594
@@ -449,16 +624,9 @@ char* find_fusermount(bool verbose) {
449624 sprintf (fusermount_full_path , "%s/%s" , dir , entry -> d_name );
450625
451626 // Check if the binary is setuid root
452- struct stat sb ;
453- if (stat (fusermount_full_path , & sb ) == -1 ) {
454- perror ("stat" );
455- free (fusermount_full_path );
456- continue ;
457- }
458-
459- if (sb .st_uid != 0 || (sb .st_mode & S_ISUID ) == 0 ) {
627+ if (!is_suid_exe (fusermount_full_path )) {
460628 if (verbose ) {
461- printf ("Not setuid root, skipping...\n" );
629+ printf ("Not setuid root executable , skipping...\n" );
462630 }
463631 free (fusermount_full_path );
464632 continue ;
@@ -514,6 +682,38 @@ char* find_fusermount(bool verbose) {
514682 return NULL ;
515683}
516684
685+ // Check FUSE availability and attempt unshare if needed
686+ bool check_fuse (bool verbose , uid_t uid , gid_t gid , const char * unshare_uid_str , const char * unshare_gid_str , bool * unshare_succeeded ) {
687+ // First check if /dev/fuse is accessible
688+ if (access ("/dev/fuse" , R_OK ) != 0 || access ("/dev/fuse" , W_OK ) != 0 ) {
689+ return false;
690+ }
691+
692+ // If we're root or already in a namespace, we're good
693+ if (uid == 0 || * unshare_succeeded || is_in_user_and_mount_namespace ()) {
694+ return true;
695+ }
696+
697+ // Check if we have a SUID fusermount
698+ char * fusermount = find_fusermount (verbose );
699+ if (fusermount != NULL ) {
700+ free (fusermount );
701+ return true;
702+ }
703+
704+ // No SUID fusermount found, try unshare
705+ if (verbose ) {
706+ fprintf (stderr , "SUID fusermount not found in PATH, trying to unshare...\n" );
707+ }
708+
709+ if (try_unshare (uid , gid , unshare_uid_str , unshare_gid_str , verbose )) {
710+ * unshare_succeeded = true;
711+ return true;
712+ }
713+
714+ return true; // Return true anyway to let FUSE try
715+ }
716+
517717/* Exit status to use when launching an AppImage fails.
518718 * For applications that assign meanings to exit status codes (e.g. rsync),
519719 * we avoid "cluttering" pre-defined exit status codes by using 127 which
@@ -681,9 +881,18 @@ void print_help(const char* appimage_path) {
681881 " --appimage-portable-config Create a portable config folder to use as\n"
682882 " $XDG_CONFIG_HOME\n"
683883 " --appimage-signature Print digital signature embedded in AppImage\n"
884+ " --appimage-unshare Try to use unshare user and mount namespaces\n"
684885 " --appimage-updateinfo[rmation] Print update info embedded in AppImage\n"
685886 " --appimage-version Print version of AppImage runtime\n"
686887 "\n"
888+ "Environment variables:\n"
889+ "\n"
890+ " APPIMAGE_EXTRACT_AND_RUN=1 Temporarily extract and run without FUSE\n"
891+ " APPIMAGE_UNSHARE=1 Try to use unshare user and mount namespaces\n"
892+ " APPIMAGE_UNSHARE_ROOT=1 Map to root (UID 0, GID 0) in user namespace\n"
893+ " APPIMAGE_UNSHARE_UID=<uid> Map to specified UID in user namespace\n"
894+ " APPIMAGE_UNSHARE_GID=<gid> Map to specified GID in user namespace\n"
895+ "\n"
687896 "Portable home:\n"
688897 "\n"
689898 " If you would like the application contained inside this AppImage to store its\n"
@@ -1698,13 +1907,64 @@ int main(int argc, char* argv[]) {
16981907 portable_option (arg , appimage_path , "home" );
16991908 portable_option (arg , appimage_path , "config" );
17001909
1910+ // Check for --appimage-unshare flag
1911+ bool requested_unshare = false;
1912+ if (arg && strcmp (arg , "appimage-unshare" ) == 0 ) {
1913+ requested_unshare = true;
1914+ }
1915+
17011916 // If there is an argument starting with appimage- (but not appimage-mount which is handled further down)
17021917 // then stop here and print an error message
1703- if ((arg && strncmp (arg , "appimage-" , 8 ) == 0 ) && (arg && strcmp (arg , "appimage-mount" ) != 0 )) {
1918+ if ((arg && strncmp (arg , "appimage-" , 8 ) == 0 ) &&
1919+ (arg && strcmp (arg , "appimage-mount" ) != 0 ) &&
1920+ (arg && strcmp (arg , "appimage-unshare" ) != 0 )) {
17041921 fprintf (stderr , "--%s is not yet implemented in version %s\n" , arg , GIT_COMMIT );
17051922 exit (1 );
17061923 }
17071924
1925+ // Get UID and GID for unshare
1926+ uid_t uid = getuid ();
1927+ gid_t gid = getgid ();
1928+
1929+ // Check environment variables for unshare
1930+ const char * unshare_env = getenv ("APPIMAGE_UNSHARE" );
1931+ const char * unshare_root = getenv ("APPIMAGE_UNSHARE_ROOT" );
1932+ const char * unshare_uid_env = getenv ("APPIMAGE_UNSHARE_UID" );
1933+ const char * unshare_gid_env = getenv ("APPIMAGE_UNSHARE_GID" );
1934+
1935+ // Determine if we should attempt unshare
1936+ bool should_unshare = requested_unshare ||
1937+ (unshare_env != NULL && strcmp (unshare_env , "1" ) == 0 ) ||
1938+ (unshare_root != NULL && strcmp (unshare_root , "1" ) == 0 ) ||
1939+ (unshare_uid_env != NULL && unshare_uid_env [0 ] != '\0' ) ||
1940+ (unshare_gid_env != NULL && unshare_gid_env [0 ] != '\0' );
1941+
1942+ // If APPIMAGE_UNSHARE_ROOT is set, map to root
1943+ const char * target_uid_str = unshare_uid_env ;
1944+ const char * target_gid_str = unshare_gid_env ;
1945+ if (unshare_root != NULL && strcmp (unshare_root , "1" ) == 0 ) {
1946+ target_uid_str = "0" ;
1947+ target_gid_str = "0" ;
1948+ }
1949+
1950+ bool unshare_succeeded = false;
1951+
1952+ // Attempt unshare if requested
1953+ if (should_unshare ) {
1954+ unshare_succeeded = try_unshare (uid , gid , target_uid_str , target_gid_str , verbose );
1955+ }
1956+
1957+ // Check FUSE availability and attempt unshare if needed
1958+ if (!check_fuse (verbose , uid , gid , target_uid_str , target_gid_str , & unshare_succeeded )) {
1959+ fprintf (stderr , "FUSE is not available\n" );
1960+ exit (EXIT_EXECERROR );
1961+ }
1962+
1963+ // Restore capabilities if we unshared successfully
1964+ if (unshare_succeeded ) {
1965+ restore_capabilities ();
1966+ }
1967+
17081968 int dir_fd , res ;
17091969
17101970 size_t templen = strlen (temp_base );
0 commit comments