Ulrich, Ping! Could you please review this patch. Cheers, Michael On Tue, Apr 8, 2008 at 12:18 AM, Michael Kerrisk <mtk.manpages@xxxxxxxxxxxxxx> wrote: > Ulrich, > > While writing a man page for utimensat(2) and futimens(3), I think I've > found a few bugs in the utimensat() system call (i.e., non-conformances > with either the specification in the draft POSIX.1-200x revision or > traditional Linux behavior). > > int utimensat(int dirfd, const char *pathname, > const struct timespec times[2], int flags); > > 1. The draft POSIX.1-200x specification for utimensat() says that if a > times[n].tv_nsec field is UTIME_OMIT or UTIME_NOW, then the value in the > corresponding tv_sec field is ignored. However the current Linux > implementation requires the tv_sec value to be zero (or the EINVAL error > results). This requirement should be removed. > > 2. The POSIX.1 draft says that to do anything other than setting both > timestamps to a time other than the current time (i.e., times is not NULL, > and both tv_nsec fields are not UTIME_NOW and both tv_nsec fields are not > UTIME_OMIT -- see lines 32400 to 32403 of draft 5 of the spec), either: the > caller's effective user ID must match the owner of the file; or the caller > must have appropriate privileges. If this condition is violated, then the > error EPERM results. However, the current implementation does not generate > EPERM if one tv_nsec field is UTIME_NOW while the other is UTIME_OMIT -- it > should give this error for that case. > > 3. Traditionally, utime()/utimes() gives the error EACCES for the case > where the timestamp pointer argument is NULL (i.e., set both timestamps to > the current time), and the file is owned by a user other than the effective > UID of the caller, and the file is not writable by the effective UID of the > program. utimensat() also gives this error in the same case. However, in > the same circumstances, when utimensat() is given a 'times' array in which > both tv_nsec fields are UTIME_NOW, which provides equivalent functionality > to specifying 'times' as NULL, the call succeeds. I think that it should fail > with the error EACCES in this case. > > 4. A further bug relates to traditional Linux behavior. Traditionally > (i.e., utime(2) and utimes(2)), the EPERM error could also occur if the > 'times' argument was non-NULL (i.e., we are setting the timestamps to a > value other than the current time) and the file is marked as immutable or > append-only. The current implementation also returns this error if 'times' > is non-NULL and the tv_nsec fields are both UTIME_NOW. For consistency, the > > (times == NULL && times[0].tv_nsec == UTIME_NOW && times[1].tv_nsec == > UTIME_NOW) > > case should be treated like the traditional utimes() case where 'times' > is NULL. That is, the call should succeed for a file marked append-only > and should give the error EACCES if the file is marked as immutable. > > The first part of the patch below (made against 2.6.25-rc6, but still > applies against rc8) addresses bugs 2, 3, and 4 and the second part of the > patch addresses bug 1. Do you agree with my analyses and fixes? > > Following the patch, at the end of this mail is a command-line-driven test > program that can be used to test the current implementation, and my patch. > Some test cases below demonstrate the 4 cases described above, showing how > a vanilla 2.6.25-rcN kernel behaves, and how it behaves with my patch applied. > > Finally, with my patch applied, I believe that the following lines could be > removed from the sys_utimensat() routine (I've done some light testing to verify > this, but more testing would be in order): > > /* Nothing to do, we must not even check the path. */ > if (tstimes[0].tv_nsec == UTIME_OMIT && > tstimes[1].tv_nsec == UTIME_OMIT) > return 0; > > Cheers, > > Michael > > Signed-off-by: Michael Kerrisk <mtk.manpages@xxxxxxxxx> > > Test cases > ========== > > All test cases run as user 'mtk' > > Bug 1 > ----- > > Behavior with vanilla 2.6.25-rcN kernel: > > $ ls -l u > -r-------- 1 mtk users 0 Apr 7 21:39 u > $ ./t_utimensat u 1 n 1 n > dirfd is -100 > pathname is u > tsp is 0xbfb41408; struct = { 1, 1073741823 } { 1, 1073741823 } > flags is 0 > utimensat: Invalid argument > > Behavior with my patch applied: > > $ ls -l u > -r-------- 1 mtk users 0 Apr 7 21:39 u > $ ./t_utimensat u 1 n 1 n > dirfd is -100 > pathname is u > tsp is 0xbfab7378; struct = { 1, 1073741823 } { 1, 1073741823 } > flags is 0 > utimensat() succeeded > Last file access: Mon Apr 7 21:39:36 2008 > Last file modification: Mon Apr 7 21:39:36 2008 > Last status change: Mon Apr 7 21:39:36 2008 > > > Bug 2 > ----- > > Behavior with vanilla 2.6.25-rcN kernel: > > $ ls -l p > -rw-r--r-- 1 root root 0 Apr 7 10:34 p > $ ./t_utimensat p 0 n 0 o > dirfd is -100 > pathname is p > tsp is 0xbfb7ac38; struct = { 0, 1073741823 } { 0, 1073741822 } > flags is 0 > utimensat() succeeded > Last file access: Mon Apr 7 22:00:51 2008 > Last file modification: Mon Apr 7 10:34:00 2008 > Last status change: Mon Apr 7 22:00:51 2008 > > > Behavior with my patch applied: > > $ ls -l p > -rw-r--r-- 1 root root 0 Apr 7 10:34 p > $ ./t_utimensat p 0 n 0 o > dirfd is -100 > pathname is p > tsp is 0xbfc40d08; struct = { 1, 1073741823 } { 1, 1073741822 } > flags is 0 > utimensat failed with EPERM > > > Bug 3 > ----- > > Behavior with vanilla 2.6.25-rcN kernel: > > $ ls -l p > -rw-r--r-- 1 root root 0 Apr 7 22:03 p > $ ./t_utimensat p 0 n 0 n > dirfd is -100 > pathname is p > tsp is 0xbfd99658; struct = { 0, 1073741823 } { 0, 1073741823 } > flags is 0 > utimensat() succeeded > Last file access: Mon Apr 7 22:03:31 2008 > Last file modification: Mon Apr 7 22:03:31 2008 > Last status change: Mon Apr 7 22:03:31 2008 > $ ./t_utimensat p > dirfd is -100 > pathname is p > tsp is (nil) > flags is 0 > utimensat failed with EACCES > > Behavior with my patch applied: > > $ ls -l p > -rw-r--r-- 1 root root 0 Apr 7 10:34 p > $ ./t_utimensat p 0 n 0 n > dirfd is -100 > pathname is p > tsp is 0xbfa98358; struct = { 0, 1073741823 } { 0, 1073741823 } > flags is 0 > utimensat failed with EACCES > $ ./t_utimensat p > dirfd is -100 > pathname is p > tsp is (nil) > flags is 0 > utimensat failed with EACCES > > > Bug 4 > ----- > > Behavior with vanilla 2.6.25-rcN kernel: > > $ ls -l fa fi > -rw-r--r-- 1 mtk users 0 Apr 7 21:43 fa > -rw-r--r-- 1 mtk users 0 Apr 7 09:11 fi > $ lsattr -l fa fi > fa Append_Only > fi Immutable > $ ./t_utimensat fa 0 n 0 n > dirfd is -100 > pathname is fa > tsp is 0xbfc43508; struct = { 0, 1073741823 } { 0, 1073741823 } > flags is 0 > utimensat failed with EPERM > $ ./t_utimensat fi 0 n 0 n > dirfd is -100 > pathname is fi > tsp is 0xbfb54c18; struct = { 0, 1073741823 } { 0, 1073741823 } > flags is 0 > utimensat failed with EPERM > > > Behavior with my patch applied: > > $ ls -l fa fi > -rw-r--r-- 1 mtk users 0 Apr 7 18:24 fa > -rw-r--r-- 1 mtk users 0 Apr 7 09:11 fi > $ lsattr -l fa fi > fa Append_Only > fi Immutable > $ ./t_utimensat fa 0 n 0 n > dirfd is -100 > pathname is fa > tsp is 0xbfa7e338; struct = { 0, 1073741823 } { 0, 1073741823 } > flags is 0 > utimensat() succeeded > Last file access: Mon Apr 7 21:43:23 2008 > Last file modification: Mon Apr 7 21:43:23 2008 > Last status change: Mon Apr 7 21:43:23 2008 > $ ./t_utimensat fi 0 n 0 n > dirfd is -100 > pathname is fi > tsp is 0xbfe8d748; struct = { 0, 1073741823 } { 0, 1073741823 } > flags is 0 > utimensat failed with EACCES > > > > ================================================== > > --- linux-2.6.25-rc6-orig/fs/utimes.c 2008-04-07 22:25:08.000000000 +0200 > +++ linux-2.6.25-rc6/fs/utimes.c 2008-04-07 23:57:41.000000000 +0200 > @@ -95,27 +95,37 @@ > > /* Don't worry, the checks are done in inode_change_ok() */ > newattrs.ia_valid = ATTR_CTIME | ATTR_MTIME | ATTR_ATIME; > - if (times) { > + if (times && ! ((times[0].tv_nsec == UTIME_NOW && > + times[1].tv_nsec == UTIME_NOW) || > + (times[0].tv_nsec == UTIME_OMIT && > + times[1].tv_nsec == UTIME_OMIT))) { > error = -EPERM; > if (IS_APPEND(inode) || IS_IMMUTABLE(inode)) > goto dput_and_out; > > - if (times[0].tv_nsec == UTIME_OMIT) > - newattrs.ia_valid &= ~ATTR_ATIME; > - else if (times[0].tv_nsec != UTIME_NOW) { > + if (times[0].tv_nsec == UTIME_OMIT) { > + newattrs.ia_atime = inode->i_atime; > + newattrs.ia_valid |= ATTR_ATIME_SET; > + } else if (times[0].tv_nsec != UTIME_NOW) { > newattrs.ia_atime.tv_sec = times[0].tv_sec; > newattrs.ia_atime.tv_nsec = times[0].tv_nsec; > newattrs.ia_valid |= ATTR_ATIME_SET; > } > > - if (times[1].tv_nsec == UTIME_OMIT) > - newattrs.ia_valid &= ~ATTR_MTIME; > - else if (times[1].tv_nsec != UTIME_NOW) { > + if (times[1].tv_nsec == UTIME_OMIT) { > + newattrs.ia_mtime = inode->i_mtime; > + newattrs.ia_valid |= ATTR_MTIME_SET; > + } else if (times[1].tv_nsec != UTIME_NOW) { > newattrs.ia_mtime.tv_sec = times[1].tv_sec; > newattrs.ia_mtime.tv_nsec = times[1].tv_nsec; > newattrs.ia_valid |= ATTR_MTIME_SET; > } > + > + } else if (unlikely(times && times[0].tv_nsec == UTIME_OMIT && > + times[1].tv_nsec == UTIME_OMIT)) { > + newattrs.ia_valid &= ~(ATTR_ATIME | ATTR_MTIME | ATTR_CTIME); > } else { > + /* times is NULL, or both tv_nsec fields are UTIME_NOW */ > error = -EACCES; > if (IS_IMMUTABLE(inode)) > goto dput_and_out; > @@ -150,14 +160,6 @@ > if (utimes) { > if (copy_from_user(&tstimes, utimes, sizeof(tstimes))) > return -EFAULT; > - if ((tstimes[0].tv_nsec == UTIME_OMIT || > - tstimes[0].tv_nsec == UTIME_NOW) && > - tstimes[0].tv_sec != 0) > - return -EINVAL; > - if ((tstimes[1].tv_nsec == UTIME_OMIT || > - tstimes[1].tv_nsec == UTIME_NOW) && > - tstimes[1].tv_sec != 0) > - return -EINVAL; > > /* Nothing to do, we must not even check the path. */ > if (tstimes[0].tv_nsec == UTIME_OMIT && > > > ================================================== > > /* t_utimensat.c > > Copyright (C) 2008, Michael Kerrisk <mtk.manpages@xxxxxxxxx> > > Licensed under the GPLv2 or later. > > A command-line interface for testing the utimensat() system > call. > > 17 Mar 2008 Initial creation > */ > #define _GNU_SOURCE > #define _ATFILE_SOURCE > #include <stdio.h> > #include <time.h> > #include <errno.h> > #include <stdlib.h> > #include <unistd.h> > #include <sys/syscall.h> > #include <fcntl.h> > #include <string.h> > #include <sys/stat.h> > > #define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \ > } while (0) > > > #define __NR_utimensat 320 /* x86 syscall number */ > > # define UTIME_NOW ((1l << 30) - 1l) > # define UTIME_OMIT ((1l << 30) - 2l) > > static inline int > utimensat(int dirfd, const char *pathname, > const struct timespec times[2], int flags) > { > return syscall(__NR_utimensat, dirfd, pathname, times, flags); > } > > static void > usageError(char *progName) > { > fprintf(stderr, "Usage: %s pathname [atime-sec " > "atime-nsec mtime-sec mtime-nsec]\n\n", progName); > fprintf(stderr, "Permitted options are:\n"); > fprintf(stderr, " [-d path] " > "open a directory file descriptor" > " (instead of using AT_FDCWD)\n"); > fprintf(stderr, " -w Open directory file " > "descriptor with O_RDWR (instead of O_RDONLY)\n"); > fprintf(stderr, " -n Use AT_SYMLINK_NOFOLLOW\n"); > fprintf(stderr, "\n"); > > fprintf(stderr, "pathname can be \"NULL\" to use NULL " > "argument in call\n"); > fprintf(stderr, "\n"); > > fprintf(stderr, "Either nsec field can be\n"); > fprintf(stderr, " 'n' for UTIME_NOW\n"); > fprintf(stderr, " 'o' for UTIME_OMIT\n"); > fprintf(stderr, "\n"); > > fprintf(stderr, "If the time fields are omitted, " > "then a NULL 'times' argument is used\n"); > fprintf(stderr, "\n"); > > exit(EXIT_FAILURE); > } > > int > main(int argc, char *argv[]) > { > int flags, dirfd, opt, oflag; > struct timespec ts[2]; > struct timespec *tsp; > char *pathname, *dirfdPath; > struct stat sb; > > flags = 0; > dirfd = AT_FDCWD; > dirfdPath = NULL; > oflag = O_RDONLY; > > while ((opt = getopt(argc, argv, "d:nw")) != -1) { > switch (opt) { > case 'd': > dirfdPath = optarg; > break; > > case 'n': > flags |= AT_SYMLINK_NOFOLLOW; > printf("Not following symbolic links\n"); > break; > > case 'w': > oflag = O_RDWR; > break; > > default: > usageError(argv[0]); > } > } > > if ((optind + 5 != argc) && (optind + 1 != argc)) > usageError(argv[0]); > > if (dirfdPath != NULL) { > dirfd = open(dirfdPath, oflag); > if (dirfd == -1) errExit("open"); > > printf("Opened dirfd"); > printf(" O_RDWR"); > printf(": %s\n", dirfdPath); > } > > pathname = (strcmp(argv[optind], "NULL") == 0) ? > NULL : argv[optind]; > > if (argc == optind + 1) { > tsp = NULL; > > } else { > ts[0].tv_sec = atoi(argv[optind + 1]); > if (argv[optind + 2][0] == 'n') { > ts[0].tv_nsec = UTIME_NOW; > } else if (argv[optind + 2][0] == 'o') { > ts[0].tv_nsec = UTIME_OMIT; > } else { > ts[0].tv_nsec = atoi(argv[optind + 2]); > } > > ts[1].tv_sec = atoi(argv[optind + 3]); > if (argv[optind + 4][0] == 'n') { > ts[1].tv_nsec = UTIME_NOW; > } else if (argv[optind + 4][0] == 'o') { > ts[1].tv_nsec = UTIME_OMIT; > } else { > ts[1].tv_nsec = atoi(argv[optind + 4]); > } > > tsp = ts; > } > > /* For testing purposes, it may have been useful to run this > program as set-user-ID-root so that a directory file > descriptor could be opened as root. Now we reset to the > real UID before making the utimensat() call, so that the > permission checking is performed under that UID. */ > > if (geteuid() == 0) { > uid_t u; > > u = getuid(); > > printf("Resettng UIDs to %ld\n", (long) u); > > if (setresuid(u, u, u) == -1) > errExit("setresuid"); > } > > printf("dirfd is %d\n", dirfd); > printf("pathname is %s\n", pathname); > printf("tsp is %p", tsp); > if (tsp != NULL) { > printf("; struct = { %ld, %ld } { %ld, %ld }", > (long) tsp[0].tv_sec, (long) tsp[0].tv_nsec, > (long) tsp[1].tv_sec, (long) tsp[1].tv_nsec); > } > printf("\n"); > printf("flags is %d\n", flags); > > if (utimensat(dirfd, pathname, tsp, flags) == -1) { > if (errno == EPERM) > printf("utimensat failed with EPERM\n"); > else if (errno == EACCES) > printf("utimensat failed with EACCES\n"); > else > perror("utimensat"); > exit(EXIT_FAILURE); > } > > printf("utimensat() succeeded\n"); > > if (stat(pathname, &sb) == -1) errExit("stat"); > printf("Last file access: %s", ctime(&sb.st_atime)); > printf("Last file modification: %s", ctime(&sb.st_mtime)); > printf("Last status change: %s", ctime(&sb.st_ctime)); > > exit(EXIT_SUCCESS); > } > -- Michael Kerrisk Maintainer of the Linux man-pages project http://www.kernel.org/doc/man-pages/ Want to report a man-pages bug? Look here: http://www.kernel.org/doc/man-pages/reporting_bugs.html -- To unsubscribe from this list: send the line "unsubscribe linux-man" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html