1 /*
2 SPDX-FileCopyrightText: 2017 Chinmoy Ranjan Pradhan <chinmoyrp65@gmail.com>
3
4 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5 */
6
7 #include "filehelper.h"
8
9 #include <cerrno>
10 #include <fcntl.h>
11 #include <grp.h>
12 #include <libgen.h>
13 #include <sys/stat.h>
14 #include <sys/time.h>
15 #include <sys/types.h>
16 #include <unistd.h>
17 #include <utime.h>
18
19 #include "../file_p.h"
20 #include "fdsender.h"
21
22 #ifndef O_PATH
23 #define O_PATH O_RDONLY
24 #endif
25
26 struct Privilege {
27 uid_t uid;
28 gid_t gid;
29 };
30
intToActionType(int action)31 static ActionType intToActionType(int action)
32 {
33 switch (action) {
34 case 1:
35 return CHMOD;
36 case 2:
37 return CHOWN;
38 case 3:
39 return DEL;
40 case 4:
41 return MKDIR;
42 case 5:
43 return OPEN;
44 case 6:
45 return OPENDIR;
46 case 7:
47 return RENAME;
48 case 8:
49 return RMDIR;
50 case 9:
51 return SYMLINK;
52 case 10:
53 return UTIME;
54 default:
55 return UNKNOWN;
56 }
57 }
58
sendFileDescriptor(int fd,const char * socketPath)59 static bool sendFileDescriptor(int fd, const char *socketPath)
60 {
61 FdSender fdSender(socketPath);
62 if (fdSender.isConnected() && fdSender.sendFileDescriptor(fd)) {
63 return true;
64 }
65 return false;
66 }
67
getTargetPrivilege(int target_fd)68 static Privilege *getTargetPrivilege(int target_fd)
69 {
70 struct stat buf;
71 if (fstat(target_fd, &buf) == -1) {
72 return nullptr;
73 }
74 return new Privilege{buf.st_uid, buf.st_gid};
75 }
76
dropPrivilege(Privilege * p)77 static bool dropPrivilege(Privilege *p)
78 {
79 if (!p) {
80 return false;
81 }
82
83 uid_t newuid = p->uid;
84 gid_t newgid = p->gid;
85
86 // drop ancillary groups first because it requires root privileges.
87 if (setgroups(1, &newgid) == -1) {
88 return false;
89 }
90 // change effective gid and uid.
91 if (setegid(newgid) == -1 || seteuid(newuid) == -1) {
92 return false;
93 }
94
95 return true;
96 }
97
gainPrivilege(Privilege * p)98 static void gainPrivilege(Privilege *p)
99 {
100 if (!p) {
101 return;
102 }
103
104 uid_t olduid = p->uid;
105 gid_t oldgid = p->gid;
106
107 seteuid(olduid);
108 setegid(oldgid);
109 setgroups(1, &oldgid);
110 }
111
exec(const QVariantMap & args)112 ActionReply FileHelper::exec(const QVariantMap &args)
113 {
114 ActionReply reply;
115 QByteArray data = args[QStringLiteral("arguments")].toByteArray();
116 QDataStream in(data);
117 int act;
118 QVariant arg1;
119 QVariant arg2;
120 QVariant arg3;
121 QVariant arg4;
122 in >> act >> arg1 >> arg2 >> arg3 >> arg4; // act=action, arg1=source file, arg$n=dest file, mode, uid, gid, etc.
123 ActionType action = intToActionType(act);
124
125 // chown requires privilege (CAP_CHOWN) to change user but the group can be changed without it.
126 // It's much simpler to do it in one privileged call.
127 if (action == CHOWN) {
128 if (lchown(arg1.toByteArray().constData(), arg2.toInt(), arg3.toInt()) == -1) {
129 reply.setError(errno);
130 }
131 return reply;
132 }
133
134 QByteArray tempPath1;
135 QByteArray tempPath2;
136 tempPath1 = tempPath2 = arg1.toByteArray();
137 const QByteArray parentDir = dirname(tempPath1.data());
138 const QByteArray baseName = basename(tempPath2.data());
139 int parent_fd = -1;
140 int base_fd = -1;
141
142 if ((parent_fd = open(parentDir.data(), O_DIRECTORY | O_PATH | O_NOFOLLOW)) == -1) {
143 reply.setError(errno);
144 return reply;
145 }
146
147 Privilege *origPrivilege = new Privilege{geteuid(), getegid()};
148 Privilege *targetPrivilege = nullptr;
149
150 if (action != CHMOD && action != UTIME) {
151 targetPrivilege = getTargetPrivilege(parent_fd);
152 } else {
153 if ((base_fd = openat(parent_fd, baseName.data(), O_NOFOLLOW)) != -1) {
154 targetPrivilege = getTargetPrivilege(base_fd);
155 } else {
156 reply.setError(errno);
157 }
158 }
159
160 if (dropPrivilege(targetPrivilege)) {
161 switch (action) {
162 case CHMOD: {
163 int mode = arg2.toInt();
164 if (fchmod(base_fd, mode) == -1) {
165 reply.setError(errno);
166 }
167 close(base_fd);
168 break;
169 }
170
171 case DEL:
172 case RMDIR: {
173 int flags = 0;
174 if (action == RMDIR) {
175 flags |= AT_REMOVEDIR;
176 }
177 if (unlinkat(parent_fd, baseName.data(), flags) == -1) {
178 reply.setError(errno);
179 }
180 break;
181 }
182
183 case MKDIR: {
184 int mode = arg2.toInt();
185 if (mkdirat(parent_fd, baseName.data(), mode) == -1) {
186 reply.setError(errno);
187 }
188 break;
189 }
190
191 case OPEN:
192 case OPENDIR: {
193 int oflags = arg2.toInt();
194 int mode = arg3.toInt();
195 int extraFlag = O_NOFOLLOW;
196 if (action == OPENDIR) {
197 extraFlag |= O_DIRECTORY;
198 }
199 if (int fd = openat(parent_fd, baseName.data(), oflags | extraFlag, mode) != -1) {
200 gainPrivilege(origPrivilege);
201 if (!sendFileDescriptor(fd, arg4.toByteArray().constData())) {
202 reply.setError(errno);
203 }
204 } else {
205 reply.setError(errno);
206 }
207 break;
208 }
209
210 case RENAME: {
211 tempPath1 = tempPath2 = arg2.toByteArray();
212 const QByteArray newParentDir = dirname(tempPath1.data());
213 const QByteArray newBaseName = basename(tempPath2.data());
214 int new_parent_fd = open(newParentDir.constData(), O_DIRECTORY | O_PATH | O_NOFOLLOW);
215 if (renameat(parent_fd, baseName.data(), new_parent_fd, newBaseName.constData()) == -1) {
216 reply.setError(errno);
217 }
218 close(new_parent_fd);
219 break;
220 }
221
222 case SYMLINK: {
223 const QByteArray target = arg2.toByteArray();
224 if (symlinkat(target.data(), parent_fd, baseName.data()) == -1) {
225 reply.setError(errno);
226 }
227 break;
228 }
229
230 case UTIME: {
231 timespec times[2];
232 time_t actime = arg2.toULongLong();
233 time_t modtime = arg3.toULongLong();
234 times[0].tv_sec = actime / 1000;
235 times[0].tv_nsec = actime * 1000;
236 times[1].tv_sec = modtime / 1000;
237 times[1].tv_nsec = modtime * 1000;
238 if (futimens(base_fd, times) == -1) {
239 reply.setError(errno);
240 }
241 close(base_fd);
242 break;
243 }
244
245 default:
246 reply.setError(ENOTSUP);
247 break;
248 }
249 gainPrivilege(origPrivilege);
250 } else {
251 reply.setError(errno);
252 }
253
254 if (origPrivilege) {
255 delete origPrivilege;
256 }
257 if (targetPrivilege) {
258 delete targetPrivilege;
259 }
260 close(parent_fd);
261 return reply;
262 }
263
264 KAUTH_HELPER_MAIN("org.kde.kio.file", FileHelper)
265