5265e25007491c7416b5505d9fb0a5ff93957444
[ira/wip.git] / source4 / ntvfs / posix / pvfs_sys.c
1 /*
2    Unix SMB/CIFS implementation.
3
4    POSIX NTVFS backend - pvfs_sys wrappers
5
6    Copyright (C) Andrew Tridgell 2010
7    Copyright (C) Andrew Bartlett 2010
8
9    This program is free software; you can redistribute it and/or modify
10    it under the terms of the GNU General Public License as published by
11    the Free Software Foundation; either version 3 of the License, or
12    (at your option) any later version.
13
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.
18
19    You should have received a copy of the GNU General Public License
20    along with this program.  If not, see <http://www.gnu.org/licenses/>.
21 */
22
23 #include "includes.h"
24 #include "vfs_posix.h"
25 #include "../lib/util/unix_privs.h"
26
27 /*
28   these wrapper functions must only be called when the appropriate ACL
29   has already been checked. The wrappers will override a EACCES result
30   by gaining root privileges if the 'pvfs:perm override' is set on the
31   share (it is enabled by default)
32
33   Careful use of O_NOFOLLOW and O_DIRECTORY is used to prevent
34   security attacks via symlinks
35  */
36
37
38 struct pvfs_sys_ctx {
39         struct pvfs_state *pvfs;
40         void *privs;
41         const char *old_wd;
42         struct stat st_orig;
43 };
44
45 /*
46   return to original directory when context is destroyed
47  */
48 static int pvfs_sys_pushdir_destructor(struct pvfs_sys_ctx *ctx)
49 {
50         struct stat st;
51
52         if (ctx->old_wd == NULL) {
53                 return 0;
54         }
55
56         if (chdir(ctx->old_wd) != 0) {
57                 smb_panic("Failed to restore working directory");
58         }
59         if (stat(".", &st) != 0) {
60                 smb_panic("Failed to stat working directory");
61         }
62         if (st.st_ino != ctx->st_orig.st_ino ||
63             st.st_dev != ctx->st_orig.st_dev) {
64                 smb_panic("Working directory changed during call");
65         }
66
67         return 0;
68 }
69
70
71 /*
72   chdir() to the directory part of a pathname, but disallow any
73   component with a symlink
74
75   Note that we can't use O_NOFOLLOW on the whole path as that only
76   prevents links in the final component of the path
77  */
78 static int pvfs_sys_chdir_nosymlink(struct pvfs_sys_ctx *ctx, const char *pathname)
79 {
80         char *p, *path;
81         size_t base_len = strlen(ctx->pvfs->base_directory);
82
83         /* don't check for symlinks in the base directory of the share */
84         if (strncmp(ctx->pvfs->base_directory, pathname, base_len) == 0 &&
85             pathname[base_len] == '/') {
86                 if (chdir(ctx->pvfs->base_directory) != 0) {
87                         return -1;
88                 }
89                 pathname += base_len + 1;
90         }
91
92         path = talloc_strdup(ctx, pathname);
93         if (path == NULL) {
94                 return -1;
95         }
96         while ((p = strchr(path, '/'))) {
97                 int fd;
98                 struct stat st1, st2;
99                 *p = 0;
100                 fd = open(path, O_NOFOLLOW | O_DIRECTORY | O_RDONLY);
101                 if (fd == -1) {
102                         return -1;
103                 }
104                 if (chdir(path) != 0) {
105                         close(fd);
106                         return -1;
107                 }
108                 if (stat(".", &st1) != 0 ||
109                     fstat(fd, &st2) != 0) {
110                         close(fd);
111                         return -1;
112                 }
113                 close(fd);
114                 if (st1.st_ino != st2.st_ino ||
115                     st1.st_dev != st2.st_dev) {
116                         DEBUG(0,(__location__ ": Inode changed during chdir in '%s' - symlink attack?",
117                                  pathname));
118                         return -1;
119                 }
120                 path = p + 1;
121         }
122
123         return 0;
124 }
125
126
127 /*
128   become root, and change directory to the directory component of a
129   path. Return a talloc context which when freed will move us back
130   to the original directory, and return us to the original uid
131
132   change the pathname argument to contain just the base component of
133   the path
134
135   return NULL on error, which could include an attempt to subvert
136   security using symlink tricks
137  */
138 static struct pvfs_sys_ctx *pvfs_sys_pushdir(struct pvfs_state *pvfs,
139                                              const char **pathname)
140 {
141         struct pvfs_sys_ctx *ctx;
142         char *cwd, *p, *dirname;
143         int ret;
144
145         ctx = talloc_zero(pvfs, struct pvfs_sys_ctx);
146         if (ctx == NULL) {
147                 return NULL;
148         }
149         ctx->pvfs = pvfs;
150         ctx->privs = root_privileges();
151         if (ctx->privs == NULL) {
152                 talloc_free(ctx);
153                 return NULL;
154         }
155
156         talloc_steal(ctx, ctx->privs);
157
158         if (!pathname) {
159                 /* no pathname needed */
160                 return ctx;
161         }
162
163         p = strrchr(*pathname, '/');
164         if (p == NULL) {
165                 /* we don't need to change directory */
166                 return ctx;
167         }
168
169         /* we keep the old st around, so we can tell that
170            we have come back to the right directory */
171         if (stat(".", &ctx->st_orig) != 0) {
172                 talloc_free(ctx);
173                 return NULL;
174         }
175
176         cwd = get_current_dir_name();
177         if (cwd == NULL) {
178                 talloc_free(ctx);
179                 return NULL;
180         }
181         ctx->old_wd = talloc_strdup(ctx, cwd);
182         if (ctx->old_wd == NULL) {
183                 free(cwd);
184                 talloc_free(ctx);
185                 return NULL;
186         }
187
188         dirname = talloc_strndup(ctx, *pathname, (p - *pathname));
189         if (dirname == NULL) {
190                 talloc_free(ctx);
191                 return NULL;
192         }
193
194         ret = pvfs_sys_chdir_nosymlink(ctx, *pathname);
195         if (ret == -1) {
196                 talloc_free(ctx);
197                 return NULL;
198         }
199
200         talloc_set_destructor(ctx, pvfs_sys_pushdir_destructor);
201
202         /* return the basename as the filename that should be operated on */
203         (*pathname) = talloc_strdup(ctx, p+1);
204         if (! *pathname) {
205                 talloc_free(ctx);
206                 return NULL;
207         }
208
209         return ctx;
210 }
211
212
213 /*
214   chown a file that we created with a root privileges override
215  */
216 static int pvfs_sys_fchown(struct pvfs_state *pvfs, struct pvfs_sys_ctx *ctx, int fd)
217 {
218         return fchown(fd, root_privileges_original_uid(ctx->privs), -1);
219 }
220
221 /*
222   chown a directory that we created with a root privileges override
223  */
224 static int pvfs_sys_chown(struct pvfs_state *pvfs, struct pvfs_sys_ctx *ctx, const char *name)
225 {
226         /* to avoid symlink hacks, we need to use fchown() on a directory fd */
227         int ret, fd;
228         fd = open(name, O_DIRECTORY | O_NOFOLLOW | O_RDONLY);
229         if (fd == -1) {
230                 return -1;
231         }
232         ret = pvfs_sys_fchown(pvfs, ctx, fd);
233         close(fd);
234         return ret;
235 }
236
237
238 /*
239   wrap open for system override
240 */
241 int pvfs_sys_open(struct pvfs_state *pvfs, const char *filename, int flags, mode_t mode)
242 {
243         int fd, ret;
244         struct pvfs_sys_ctx *ctx;
245         int saved_errno, orig_errno;
246         int retries = 5;
247
248         orig_errno = errno;
249
250         fd = open(filename, flags, mode);
251         if (fd != -1 ||
252             !(pvfs->flags & PVFS_FLAG_PERM_OVERRIDE) ||
253             errno != EACCES) {
254                 return fd;
255         }
256
257         saved_errno = errno;
258         ctx = pvfs_sys_pushdir(pvfs, &filename);
259         if (ctx == NULL) {
260                 errno = saved_errno;
261                 return -1;
262         }
263
264         /* don't allow permission overrides to follow links */
265         flags |= O_NOFOLLOW;
266
267         /*
268            if O_CREAT was specified and O_EXCL was not specified
269            then initially do the open without O_CREAT, as in that case
270            we know that we did not create the file, so we don't have
271            to fchown it
272          */
273         if ((flags & O_CREAT) && !(flags & O_EXCL)) {
274         try_again:
275                 fd = open(filename, flags & ~O_CREAT, mode);
276                 /* if this open succeeded, or if it failed
277                    with anything other than ENOENT, then we return the
278                    open result, with the original errno */
279                 if (fd == -1 && errno != ENOENT) {
280                         talloc_free(ctx);
281                         errno = saved_errno;
282                         return -1;
283                 }
284                 if (fd != -1) {
285                         /* the file already existed and we opened it */
286                         talloc_free(ctx);
287                         errno = orig_errno;
288                         return fd;
289                 }
290
291                 fd = open(filename, flags | O_EXCL, mode);
292                 if (fd == -1 && errno != EEXIST) {
293                         talloc_free(ctx);
294                         errno = saved_errno;
295                         return -1;
296                 }
297                 if (fd != -1) {
298                         /* we created the file, we need to set the
299                            right ownership on it */
300                         ret = pvfs_sys_fchown(pvfs, ctx, fd);
301                         if (ret == -1) {
302                                 close(fd);
303                                 unlink(filename);
304                                 talloc_free(ctx);
305                                 errno = saved_errno;
306                                 return -1;
307                         }
308                         talloc_free(ctx);
309                         errno = orig_errno;
310                         return fd;
311                 }
312
313                 /* the file got created between the two times
314                    we tried to open it! Try again */
315                 if (retries-- > 0) {
316                         goto try_again;
317                 }
318
319                 talloc_free(ctx);
320                 errno = saved_errno;
321                 return -1;
322         }
323
324         fd = open(filename, flags, mode);
325         if (fd == -1) {
326                 talloc_free(ctx);
327                 errno = saved_errno;
328                 return -1;
329         }
330
331         /* if we have created a file then fchown it */
332         if (flags & O_CREAT) {
333                 ret = pvfs_sys_fchown(pvfs, ctx, fd);
334                 if (ret == -1) {
335                         close(fd);
336                         unlink(filename);
337                         talloc_free(ctx);
338                         errno = saved_errno;
339                         return -1;
340                 }
341         }
342
343         talloc_free(ctx);
344         return fd;
345 }
346
347
348 /*
349   wrap unlink for system override
350 */
351 int pvfs_sys_unlink(struct pvfs_state *pvfs, const char *filename)
352 {
353         int ret;
354         struct pvfs_sys_ctx *ctx;
355         int saved_errno, orig_errno;
356
357         orig_errno = errno;
358
359         ret = unlink(filename);
360         if (ret != -1 ||
361             !(pvfs->flags & PVFS_FLAG_PERM_OVERRIDE) ||
362             errno != EACCES) {
363                 return ret;
364         }
365
366         saved_errno = errno;
367
368         ctx = pvfs_sys_pushdir(pvfs, &filename);
369         if (ctx == NULL) {
370                 errno = saved_errno;
371                 return -1;
372         }
373
374         ret = unlink(filename);
375         if (ret == -1) {
376                 errno = saved_errno;
377                 talloc_free(ctx);
378                 return -1;
379         }
380
381         errno = orig_errno;
382         talloc_free(ctx);
383         return ret;
384 }
385
386
387 static bool contains_symlink(const char *path)
388 {
389         int fd = open(path, O_NOFOLLOW | O_RDONLY);
390         if (fd != -1) {
391                 close(fd);
392                 return false;
393         }
394         return (errno == ELOOP);
395 }
396
397 /*
398   wrap rename for system override
399 */
400 int pvfs_sys_rename(struct pvfs_state *pvfs, const char *name1, const char *name2)
401 {
402         int ret;
403         struct pvfs_sys_ctx *ctx;
404         int saved_errno, orig_errno;
405
406         orig_errno = errno;
407
408         ret = rename(name1, name2);
409         if (ret != -1 ||
410             !(pvfs->flags & PVFS_FLAG_PERM_OVERRIDE) ||
411             errno != EACCES) {
412                 return ret;
413         }
414
415         saved_errno = errno;
416
417         ctx = pvfs_sys_pushdir(pvfs, &name1);
418         if (ctx == NULL) {
419                 errno = saved_errno;
420                 return -1;
421         }
422
423         /* we need the destination as an absolute path */
424         if (name2[0] != '/') {
425                 name2 = talloc_asprintf(ctx, "%s/%s", ctx->old_wd, name2);
426                 if (name2 == NULL) {
427                         errno = saved_errno;
428                         talloc_free(ctx);
429                         return -1;
430                 }
431         }
432
433         /* make sure the destination isn't a symlink beforehand */
434         if (contains_symlink(name2)) {
435                 errno = saved_errno;
436                 talloc_free(ctx);
437                 return -1;
438         }
439
440         ret = rename(name1, name2);
441         if (ret == -1) {
442                 errno = saved_errno;
443                 talloc_free(ctx);
444                 return -1;
445         }
446
447         /* make sure the destination isn't a symlink afterwards */
448         if (contains_symlink(name2)) {
449                 DEBUG(0,(__location__ ": Possible symlink attack in rename to '%s' - unlinking\n", name2));
450                 unlink(name2);
451                 errno = saved_errno;
452                 talloc_free(ctx);
453                 return -1;
454         }
455
456         errno = orig_errno;
457         talloc_free(ctx);
458         return ret;
459 }
460
461
462 /*
463   wrap mkdir for system override
464 */
465 int pvfs_sys_mkdir(struct pvfs_state *pvfs, const char *dirname, mode_t mode)
466 {
467         int ret;
468         struct pvfs_sys_ctx *ctx;
469         int saved_errno, orig_errno;
470
471         orig_errno = errno;
472
473         ret = mkdir(dirname, mode);
474         if (ret != -1 ||
475             !(pvfs->flags & PVFS_FLAG_PERM_OVERRIDE) ||
476             errno != EACCES) {
477                 return ret;
478         }
479
480         saved_errno = errno;
481         ctx = pvfs_sys_pushdir(pvfs, &dirname);
482         if (ctx == NULL) {
483                 errno = saved_errno;
484                 return -1;
485         }
486
487         ret = mkdir(dirname, mode);
488         if (ret == -1) {
489                 talloc_free(ctx);
490                 errno = saved_errno;
491                 return -1;
492         }
493
494         ret = pvfs_sys_chown(pvfs, ctx, dirname);
495         if (ret == -1) {
496                 rmdir(dirname);
497                 talloc_free(ctx);
498                 errno = saved_errno;
499                 return -1;
500         }
501
502         talloc_free(ctx);
503         return ret;
504 }
505
506
507 /*
508   wrap rmdir for system override
509 */
510 int pvfs_sys_rmdir(struct pvfs_state *pvfs, const char *dirname)
511 {
512         int ret;
513         struct pvfs_sys_ctx *ctx;
514         int saved_errno, orig_errno;
515
516         orig_errno = errno;
517
518         ret = rmdir(dirname);
519         if (ret != -1 ||
520             !(pvfs->flags & PVFS_FLAG_PERM_OVERRIDE) ||
521             errno != EACCES) {
522                 return ret;
523         }
524
525         saved_errno = errno;
526
527         ctx = pvfs_sys_pushdir(pvfs, &dirname);
528         if (ctx == NULL) {
529                 errno = saved_errno;
530                 return -1;
531         }
532
533         ret = rmdir(dirname);
534         if (ret == -1) {
535                 errno = saved_errno;
536                 talloc_free(ctx);
537                 return -1;
538         }
539
540         errno = orig_errno;
541         talloc_free(ctx);
542         return ret;
543 }
544
545 /*
546   wrap fchmod for system override
547 */
548 int pvfs_sys_fchmod(struct pvfs_state *pvfs, int fd, mode_t mode)
549 {
550         int ret;
551         struct pvfs_sys_ctx *ctx;
552         int saved_errno, orig_errno;
553
554         orig_errno = errno;
555
556         ret = fchmod(fd, mode);
557         if (ret != -1 ||
558             !(pvfs->flags & PVFS_FLAG_PERM_OVERRIDE) ||
559             errno != EACCES) {
560                 return ret;
561         }
562
563         saved_errno = errno;
564
565         ctx = pvfs_sys_pushdir(pvfs, NULL);
566         if (ctx == NULL) {
567                 errno = saved_errno;
568                 return -1;
569         }
570
571         ret = fchmod(fd, mode);
572         if (ret == -1) {
573                 errno = saved_errno;
574                 talloc_free(ctx);
575                 return -1;
576         }
577
578         errno = orig_errno;
579         talloc_free(ctx);
580         return ret;
581 }
582
583
584 /*
585   wrap chmod for system override
586 */
587 int pvfs_sys_chmod(struct pvfs_state *pvfs, const char *filename, mode_t mode)
588 {
589         int ret;
590         struct pvfs_sys_ctx *ctx;
591         int saved_errno, orig_errno;
592
593         orig_errno = errno;
594
595         ret = chmod(filename, mode);
596         if (ret != -1 ||
597             !(pvfs->flags & PVFS_FLAG_PERM_OVERRIDE) ||
598             errno != EACCES) {
599                 return ret;
600         }
601
602         saved_errno = errno;
603
604         ctx = pvfs_sys_pushdir(pvfs, &filename);
605         if (ctx == NULL) {
606                 errno = saved_errno;
607                 return -1;
608         }
609
610         ret = chmod(filename, mode);
611         if (ret == -1) {
612                 errno = saved_errno;
613                 talloc_free(ctx);
614                 return -1;
615         }
616
617         errno = orig_errno;
618         talloc_free(ctx);
619         return ret;
620 }