diff --git a/src/ioslaves/file/file_p.h b/src/ioslaves/file/file_p.h --- a/src/ioslaves/file/file_p.h +++ b/src/ioslaves/file/file_p.h @@ -24,6 +24,7 @@ #include enum ActionType { + UNKNOWN, CHMOD = 1, CHOWN, DEL, diff --git a/src/ioslaves/file/kauth/filehelper.cpp b/src/ioslaves/file/kauth/filehelper.cpp --- a/src/ioslaves/file/kauth/filehelper.cpp +++ b/src/ioslaves/file/kauth/filehelper.cpp @@ -21,14 +21,38 @@ #include #include #include -#include -#include #include +#include +#include +#include +#include #include "filehelper.h" #include "fdsender.h" #include "../file_p.h" +struct OwnerId { + uid_t uid; + gid_t gid; +}; + +static ActionType intToActionType(int action) +{ + switch (action) { + case 1: return CHMOD; + case 2: return CHOWN; + case 3: return DEL; + case 4: return MKDIR; + case 5: return OPEN; + case 6: return OPENDIR; + case 7: return RENAME; + case 8: return RMDIR; + case 9: return SYMLINK; + case 10: return UTIME; + default: return UNKNOWN; + } +} + static bool sendFileDescriptor(int fd, const char *socketPath) { FdSender fdSender(socketPath); @@ -38,102 +62,175 @@ return false; } +static OwnerId *getNecessaryPrivileges(ActionType action, int parent_fd, const QByteArray &baseName) +{ + + //for chmod, chown, utime, drop privileges to that of owner of the target file/dir + //for other actions use owner of the parent directory. + int target_fd = -1; + if (action != CHMOD && action != CHOWN && action != UTIME) { + target_fd = parent_fd; + } else { + target_fd = openat(parent_fd, baseName.data(), O_NOFOLLOW); + } + + struct stat buf = {0}; + if (fstat(target_fd, &buf) == -1) { + return nullptr; + } + + return new OwnerId{buf.st_uid, buf.st_gid}; +} + +static bool dropPrivileges(OwnerId *o) +{ + if (!o) { + return false; + } + + uid_t newuid = o->uid; + gid_t newgid = o->gid; + delete o; + + if (newuid == 0 && newgid == 0) { + return true; + } + + //drop ancillary groups first because it requires root privileges. + if (setgroups(1, &newgid) == -1) { + return false; + } + //change real, effective, saved gid and uid. + if (setegid(newgid) == -1 || seteuid(newuid) == -1) { + return false; + } + + return true; +} + ActionReply FileHelper::exec(const QVariantMap &args) { ActionReply reply; QByteArray data = args["arguments"].toByteArray(); QDataStream in(data); - int action; + int act; QVariant arg1, arg2, arg3, arg4; - in >> action >> arg1 >> arg2 >> arg3 >> arg4; - - // the path of an existing or a new file/dir upon which the method will operate - const QByteArray path = arg1.toByteArray(); + in >> act >> arg1 >> arg2 >> arg3 >> arg4; // act=action, arg1=source file, arg$n=dest file, mode, uid, gid, etc. + ActionType action = intToActionType(act); + QByteArray tempPath1, tempPath2; + tempPath1 = tempPath2 = arg1.toByteArray(); + const QByteArray parentDir = dirname(tempPath1.data()); + const QByteArray baseName = basename(tempPath2.data()); + int parent_fd = open(parentDir.data(), O_DIRECTORY | O_PATH | O_NOFOLLOW); - switch(action) { - case CHMOD: { - int mode = arg2.toInt(); - if (chmod(path.data(), mode) == 0) { - return reply; - } - break; + int new_parent_fd = -1; + QByteArray newParentDir, newBaseName; + if (action == RENAME) { + tempPath1 = tempPath2 = arg2.toByteArray(); + newParentDir = dirname(tempPath1.data()); + newBaseName = basename(tempPath2.data()); + new_parent_fd = open(newParentDir.constData(), O_DIRECTORY | O_PATH | O_NOFOLLOW); } - case CHOWN: { - int uid = arg2.toInt(); - int gid = arg3.toInt(); - if (chown(path.data(), uid, gid) == 0) { - return reply; - } - break; + + + OwnerId *oid = getNecessaryPrivileges(action, parent_fd, baseName); + if (!dropPrivileges(oid)) { + reply.setError(errno); } - case DEL: { - if (unlink(path.data()) == 0) { - return reply; + + switch(action) { + case CHMOD: { + int mode = arg2.toInt(); + if (fchmodat(parent_fd, baseName.data(), mode, AT_SYMLINK_NOFOLLOW) == -1) { + reply.setError(errno); + } + break; } - break; - } - case MKDIR: { - if (mkdir(path.data(), 0777) == 0) { - return reply; + + case CHOWN: { + int uid = arg2.toInt(); + int gid = arg3.toInt(); + if (fchownat(parent_fd, baseName.data(), uid, gid, AT_SYMLINK_NOFOLLOW) == -1) { + reply.setError(errno); + } + break; } - break; - } - case OPEN: { - int oflags = arg2.toInt(); - int mode = arg3.toInt(); - int fd = open(path.data(), oflags, mode); - bool success = (fd != -1) && sendFileDescriptor(fd, arg4.toByteArray().constData()); - close(fd); - if (success) { - return reply; + + case DEL: + case RMDIR: { + int flags = 0; + if (action == RMDIR) { + flags |= AT_REMOVEDIR; + } + if (unlinkat(parent_fd, baseName.data(), flags) == -1) { + reply.setError(errno); + } + break; } - break; - } - case OPENDIR: { - DIR *dp = opendir(path.data()); - bool success = false; - if (dp) { - int fd = dirfd(dp); - success = (fd != -1) && sendFileDescriptor(fd, arg4.toByteArray().constData()); - closedir(dp); - if (success) { - return reply; + + case MKDIR: { + int mode = arg2.toInt(); + if (mkdirat(parent_fd, baseName.data(), mode) == -1) { + reply.setError(errno); } + break; } - break; - } - case RENAME: { - const QByteArray newName = arg2.toByteArray(); - if (rename(path.data(), newName.data()) == 0) { - return reply; + + case OPEN: + case OPENDIR: { + int oflags = arg2.toInt(); + int mode = arg3.toInt(); + int extraFlag = O_NOFOLLOW; + if (action == OPENDIR) { + extraFlag |= O_DIRECTORY; + } + int fd = openat(parent_fd, baseName.data(), oflags | extraFlag, mode); + bool success = (fd != -1) && sendFileDescriptor(fd, arg4.toByteArray().constData()); + close(fd); + if (!success) { + reply.setError(errno); + } + break; } - break; - } - case RMDIR: { - if (rmdir(path.data()) == 0) { - return reply; + + case RENAME: { + if (renameat(parent_fd, baseName.data(), new_parent_fd, newBaseName.constData()) == -1) { + oid = getNecessaryPrivileges(action, new_parent_fd, newBaseName); + seteuid(0); + if (!dropPrivileges(oid) || renameat(parent_fd, baseName.data(), new_parent_fd, newBaseName.constData()) == -1) { + reply.setError(errno); + } + } + break; } - break; - } - case SYMLINK: { - const QByteArray target = arg2.toByteArray(); - if (symlink(target.data(), path.data()) == 0) { - return reply; + + case SYMLINK: { + const QByteArray target = arg2.toByteArray(); + if (symlinkat(target.data(), parent_fd, baseName.data()) == -1) { + return reply; + } + break; } - break; - } - case UTIME: { - utimbuf ut; - ut.actime = arg2.toULongLong(); - ut.modtime = arg3.toULongLong(); - if (utime(path.data(), &ut) == 0) { - return reply; + + case UTIME: { + timespec times[2]; + time_t actime = arg2.toULongLong(); + time_t modtime = arg3.toULongLong(); + times[0].tv_sec = actime * 1000; + times[0].tv_nsec = actime / 1000; + times[1].tv_sec = modtime * 1000; + times[1].tv_nsec = modtime / 1000; + if (utimensat(parent_fd, baseName.data(), times, AT_SYMLINK_NOFOLLOW) == -1) { + reply.setError(errno); + } + break; } - break; + + default: break; } - }; - reply.setError(errno ? errno : -1); + close(parent_fd); + close(new_parent_fd); return reply; }