#ifndef _GNU_SOURCE # define _GNU_SOURCE //asprintf #endif #include "config.h" #include "perms.h" #include "support/nls-enable.h" #include #include #ifndef XATTR_SELINUX_SUFFIX # define XATTR_SELINUX_SUFFIX "selinux" #endif #ifndef XATTR_CAPS_SUFFIX # define XATTR_CAPS_SUFFIX "capability" #endif struct inode_params { ext2_filsys fs; char *path; char *filename; char *src_dir; char *target_out; char *mountpoint; fs_config_f fs_config_func; struct selabel_handle *sehnd; time_t fixed_time; const struct ugid_map* uid_map; const struct ugid_map* gid_map; errcode_t error; }; static errcode_t ino_add_xattr(ext2_filsys fs, ext2_ino_t ino, const char *name, const void *value, int value_len) { errcode_t retval, close_retval; struct ext2_xattr_handle *xhandle; retval = ext2fs_xattrs_open(fs, ino, &xhandle); if (retval) { com_err(__func__, retval, _("while opening inode %u"), ino); return retval; } retval = ext2fs_xattrs_read(xhandle); if (retval) { com_err(__func__, retval, _("while reading xattrs of inode %u"), ino); goto xattrs_close; } retval = ext2fs_xattr_set(xhandle, name, value, value_len); if (retval) { com_err(__func__, retval, _("while setting xattrs of inode %u"), ino); goto xattrs_close; } xattrs_close: close_retval = ext2fs_xattrs_close(&xhandle); if (close_retval) { com_err(__func__, close_retval, _("while closing xattrs of inode %u"), ino); return retval ? retval : close_retval; } return retval; } static errcode_t set_selinux_xattr(ext2_filsys fs, ext2_ino_t ino, struct inode_params *params) { errcode_t retval; char *secontext = NULL; struct ext2_inode inode; if (params->sehnd == NULL) return 0; retval = ext2fs_read_inode(fs, ino, &inode); if (retval) { com_err(__func__, retval, _("while reading inode %u"), ino); return retval; } retval = selabel_lookup(params->sehnd, &secontext, params->filename, inode.i_mode); if (retval < 0) { int saved_errno = errno; com_err(__func__, errno, _("searching for label \"%s\""), params->filename); return saved_errno; } retval = ino_add_xattr(fs, ino, "security." XATTR_SELINUX_SUFFIX, secontext, strlen(secontext) + 1); freecon(secontext); return retval; } /* * Returns mapped UID/GID if there is a corresponding entry in |mapping|. * Otherwise |id| as is. */ static unsigned int resolve_ugid(const struct ugid_map* mapping, unsigned int id) { size_t i; for (i = 0; i < mapping->size; ++i) { const struct ugid_map_entry* entry = &mapping->entries[i]; if (entry->parent_id <= id && id < entry->parent_id + entry->length) { return id + entry->child_id - entry->parent_id; } } /* No entry is found. */ return id; } static errcode_t set_perms_and_caps(ext2_filsys fs, ext2_ino_t ino, struct inode_params *params) { errcode_t retval; uint64_t capabilities = 0; struct ext2_inode inode; struct vfs_cap_data cap_data; unsigned int uid = 0, gid = 0, imode = 0; retval = ext2fs_read_inode(fs, ino, &inode); if (retval) { com_err(__func__, retval, _("while reading inode %u"), ino); return retval; } /* Permissions */ if (params->fs_config_func != NULL) { const char *filename = params->filename; if (strcmp(filename, params->mountpoint) == 0) { /* The root of the filesystem needs to be an empty string. */ filename = ""; } params->fs_config_func(filename, S_ISDIR(inode.i_mode), params->target_out, &uid, &gid, &imode, &capabilities); uid = resolve_ugid(params->uid_map, uid); gid = resolve_ugid(params->gid_map, gid); inode.i_uid = (__u16) uid; inode.i_gid = (__u16) gid; ext2fs_set_i_uid_high(inode, (__u16) (uid >> 16)); ext2fs_set_i_gid_high(inode, (__u16) (gid >> 16)); inode.i_mode = (inode.i_mode & S_IFMT) | (imode & 0xffff); retval = ext2fs_write_inode(fs, ino, &inode); if (retval) { com_err(__func__, retval, _("while writing inode %u"), ino); return retval; } } /* Capabilities */ if (!capabilities) return 0; memset(&cap_data, 0, sizeof(cap_data)); cap_data.magic_etc = VFS_CAP_REVISION_2 | VFS_CAP_FLAGS_EFFECTIVE; cap_data.data[0].permitted = (uint32_t) (capabilities & 0xffffffff); cap_data.data[1].permitted = (uint32_t) (capabilities >> 32); return ino_add_xattr(fs, ino, "security." XATTR_CAPS_SUFFIX, &cap_data, sizeof(cap_data)); } static errcode_t set_timestamp(ext2_filsys fs, ext2_ino_t ino, struct inode_params *params) { errcode_t retval; struct ext2_inode inode; struct stat stat; char *src_filename = NULL; retval = ext2fs_read_inode(fs, ino, &inode); if (retval) { com_err(__func__, retval, _("while reading inode %u"), ino); return retval; } if (params->fixed_time == -1 && params->src_dir) { /* replace mountpoint from filename with src_dir */ if (asprintf(&src_filename, "%s/%s", params->src_dir, params->filename + strlen(params->mountpoint)) < 0) { return -ENOMEM; } retval = lstat(src_filename, &stat); if (retval < 0) { com_err(__func__, errno, _("while lstat file %s"), src_filename); goto end; } inode.i_atime = inode.i_ctime = inode.i_mtime = stat.st_mtime; } else { inode.i_atime = inode.i_ctime = inode.i_mtime = params->fixed_time; } retval = ext2fs_write_inode(fs, ino, &inode); if (retval) { com_err(__func__, retval, _("while writing inode %u"), ino); goto end; } end: free(src_filename); return retval; } static int is_dir(ext2_filsys fs, ext2_ino_t ino) { struct ext2_inode inode; if (ext2fs_read_inode(fs, ino, &inode)) return 0; return S_ISDIR(inode.i_mode); } static errcode_t androidify_inode(ext2_filsys fs, ext2_ino_t ino, struct inode_params *params) { errcode_t retval; retval = set_timestamp(fs, ino, params); if (retval) return retval; retval = set_selinux_xattr(fs, ino, params); if (retval) return retval; return set_perms_and_caps(fs, ino, params); } static int walk_dir(ext2_ino_t dir EXT2FS_ATTR((unused)), int flags EXT2FS_ATTR((unused)), struct ext2_dir_entry *de, int offset EXT2FS_ATTR((unused)), int blocksize EXT2FS_ATTR((unused)), char *buf EXT2FS_ATTR((unused)), void *priv_data) { __u16 name_len; errcode_t retval; struct inode_params *params = (struct inode_params *)priv_data; name_len = de->name_len & 0xff; if (!strncmp(de->name, ".", name_len) || (!strncmp(de->name, "..", name_len))) return 0; if (asprintf(¶ms->filename, "%s/%.*s", params->path, name_len, de->name) < 0) { params->error = ENOMEM; return -ENOMEM; } if (!strncmp(de->name, "lost+found", 10)) { retval = set_selinux_xattr(params->fs, de->inode, params); if (retval) goto end; } else { retval = androidify_inode(params->fs, de->inode, params); if (retval) goto end; if (is_dir(params->fs, de->inode)) { char *cur_path = params->path; char *cur_filename = params->filename; params->path = params->filename; retval = ext2fs_dir_iterate2(params->fs, de->inode, 0, NULL, walk_dir, params); if (retval) goto end; params->path = cur_path; params->filename = cur_filename; } } end: free(params->filename); params->error |= retval; return retval; } errcode_t __android_configure_fs(ext2_filsys fs, char *src_dir, char *target_out, char *mountpoint, fs_config_f fs_config_func, struct selabel_handle *sehnd, time_t fixed_time, const struct ugid_map* uid_map, const struct ugid_map* gid_map) { errcode_t retval; struct inode_params params = { .fs = fs, .src_dir = src_dir, .target_out = target_out, .fs_config_func = fs_config_func, .sehnd = sehnd, .fixed_time = fixed_time, .path = mountpoint, .filename = mountpoint, .mountpoint = mountpoint, .uid_map = uid_map, .gid_map = gid_map, .error = 0 }; /* walk_dir will add the "/". Don't add it twice. */ if (strlen(mountpoint) == 1 && mountpoint[0] == '/') params.path = ""; retval = androidify_inode(fs, EXT2_ROOT_INO, ¶ms); if (retval) return retval; retval = ext2fs_dir_iterate2(fs, EXT2_ROOT_INO, 0, NULL, walk_dir, ¶ms); if (retval) return retval; return params.error; } errcode_t android_configure_fs(ext2_filsys fs, char *src_dir, char *target_out, char *mountpoint, struct selinux_opt *seopts EXT2FS_ATTR((unused)), unsigned int nopt EXT2FS_ATTR((unused)), char *fs_config_file, time_t fixed_time, const struct ugid_map* uid_map, const struct ugid_map* gid_map) { errcode_t retval; fs_config_f fs_config_func = NULL; struct selabel_handle *sehnd = NULL; /* Retrieve file contexts */ #if !defined(__ANDROID__) if (nopt > 0) { sehnd = selabel_open(SELABEL_CTX_FILE, seopts, nopt); if (!sehnd) { int saved_errno = errno; com_err(__func__, errno, _("while opening file contexts \"%s\""), seopts[0].value); return saved_errno; } } #else sehnd = selinux_android_file_context_handle(); if (!sehnd) { com_err(__func__, EINVAL, _("while opening android file_contexts")); return EINVAL; } #endif /* Load the FS config */ if (fs_config_file) { retval = load_canned_fs_config(fs_config_file); if (retval < 0) { com_err(__func__, retval, _("while loading fs_config \"%s\""), fs_config_file); return retval; } fs_config_func = canned_fs_config; } else if (mountpoint) fs_config_func = fs_config; return __android_configure_fs(fs, src_dir, target_out, mountpoint, fs_config_func, sehnd, fixed_time, uid_map, gid_map); }