First, some generals remarks:

- The debug feature (not documented) defeat the `-r' flag purpose.

  I think the code should be either:
    - enclosed in #ifdef DEBUG (prefered way)
    - not permitted if `rflag' or `wflag' are setted

- Adding tame(2) may be a good way to enforce the policyi, but it should
  be added later (when other userland tools gains it).


Else, just some comments inline.

On Thu, Sep 10, 2015 at 12:58:52AM +0200, Alexander Hall wrote:
> 
> Index: rmt.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/rmt/rmt.c,v
> retrieving revision 1.15
> diff -u -p -r1.15 rmt.c
> --- rmt.c     16 Jan 2015 06:40:20 -0000      1.15
> +++ rmt.c     9 Sep 2015 22:57:41 -0000
> @@ -72,14 +75,50 @@ main(int argc, char *argv[])
>       int rval;
>       char c;
>       int n, i, cc;
> +     int ch, rflag = 0, wflag = 0;
> +     int f, acc;
> +     mode_t m;
> +     char *dir = NULL;
> +     char *devp;
> +     size_t dirlen;
> +
> +     while ((ch = getopt(argc, argv, "d:rw")) != -1) {
> +             switch (ch) {
> +             case 'd':
> +                     dir = optarg;
> +                     if (*dir != '/')
> +                             errx(1, "directory must be absolute");
> +                     break;
> +             case 'r':
> +                     rflag = 1;
> +                     break;
> +             case 'w':
> +                     wflag = 1;
> +                     break;
> +             default:
> +                     usage();
> +                     /* NOTREACHED */
> +             }
> +     }
> +     argc -= optind;
> +     argv += optind;
> +
> +     if (rflag && wflag)
> +             usage();
>  
> -     argc--, argv++;
>       if (argc > 0) {
>               debug = fopen(*argv, "w");
>               if (debug == 0)
> -                     exit(1);
> +                     err(1, "cannot open debug file");
>               (void) setbuf(debug, (char *)0);
>       }
> +
> +     if (dir) {
> +             if (chdir(dir) != 0)
> +                     err(1, "chdir");
> +             dirlen = strlen(dir);
> +     }
> +
>  top:
>       errno = 0;
>       rval = 0;
> @@ -93,10 +132,66 @@ top:
>               getstring(device, sizeof(device));
>               getstring(mode, sizeof(mode));
>               DEBUG2("rmtd: O %s %s\n", device, mode);
> -             tape = open(device, atoi(mode),
> -                 S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
> +
> +             devp = device;
> +             f = atoi(mode);

atoi(3) is a bit fragile.

> +             m = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH;
> +             acc = f & O_ACCMODE;
> +             if (dir) {
> +                     /* Strip away valid directory prefix. */
> +                     if (strncmp(dir, devp, dirlen) == 0 &&
> +                         (devp[dirlen - 1] == '/' ||
> +                          devp[dirlen] == '/')) {
> +                          devp += dirlen;
> +                          while (*devp == '/')
> +                             devp++;
> +                     }

the "strip away valid directory prefix" code is a bit complex. If I
understand your purpose, you deal with possible trailing '/' in `dir' in
the same code than removing the prefix from `devp'.

maybe you should:

  1. canonalize `dir' (by removing possible trailing '/') (should be
  done in if (dir) { } block before).

>> if (dir[dirlen] == '/') {
>>      dir[dirlen] = '\0';
>>      dirlen --;
>> }

  2. remove `dir' prefix from `devp'

>> if (strncmp(dir, devp, dirlen) == 0 && devp[dirlen] == '/')
>>   devp += dirlen + 1;

> +                     /* Don't allow directory traversal. */
> +                     if (strchr(devp, '/')) {
> +                             errno = EACCES;
> +                             goto ioerror;
> +                     }
> +                     f |= O_NOFOLLOW;
> +             }
> +             if (rflag) {
> +                     /*
> +                      * Only allow readonly open and ignore file
> +                      * creation requests.
> +                      */
> +                     if (acc != O_RDONLY) {
> +                             errno = EPERM;
> +                             goto ioerror;
> +                     }
> +                     f &= ~O_CREAT;
> +             } else if (wflag) {
> +                     /*
> +                      * Require, and force creation of, a nonexistant file,
> +                      * unless we are reopening the last opened file again,
> +                      * in which case it is opened read-only.
> +                      */
> +                     if (strcmp(devp, lastdevice) != 0) {
> +                             /*
> +                              * Disallow read-only open since that would
> +                              * only result in an empty file.
> +                              */
> +                             if (acc == O_RDONLY) {
> +                                     errno = EPERM;
> +                                     goto ioerror;
> +                             }

does when using -w, O_RDWR should be permitted ? I would expect
O_WRONLY to be the only allowed mode.

> +                             f |= O_CREAT | O_EXCL;

anyway, O_CREAT | O_EXCL would mitigate the use of O_RDWR for reading
existing file...

> +                     } else {
> +                             acc = O_RDONLY;
> +                     }
> +                     /* Create readonly file */
> +                     m = S_IRUSR|S_IRGRP|S_IROTH;
> +             }
> +             /* Apply new access mode. */
> +             f = (f & ~O_ACCMODE) | acc;
> +
> +             tape = open(device, f, m);

we have checked if `devp' is safe, so we should use `devp' instead of
`device'.

>               if (tape == -1)
>                       goto ioerror;
> +             (void)strlcpy(lastdevice, devp, sizeof(lastdevice));
>               goto respond;
>  
>       case 'C':

-- 
Sebastien Marie

Reply via email to