/** * Resolve bindings (if any) in @guest_path and copy the translated * path into @host_path. Also, this function checks that a non-final * component is either a directory (returned value is 0) or a symlink * (returned value is 1), otherwise it returns -errno (-ENOENT or * -ENOTDIR). */ static inline int substitute_binding_stat(Tracee *tracee, Finality finality, unsigned int recursion_level, const char guest_path[PATH_MAX], char host_path[PATH_MAX]) { struct stat statl; int status; strcpy(host_path, guest_path); status = substitute_binding(tracee, GUEST, host_path); if (status < 0) return status; /* Don't notify extensions during the initialization of a binding. */ if (tracee->glue_type == 0) { status = notify_extensions(tracee, HOST_PATH, (intptr_t)host_path, IS_FINAL(finality) && recursion_level == 0); if (status < 0) return status; } statl.st_mode = 0; status = lstat(host_path, &statl); /* Build the glue between the hostfs and the guestfs during * the initialization of a binding. */ if (status < 0 && tracee->glue_type != 0) { statl.st_mode = build_glue(tracee, guest_path, host_path, finality); if (statl.st_mode == 0) status = -1; } /* Return an error if a non-final component isn't a * directory nor a symlink. The error is "No such * file or directory" if this component doesn't exist, * otherwise the error is "Not a directory". */ if (!IS_FINAL(finality) && !S_ISDIR(statl.st_mode) && !S_ISLNK(statl.st_mode)) return (status < 0 ? -ENOENT : -ENOTDIR); return (S_ISLNK(statl.st_mode) ? 1 : 0); }
/** * Copy in @result the equivalent of "@tracee->root + canon(@dir_fd + * @user_path)". If @user_path is not absolute then it is relative to * the directory referred by the descriptor @dir_fd (AT_FDCWD is for * the current working directory). See the documentation of * canonicalize() for the meaning of @deref_final. This function * returns -errno if an error occured, otherwise 0. */ int translate_path(Tracee *tracee, char result[PATH_MAX], int dir_fd, const char *user_path, bool deref_final) { char guest_path[PATH_MAX]; int status; /* Use "/" as the base if it is an absolute guest path. */ if (user_path[0] == '/') { strcpy(result, "/"); } /* It is relative to a directory referred by a descriptor, see * openat(2) for details. */ else if (dir_fd != AT_FDCWD) { /* /proc/@tracee->pid/fd/@dir_fd -> result. */ status = readlink_proc_pid_fd(tracee->pid, dir_fd, result); if (status < 0) return status; /* Named file descriptors may reference special * objects like pipes, sockets, inodes, ... Such * objects do not belong to the file-system. */ if (result[0] != '/') return -ENOTDIR; /* Remove the leading "root" part of the base * (required!). */ status = detranslate_path(tracee, result, NULL); if (status < 0) return status; } /* It is relative to the current working directory. */ else { status = getcwd2(tracee, result); if (status < 0) return status; } VERBOSE(tracee, 2, "pid %d: translate(\"%s\" + \"%s\")", tracee != NULL ? tracee->pid : 0, result, user_path); status = notify_extensions(tracee, GUEST_PATH, (intptr_t) result, (intptr_t) user_path); if (status < 0) return status; if (status > 0) goto skip; /* So far "result" was used as a base path, it's time to join * it to the user path. */ assert(result[0] == '/'); status = join_paths(2, guest_path, result, user_path); if (status < 0) return status; strcpy(result, "/"); /* Canonicalize regarding the new root. */ status = canonicalize(tracee, guest_path, deref_final, result, 0); if (status < 0) return status; /* Final binding substitution to convert "result" into a host * path, since canonicalize() works from the guest * point-of-view. */ status = substitute_binding(tracee, GUEST, result); if (status < 0) return status; skip: VERBOSE(tracee, 2, "pid %d: -> \"%s\"", tracee != NULL ? tracee->pid : 0, result); return 0; }
/** * Remove/substitute the leading part of a "translated" @path. It * returns 0 if no transformation is required (ie. symmetric binding), * otherwise it returns the size in bytes of the updated @path, * including the end-of-string terminator. On error it returns * -errno. */ int detranslate_path(Tracee *tracee, char path[PATH_MAX], const char t_referrer[PATH_MAX]) { size_t prefix_length; ssize_t new_length; bool sanity_check; bool follow_binding; /* Sanity check. */ if (strnlen(path, PATH_MAX) >= PATH_MAX) return -ENAMETOOLONG; /* Don't try to detranslate relative paths (typically the * target of a relative symbolic link). */ if (path[0] != '/') return 0; /* Is it a symlink? */ if (t_referrer != NULL) { Comparison comparison; sanity_check = false; follow_binding = false; /* In some cases bindings have to be resolved. */ comparison = compare_paths("/proc", t_referrer); if (comparison == PATH1_IS_PREFIX) { /* Some links in "/proc" are generated * dynamically by the kernel. PRoot has to * emulate some of them. */ char proc_path[PATH_MAX]; strcpy(proc_path, path); new_length = readlink_proc2(tracee, proc_path, t_referrer); if (new_length < 0) return new_length; if (new_length != 0) { strcpy(path, proc_path); return new_length + 1; } /* Always resolve bindings for symlinks in * "/proc", they always point to the emulated * file-system namespace by design. */ follow_binding = true; } else if (!belongs_to_guestfs(tracee, t_referrer)) { const char *binding_referree; const char *binding_referrer; binding_referree = get_path_binding(tracee, HOST, path); binding_referrer = get_path_binding(tracee, HOST, t_referrer); assert(binding_referrer != NULL); /* Resolve bindings for symlinks that belong * to a binding and point to the same binding. * For example, if "-b /lib:/foo" is specified * and the symlink "/lib/a -> /lib/b" exists * in the host rootfs namespace, then it * should appear as "/foo/a -> /foo/b" in the * guest rootfs namespace for consistency * reasons. */ if (binding_referree != NULL) { comparison = compare_paths(binding_referree, binding_referrer); follow_binding = (comparison == PATHS_ARE_EQUAL); } } } else { sanity_check = true; follow_binding = true; } if (follow_binding) { switch (substitute_binding(tracee, HOST, path)) { case 0: return 0; case 1: return strlen(path) + 1; default: break; } } switch (compare_paths(get_root(tracee), path)) { case PATH1_IS_PREFIX: /* Remove the leading part, that is, the "root". */ prefix_length = strlen(get_root(tracee)); /* Special case when path to the guest rootfs == "/". */ if (prefix_length == 1) prefix_length = 0; new_length = strlen(path) - prefix_length; memmove(path, path + prefix_length, new_length); path[new_length] = '\0'; break; case PATHS_ARE_EQUAL: /* Special case when path == root. */ new_length = 1; strcpy(path, "/"); break; default: /* Ensure the path is within the new root. */ if (sanity_check) return -EPERM; else return 0; } return new_length + 1; }
/** * Copy in @host_path the equivalent of "@tracee->root + * canonicalize(@dir_fd + @guest_path)". If @guest_path is not * absolute then it is relative to the directory referred by the * descriptor @dir_fd (AT_FDCWD is for the current working directory). * See the documentation of canonicalize() for the meaning of * @deref_final. This function returns -errno if an error occured, * otherwise 0. */ int translate_path(Tracee *tracee, char host_path[PATH_MAX], int dir_fd, const char *guest_path, bool deref_final) { int status; /* Use "/" as the base if it is an absolute guest path. */ if (guest_path[0] == '/') { strcpy(host_path, "/"); } /* It is relative to the current working directory or to a * directory referred by a descriptors, see openat(2) for * details. */ else if (dir_fd == AT_FDCWD) { status = getcwd2(tracee, host_path); if (status < 0) return status; } else { struct stat statl; /* /proc/@tracee->pid/fd/@dir_fd -> host_path. */ status = readlink_proc_pid_fd(tracee->pid, dir_fd, host_path); if (status < 0) return status; /* Ensure it points to a directory. */ status = stat(host_path, &statl); if (status < 0) return -errno; if (!S_ISDIR(statl.st_mode)) return -ENOTDIR; /* Remove the leading "root" part of the base * (required!). */ status = detranslate_path(tracee, host_path, NULL); if (status < 0) return status; } VERBOSE(tracee, 2, "pid %d: translate(\"%s\" + \"%s\")", tracee != NULL ? tracee->pid : 0, host_path, guest_path); status = notify_extensions(tracee, GUEST_PATH, (intptr_t)host_path, (intptr_t)guest_path); if (status < 0) return status; if (status > 0) goto skip; /* Canonicalize regarding the new root. */ status = canonicalize(tracee, guest_path, deref_final, host_path, 0); if (status < 0) return status; /* Final binding substitution to convert "host_path" into a host * path, since canonicalize() works from the guest * point-of-view. */ status = substitute_binding(tracee, GUEST, host_path); if (status < 0) return status; skip: VERBOSE(tracee, 2, "pid %d: -> \"%s\"", tracee != NULL ? tracee->pid : 0, host_path); return 0; }