On 09/12/15 09:13, Sebastien Marie wrote:
> First, some generals remarks:
>
> - The debug feature (not documented) defeat the `-r' flag purpose.

How do you mean it defeats it? The purpose of the '-r' flag is to stop
a user from creating and/or writing to files. Obviously said user may
not dictate the rmt arguments himself in that case.

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

I was tempted to rip that undocumented feature out entirely. But that's
another diff, so I left it as-is. The only regression the diff
introduces is if you are currently using a debug log filename with a
leading dash. I won't care about that.

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

Sure, later.

> 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

>> @@ -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.

Yeah, but the code is full of it already, so I left it consistent and
potentially for another diff to fix.

>> +            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'.

Yes, and also potential extra slashes in the attempted filename
("rmt -d /some/path" vs the filename "/some/path//filename")

While maybe somewhat complex, do you think it's wrong?

> 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;

FWIW, this does not handle multiple slashes in the attempted filename.


>> +            } 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...

Yeah, I considered that being fine. -r and -w is not strictly read-only
and write-only in every low-level sense, but at a slightly higher level.

>> +                    } 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'.

I was thinking it didn't matter much, but yes, we're less likely to be
bitten by race conditions that way.

That said, this is the only thing I feel compelled to change. If you
feel strongly otherwise, please convince me.

New diff below. OK?

/Alexander

Index: rmt.8
===================================================================
RCS file: /cvs/src/usr.sbin/rmt/rmt.8,v
retrieving revision 1.12
diff -u -p -r1.12 rmt.8
--- rmt.8       23 Jul 2011 15:40:13 -0000      1.12
+++ rmt.8       15 Sep 2015 20:03:27 -0000
@@ -36,18 +36,38 @@
 .Nm rmt
 .Nd remote magtape protocol module
 .Sh SYNOPSIS
-.Nm rmt
+.Nm
+.Op Fl r | w
+.Op Fl d Ar directory
 .Sh DESCRIPTION
 .Nm
 is a program used by the remote dump and restore programs
-in manipulating a magnetic tape drive through an interprocess
-communication connection.
+through an interprocess communication connection.
+Traditionally it is used for manipulating a magnetic tape drive but it may
+be used for regular file access as well.
 .Nm
 is normally started up with an
 .Xr rcmd 3
 or
 .Xr rcmdsh 3
 call.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl d Ar directory
+Confine file access to
+.Ar directory .
+Forward slashes in filenames are disallowed and symlinks are not followed.
+.It Fl r
+Read-only mode, suitable for use with
+.Xr rrestore 8 .
+.It Fl w
+File write mode, suitable for use with
+.Xr rdump 8
+for dumping to regular files.
+Creates missing files and refuses to open existing ones.
+The file permission bits are set to readonly.
+.El
 .Pp
 The
 .Nm
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       15 Sep 2015 20:03:27 -0000
@@ -41,6 +41,7 @@
 #include <unistd.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <err.h>
 #include <errno.h>
 #include <string.h>
 #include <limits.h>
@@ -52,6 +53,7 @@ int   maxrecsize = -1;
 
 #define        STRSIZE 64
 char   device[PATH_MAX];
+char   lastdevice[PATH_MAX] = "";
 char   count[STRSIZE], mode[STRSIZE], pos[STRSIZE], op[STRSIZE];
 
 char   resp[BUFSIZ];
@@ -61,9 +63,10 @@ FILE *debug;
 #define        DEBUG1(f,a)     if (debug) fprintf(debug, f, a)
 #define        DEBUG2(f,a1,a2) if (debug) fprintf(debug, f, a1, a2)
 
-char   *checkbuf(char *, int);
-void   getstring(char *, int);
-void   error(int);
+char           *checkbuf(char *, int);
+void           getstring(char *, int);
+void           error(int);
+__dead void    usage(void);
 
 int
 main(int argc, char *argv[])
@@ -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);
+               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++;
+                       }
+                       /* 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;
+                               }
+                               f |= O_CREAT | O_EXCL;
+                       } 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(devp, f, m);
                if (tape == -1)
                        goto ioerror;
+               (void)strlcpy(lastdevice, devp, sizeof(lastdevice));
                goto respond;
 
        case 'C':
@@ -224,4 +319,14 @@ error(int num)
        DEBUG2("rmtd: E %d (%s)\n", num, strerror(num));
        (void) snprintf(resp, sizeof (resp), "E%d\n%s\n", num, strerror(num));
        (void) write(STDOUT_FILENO, resp, strlen(resp));
+}
+
+__dead void
+usage(void)
+{
+       extern char *__progname;
+
+       (void)fprintf(stderr, "usage: %s [-r | -w] [-d directory]\n",
+           __progname);
+       exit(1);
 }

Reply via email to