diskless(8) just needs tftpd(8) to deliver files, none of the possibly
untrusted clients are supposed to ever write anything.

Either way, even when run without -c, a single file writable by _tftpd
might be enough for a malicious client to fill up the server's disk.

A proper read-only mode ("stdio rpath dns inet") seems much safer.

diskless(8) setup and manual testing runs fine with this, clients get
proper error responses (just like trying to write /etc/random.seed) and
the server keeps running without any pledge violations:

        $ doas ./obj/tfpd -dvR ./_tftpd

        $ touch file
        $ echo put file | tftp localhost
        tftp> Error code 2: Access violation

        tftpd: 127.0.0.1: write request for 'file'
        tftpd: 127.0.0.1: nak: Access violation


tftpd(8) is nicely written such that all file access goes through a
single validation function, so adding read-only logic seems trivial.

Did I miss anything?
Feedback? OK?

This applies cleanly to -current but conflicts with the -c/cpath diff
on tech@, so one needs rebasing after the other lands.

Index: tftpd.8
===================================================================
RCS file: /cvs/src/usr.sbin/tftpd/tftpd.8,v
retrieving revision 1.8
diff -u -p -r1.8 tftpd.8
--- tftpd.8     4 Mar 2019 01:06:03 -0000       1.8
+++ tftpd.8     1 Oct 2022 17:30:27 -0000
@@ -37,7 +37,7 @@
 .Nd Trivial File Transfer Protocol daemon
 .Sh SYNOPSIS
 .Nm tftpd
-.Op Fl 46cdiv
+.Op Fl 46cdivR
 .Op Fl l Ar address
 .Op Fl p Ar port
 .Op Fl r Ar socket
@@ -56,6 +56,8 @@ will allow only publicly readable files 
 Files may be written only if they already exist and are publicly writable,
 unless the
 .Fl c
+or
+.Fl R
 flag is specified
 .Pq see below .
 Note that this extends the concept of
@@ -145,6 +147,8 @@ to
 on startup;
 the remote host is not expected to pass the directory
 as part of the file name to transfer.
+.Fl R
+Prevent creating or writing to files.
 .El
 .Sh SEE ALSO
 .Xr tftp 1 ,
Index: tftpd.c
===================================================================
RCS file: /cvs/src/usr.sbin/tftpd/tftpd.c,v
retrieving revision 1.47
diff -u -p -r1.47 tftpd.c
--- tftpd.c     24 Oct 2021 21:24:19 -0000      1.47
+++ tftpd.c     2 Oct 2022 18:12:25 -0000
@@ -289,6 +289,7 @@ usage(void)
 }
 
 int              cancreate = 0;
+int              readonly = 0;
 int              verbose = 0;
 int              debug = 0;
 int              iflag = 0;
@@ -309,7 +310,7 @@ main(int argc, char *argv[])
        int family = AF_UNSPEC;
        int devnull = -1;
 
-       while ((c = getopt(argc, argv, "46cdil:p:r:v")) != -1) {
+       while ((c = getopt(argc, argv, "46cdil:p:r:vR")) != -1) {
                switch (c) {
                case '4':
                        family = AF_INET;
@@ -342,6 +343,9 @@ main(int argc, char *argv[])
                case 'v':
                        verbose = 1;
                        break;
+               case 'R':
+                       readonly = 1;
+                       break;
                default:
                        usage();
                        /* NOTREACHED */
@@ -351,6 +355,9 @@ main(int argc, char *argv[])
        argc -= optind;
        argv += optind;
 
+       if (cancreate && readonly)
+               errx(1, "options -c and -R are incompatible");
+
        if (argc != 1)
                usage();
 
@@ -391,8 +398,13 @@ main(int argc, char *argv[])
        if (!debug && rdaemon(devnull) == -1)
                err(1, "unable to daemonize");
 
-       if (pledge("stdio rpath wpath cpath fattr dns inet", NULL) == -1)
-               lerr(1, "pledge");
+       if (readonly) {
+               if (pledge("stdio rpath dns inet", NULL) == -1)
+                       lerr(1, "pledge");
+       } else {
+               if (pledge("stdio rpath wpath cpath fattr dns inet", NULL) == 
-1)
+                       lerr(1, "pledge");
+       }
 
        event_init();
 
@@ -964,6 +976,9 @@ validate_access(struct tftp_client *clie
        int              fd, wmode;
        const char      *errstr, *filename;
        char             rewritten[PATH_MAX];
+
+       if (readonly && mode != RRQ)
+               return (EACCESS);
 
        if (strcmp(requested, SEEDPATH) == 0) {
                char *buf;

Reply via email to