/* * - Virtual Filesystem Root * * Copyright (c) 2009-2018 * AVM GmbH, Berlin, Germany * * License: Free, use with no restriction. */ #ifdef HAVE_CONFIG_H # include #endif #if !defined (__GNUC__) && defined (_AIX) #pragma alloca #endif #ifndef alloca /* Make alloca work the best possible way. */ # ifdef __GNUC__ # define alloca __builtin_alloca # else /* not __GNUC__ */ # if HAVE_ALLOCA_H # include # else /* not __GNUC__ or HAVE_ALLOCA_H */ # ifndef _AIX /* Already did AIX, up at the top. */ char *alloca (); # endif /* not _AIX */ # endif /* not HAVE_ALLOCA_H */ # endif /* not __GNUC__ */ #endif /* not alloca */ #include #include #include #include #ifdef HAVE_SYS_WAIT_H # include #endif #include #include #include #include #include /* * musl did not define values for FTW_ACTIONRETVAL */ #ifndef FTW_CONTINUE #define FTW_CONTINUE 0 #define FTW_STOP 1 #endif #include #include #include #include #include #include #include #include #include #include #include #include #ifdef TIME_WITH_SYS_TIME # include # include #else # ifdef HAVE_SYS_TIME_H # include # else # include # endif #endif #include #ifdef HAVE_MMAP #include #endif #include #include "virtual.h" #include "avm_acl.h" // #include "extern.h" #if 0 #ifdef __GNUC__ static void _fLog(const char *fmt, ...) __attribute__ ((__format__(__printf__, 1, 2))); #endif static void _fLog(const char *fmt, ...) { FILE *fp = fopen("/dev/console", "w"); if (!fp) fp = fopen("/var/tmp/acl.debug", "a"); // NOTE that you have to "chmod 777 /var/tmp" before if (fp) { va_list ap; fprintf(fp, "acl(%d): ", getpid()); va_start(ap, fmt); vfprintf(fp, fmt, ap); va_end(ap); fprintf(fp, "\n"); fclose(fp); } } #define Log(x) _fLog x #else #define Log(x) #endif static char *g_real_root = 0; // absolute linux path that is the virtual "/" static int g_real_root_len; static char *tmpstring(size_t siz) { #define NTMPSTRING 16 static char *tmps[NTMPSTRING] = { 0, }; static int pos = 0; char *p; free(tmps[pos]); p = tmps[pos] = malloc(siz); /* TODO well known memory leak, until program exit(3) */ pos++; if (pos == NTMPSTRING) pos = 0; return p; } #if 0 /* not used */ static int virt_dir_exists(const char *abs_virt_path, const char *dirname) { if (!g_real_root) return 0; size_t len = g_real_root_len + strlen(abs_virt_path) + 1 + strlen(dirname) + 1; char *path = tmpstring(len); if (!path) return 0; snprintf(path, len, "%s%s/%s", g_real_root, abs_virt_path, dirname); struct stat sb; if (stat(path, &sb)) return 0; return sb.st_mode & S_IFDIR; } #endif /* 0 */ static char *virtual_to_real_path(const char *path) { char *real; size_t len; if (!g_real_root) return NULL; if (*path == '/' && strcmp(g_real_root, "/")) { // absolute client path -> change virt. to real root len = strlen(path) + g_real_root_len + 1; real = tmpstring(len); if (!real) return NULL; if (strcmp(path, "/")) { snprintf(real, len, "%s%s", g_real_root, path); } else { snprintf(real, len, "%s", g_real_root); } } else { // relative virtual paths are autom. // relative real paths len = strlen(path) + 1; real = tmpstring(len); if (!real) return NULL; snprintf(real, len, "%s", path); } return real; } // change the real path to a virtual client path void virtual_make_from_real_path(char **ppath) { if (!g_real_root) return; if (!strcmp(g_real_root, "/")) return; if (*ppath == strstr(*ppath, g_real_root)) { char *p; size_t diff = strlen(*ppath) - g_real_root_len; size_t len; if (diff) { len = diff + 1; p = tmpstring(len); if (!p) return; snprintf(p, len, "%s", (*ppath) + g_real_root_len); } else { len = 2; p = tmpstring(len); if (!p) return; snprintf(p, len, "/"); } *ppath = p; } } /* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */ static int IsAllowed(const char *path, char **preal_path, int *pwrite_access) { *preal_path = virtual_to_real_path(path); if (*preal_path) { if (acl_is_allowed_path(*preal_path, pwrite_access)) { Log(("%s %s real_path=%s is allwed write_access=%d", __FILE__, __FUNCTION__, *preal_path, *pwrite_access)); return 1; } else { Log(("%s %s not allowed to access real_path=%s", __FILE__, __FUNCTION__, *preal_path)); } } else { Log(("%s %s no real_path for %s", __FILE__, __FUNCTION__, path)); } errno = EACCES; return 0; } int virtual_is_write_access(const char *path, char **preal_path) { int write_access; if (!IsAllowed(path, preal_path, &write_access)) { Log(("virtual_is_write_access failed path=%s", path)); return 0; } Log(("virtual_is_write_access path=%s real=%s write_access=%d", path, *preal_path, write_access)); if (!write_access) errno = EACCES; return write_access; } int virtual_is_read_access(const char *path, char **preal_path) { int write_access; return IsAllowed(path, preal_path, &write_access); } /* ------------------------------------------------------------------------ */ void virtual_set_root(const char *real_root) { free(g_real_root); if (real_root) g_real_root = strdup(real_root); else g_real_root = 0; if (g_real_root) g_real_root_len = strlen(g_real_root); } FILE *virtual_fopen(const char *fn, const char *mode) { int is_write = 1; char *real; int write_access; int allowed = IsAllowed(fn, &real, &write_access); if (mode[0] == 'r' && mode[1] != '+') is_write = 0; if (!is_write) { FILE *fp = 0; if (real) { fp = fopen(real, mode); if (!fp) return 0; // use original errno } if (!allowed || !real) { if (fp) fclose(fp); errno = EACCES; return 0; } return fp; } else { if (!allowed || !write_access) { errno = EACCES; return 0; } return fopen(real, mode); } } int virtual_mkdir(const char *dir, mode_t mode) { char *real; if (!virtual_is_write_access(dir, &real)) { return -1; } return mkdir(real, mode); } int virtual_rename(const char *from, const char *to) { char *real_from; char *real_to; if (!virtual_is_write_access(from, &real_from)) { return -1; } if (!virtual_is_write_access(to, &real_to)) { return -1; } return rename(real_from, real_to); } int virtual_stat(const char *path, struct stat *buf) { char *real_path; struct stat sb; int write_access; int allowed = IsAllowed(path, &real_path, &write_access); if (real_path) { int ret = stat(real_path, &sb); if (ret) return ret; // use original errno } if (!allowed || !real_path) { errno = EACCES; return -1; } if (!write_access) sb.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH); memcpy(buf, &sb, sizeof(struct stat)); return 0; } int virtual_rmdir(const char *pathname) { char *real_path; if (!virtual_is_write_access(pathname, &real_path)) { return -1; } return rmdir(real_path); } /** * @brief Try to delete everything that comes by, throws an error if something goes wrong */ static int nftw_delete_everything_cb (const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) { int write_access = 0; int ret = 0; // check if specific path is accessable ret = acl_is_allowed_path(fpath, &write_access); if (ret != 1 || write_access != 1) { return FTW_STOP; } // try to delete file or dir switch (typeflag) { case FTW_F: // file if (unlink(fpath)) { return FTW_STOP; } break; case FTW_D: // dir (not called for nftw with FTW_DEPTH) if (rmdir(fpath)) { return FTW_STOP; } break; case FTW_DNR: // dir, not readable return FTW_STOP; case FTW_DP: // dir if (rmdir(fpath)) { return FTW_STOP; } break; case FTW_NS: // can be seen, but stat is not possible return FTW_STOP; case FTW_SL: // symbolic link if (unlink(fpath)) { return FTW_STOP; } break; case FTW_SLN: // symbolic link to nonexisting thing if (unlink(fpath)) { return FTW_STOP; } break; default: return FTW_STOP; } return FTW_CONTINUE; } int virtual_rmdir_recursive(const char *pathname) { int ret; char *real_path; if (!virtual_is_write_access(pathname, &real_path)) { errno = EACCES; return -1; } // check if directory struct stat stat_buf = {0}; ret = stat(real_path, &stat_buf); if (ret || !S_ISDIR(stat_buf.st_mode)) { if (ret) { errno = EACCES; } else { errno = ENOTDIR; } return -1; } ret = nftw(real_path, nftw_delete_everything_cb, 10, FTW_DEPTH | FTW_PHYS); if (ret != 0) { errno = EACCES; return -2; } errno = 0; return ret; } int virtual_unlink(const char *pathname) { char *real_path; if (!virtual_is_write_access(pathname, &real_path)) { return -1; } return unlink(real_path); } int virtual_chdir(const char *path) { char *real_path; if (!virtual_is_read_access(path, &real_path)) return -1; return chdir(real_path); } DIR *virtual_opendir(const char *name) { char *real_path; int allowed = virtual_is_read_access(name, &real_path); DIR *d = 0; if (real_path) { d = opendir(real_path); if (!d) return 0; // use original errno } if (!allowed || !real_path) { if (d) closedir(d); errno = EACCES; return 0; } return d; } int virtual_chmod(const char *path, mode_t mode) { char *real_path; if (!virtual_is_write_access(path, &real_path)) { return -1; } #if 0 // never allow chmod of a mountpoint if (acl_is_mountpoint(real_path)) { errno = EACCES; return -1; } #endif uid_t uid = geteuid(); seteuid(0); int ret = chmod(real_path, mode); seteuid(uid); return ret; }