pledge/unveil: net/gophernicus

classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

pledge/unveil: net/gophernicus

Edd Barrett-3
Hey,

Having been interested in learning about pledge and unveil, I decided to
try and tighten up net/gophernicus (just in time for the hipster
resurgence of gopher you've all been waiting for :P ).

It turned out to be harder than expected, due to some of the features
and design decisions made upstream (shared memory, arbitrary popens,
personal "gopherspaces"), but I think I have something workable.

In short, it depends what features you have enabled as to how far we can
tighten up security. The new section in the README should serve as an
overview.

This work is loosely based on:
https://cryogenix.net/gophernicus.html

(But note that I'm not addressing TLS support here)

Posting for comments before I propose this to upstream. I'd rather not
have to maintain this as local patches.

Happy new year porters!


Index: Makefile
===================================================================
RCS file: /cvs/ports/net/gophernicus/Makefile,v
retrieving revision 1.17
diff -u -p -r1.17 Makefile
--- Makefile 4 Sep 2018 12:46:17 -0000 1.17
+++ Makefile 31 Dec 2018 19:30:19 -0000
@@ -3,7 +3,7 @@
 COMMENT= modern gopher server
 DISTNAME= gophernicus-2.5
 CATEGORIES= net
-REVISION= 0
+REVISION= 1
 
 HOMEPAGE= gopher://gophernicus.org/
 MAINTAINER = Brian Callahan <[hidden email]>
Index: patches/patch-README
===================================================================
RCS file: patches/patch-README
diff -N patches/patch-README
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ patches/patch-README 1 Jan 2019 16:01:17 -0000
@@ -0,0 +1,17 @@
+$OpenBSD$
+
+Pledge and unveil. Based upon:
+https://github.com/0x16h/gophernicus/commit/1f61d46a5ebb061d5862a6a61e296b473c169ec6
+
+Index: README
+--- README.orig
++++ README
+@@ -43,6 +43,8 @@ Command line options:
+     -nm           Disable shared memory use (for debugging)
+     -nr           Disable root user checking (for debugging)
+     -np           Disable HAproxy proxy protocol
++    -ne           Disable executable gophermaps
++    -nu           Disable personal gopherspaces
+
+     -d            Debug to syslog (not for production use)
+     -v            Display version number and build date
Index: patches/patch-gophernicus_c
===================================================================
RCS file: patches/patch-gophernicus_c
diff -N patches/patch-gophernicus_c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ patches/patch-gophernicus_c 1 Jan 2019 15:58:47 -0000
@@ -0,0 +1,174 @@
+$OpenBSD$
+
+Pledge and unveil. Based upon:
+https://github.com/0x16h/gophernicus/commit/1f61d46a5ebb061d5862a6a61e296b473c169ec6
+
+Index: gophernicus.c
+--- gophernicus.c.orig
++++ gophernicus.c
+@@ -25,7 +25,6 @@
+
+ #include "gophernicus.h"
+
+-
+ /*
+  * Print gopher menu line
+  */
+@@ -219,7 +218,8 @@ void selector_to_path(state *st)
+
+ #ifdef HAVE_PASSWD
+ /* Virtual userdir (~user -> /home/user/public_gopher)? */
+- if (*(st->user_dir) && sstrncmp(st->req_selector, "/~") == MATCH) {
++ if (st->opt_personal_spaces && *(st->user_dir) &&
++    sstrncmp(st->req_selector, "/~") == MATCH) {
+
+ /* Parse userdir login name & path */;
+ sstrlcpy(buf, st->req_selector + 2);
+@@ -453,6 +453,8 @@ void init_state(state *st)
+ st->opt_shm = TRUE;
+ st->opt_root = TRUE;
+ st->opt_proxy = TRUE;
++ st->opt_execmaps = TRUE;
++ st->opt_personal_spaces = TRUE;
+ st->debug = FALSE;
+
+ /* Load default suffix -> filetype mappings */
+@@ -488,6 +490,9 @@ int main(int argc, char *argv[])
+ char local[BUFSIZE];
+ int dummy;
+ #endif
++#ifdef __OpenBSD__
++ char pledges[256];
++#endif
+
+ /* Get the name of this binary */
+ if ((c = strrchr(argv[0], '/'))) sstrlcpy(self, c + 1);
+@@ -506,6 +511,78 @@ int main(int argc, char *argv[])
+ /* Open syslog() */
+ if (st.opt_syslog) openlog(self, LOG_PID, LOG_DAEMON);
+
++#ifdef __OpenBSD__
++ /* unveil(2) support */
++ if (st.opt_execmaps) {
++ /*
++ * We can't really unveil(2) if the user is expecting to shell-out
++                 * to an arbitrary command.
++ */
++ if (st.opt_syslog) {
++ syslog(LOG_WARNING,
++    "executable gophermaps enabled, can't unveil(2)");
++ }
++ } else {
++ /* These paths must always available */
++ if (unveil("/tmp", "rcw") == -1)
++ die(&st, NULL, "unveil");
++ if (unveil("/etc/hosts", "r") == -1)
++ die(&st, NULL, "unveil");
++ if (unveil("/etc/resolv.conf", "r") == -1)
++ die(&st, NULL, "unveil");
++ if (unveil(st.server_root, "r") == -1)
++ die(&st, NULL, "unveil");
++
++ /*
++ * Personal spaces serve "gopher://host/1/~user" from
++ * "/home/user/public_gopher" and also needs to read the
++ * password database to get the user's ~/public_gopher.
++ */
++ if (st.opt_personal_spaces) {
++ syslog(LOG_WARNING,
++    "personal gopherspaces enabled, unveil(2) /home and /etc/pwd.db");
++ if (unveil("/home", "r") == -1)
++ die(&st, NULL, "unveil");
++
++ if (unveil("/etc/pwd.db", "r") == -1)
++ die(&st, NULL, "unveil");
++ }
++ }
++
++ /* pledge(2) support */
++ if (st.opt_shm) {
++ /*
++ * pledge(2) never allows shared memory.
++ */
++ if (st.opt_syslog) {
++ syslog(LOG_WARNING,
++    "shared-memory enabled, can't pledge(2)");
++ }
++ } else {
++ strlcpy(pledges,
++    "stdio rpath wpath cpath tmppath inet sendfd recvfd tty proc",
++    sizeof(pledges));
++
++ /* Executable maps shell-out using popen(3) */
++ if (st.opt_execmaps) {
++ strlcat(pledges, " exec", sizeof(pledges));
++ syslog(LOG_WARNING,
++    "executable gophermaps enabled, adding 'exec' to pledge(2)");
++ }
++
++ /* Personal spaces require getpwnam(3) and getpwent(3) */
++ if (st.opt_personal_spaces) {
++ strlcat(pledges, " getpw", sizeof(pledges));
++ syslog(LOG_WARNING,
++    "personal gopherspaces enabled, adding 'getpw' to pledge(2)");
++ }
++
++ if (pledge(pledges, NULL) == -1)
++ die(&st, NULL, "pledge");
++ }
++
++#endif
++
+ /* Check if TCP wrappers have something to say about this connection */
+ #ifdef HAVE_LIBWRAP
+ if (sstrncmp(st.req_remote_addr, UNKNOWN_ADDR) != MATCH &&
+@@ -527,30 +604,31 @@ int main(int argc, char *argv[])
+
+ /* Try to get shared memory */
+ #ifdef HAVE_SHMEM
+- if ((shmid = shmget(SHM_KEY, sizeof(shm_state), IPC_CREAT | SHM_MODE)) == ERROR) {
++ if (st.opt_shm) {
++ if ((shmid = shmget(SHM_KEY, sizeof(shm_state), IPC_CREAT | SHM_MODE)) == ERROR) {
+
+- /* Getting memory failed -> delete the old allocation */
+- shmctl(shmid, IPC_RMID, &shm_ds);
+- shm = NULL;
+- }
+- else {
+- /* Map shared memory */
+- if ((shm = (shm_state *) shmat(shmid, (void *) 0, 0)) == (void *) ERROR)
++ /* Getting memory failed -> delete the old allocation */
++ shmctl(shmid, IPC_RMID, &shm_ds);
+ shm = NULL;
++ }
++ else {
++ /* Map shared memory */
++ if ((shm = (shm_state *) shmat(shmid, (void *) 0, 0)) == (void *) ERROR)
++ shm = NULL;
+
+- /* Initialize mapped shared memory */
+- if (shm && shm->start_time == 0) {
+- shm->start_time = time(NULL);
++ /* Initialize mapped shared memory */
++ if (shm && shm->start_time == 0) {
++ shm->start_time = time(NULL);
+
+- /* Keep server platform & description in shm */
+- platform(&st);
+- sstrlcpy(shm->server_platform, st.server_platform);
+- sstrlcpy(shm->server_description, st.server_description);
++ /* Keep server platform & description in shm */
++ platform(&st);
++ sstrlcpy(shm->server_platform, st.server_platform);
++ sstrlcpy(shm->server_description, st.server_description);
++ }
+ }
++ } else {
++ shm = NULL;
+ }
+-
+- /* For debugging shared memory issues */
+- if (!st.opt_shm) shm = NULL;
+
+ /* Get server platform and description */
+ if (shm) {
Index: patches/patch-gophernicus_h
===================================================================
RCS file: patches/patch-gophernicus_h
diff -N patches/patch-gophernicus_h
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ patches/patch-gophernicus_h 1 Jan 2019 15:58:48 -0000
@@ -0,0 +1,17 @@
+$OpenBSD$
+
+Pledge and unveil. Based upon:
+https://github.com/0x16h/gophernicus/commit/1f61d46a5ebb061d5862a6a61e296b473c169ec6
+
+Index: gophernicus.h
+--- gophernicus.h.orig
++++ gophernicus.h
+@@ -351,6 +351,8 @@ typedef struct {
+ char opt_shm;
+ char opt_root;
+ char opt_proxy;
++ char opt_execmaps;
++ char opt_personal_spaces;
+ char debug;
+ } state;
+
Index: patches/patch-menu_c
===================================================================
RCS file: patches/patch-menu_c
diff -N patches/patch-menu_c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ patches/patch-menu_c 1 Jan 2019 15:58:49 -0000
@@ -0,0 +1,26 @@
+$OpenBSD$
+
+Pledge and unveil. Based upon:
+https://github.com/0x16h/gophernicus/commit/1f61d46a5ebb061d5862a6a61e296b473c169ec6
+
+Index: menu.c
+--- menu.c.orig
++++ menu.c
+@@ -306,8 +306,15 @@ int gophermap(state *st, char *mapfile, int depth)
+
+ /* Debug output */
+ if (st->debug) {
+- if (exe) syslog(LOG_INFO, "parsing executable gophermap \"%s\"", mapfile);
+- else syslog(LOG_INFO, "parsing static gophermap \"%s\"", mapfile);
++ if (exe) {
++ if (st->opt_execmaps)
++ syslog(LOG_INFO, "parsing executable gophermap: %s", mapfile);
++ else {
++ syslog(LOG_INFO, "ignoring executable gophermap: %s", mapfile);
++ return OK;
++ }
++ } else
++ syslog(LOG_INFO, "parsing static gophermap: %s", mapfile);
+ }
+
+ /* Try to execute or open the mapfile */
Index: patches/patch-options_c
===================================================================
RCS file: patches/patch-options_c
diff -N patches/patch-options_c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ patches/patch-options_c 1 Jan 2019 15:58:52 -0000
@@ -0,0 +1,17 @@
+$OpenBSD$
+
+Pledge and unveil. Based upon:
+https://github.com/0x16h/gophernicus/commit/1f61d46a5ebb061d5862a6a61e296b473c169ec6
+
+Index: options.c
+--- options.c.orig
++++ options.c
+@@ -143,6 +143,8 @@ void parse_args(state *st, int argc, char *argv[])
+ if (*optarg == 'm') { st->opt_shm = FALSE; break; }
+ if (*optarg == 'r') { st->opt_root = FALSE; break; }
+ if (*optarg == 'p') { st->opt_proxy = FALSE; break; }
++ if (*optarg == 'e') { st->opt_execmaps = FALSE; break; }
++ if (*optarg == 'u') { st->opt_personal_spaces = FALSE; break; }
+ break;
+
+ case 'd': st->debug = TRUE; break;
Index: pkg/README
===================================================================
RCS file: /cvs/ports/net/gophernicus/pkg/README,v
retrieving revision 1.5
diff -u -p -r1.5 README
--- pkg/README 4 Sep 2018 12:46:17 -0000 1.5
+++ pkg/README 1 Jan 2019 16:14:42 -0000
@@ -10,7 +10,7 @@ Setting up a gopher site
 After installing the gophernicus package, edit /etc/inetd.conf and add
 the following, all on one line:
 
-gopher stream tcp nowait _gophernicus ${TRUEPREFIX}/libexec/in.gophernicus in.gophernicus -h "hostname"
+gopher stream tcp nowait _gophernicus ${TRUEPREFIX}/libexec/in.gophernicus in.gophernicus -ne -nm -nu -h hostname
 
 Replace "hostname" with the system's hostname, this should be valid
 and resolvable as it is used to construct links.
@@ -24,3 +24,25 @@ only serve files which are world-readabl
 server process is not enough.
 
 For more information, see ${TRUEPREFIX}/share/doc/gophernicus/README.
+
+pledge(2) / unveil(2)
+=====================
+
+The above inetd.conf(8) line starts ${PKGSTEM} in the most secure mode of
+operation with regards to pledge(2) and unveil(2). In this configuration, some
+features are disabled in favour of tighter security:
+
+ * The `-ne` argument disables executable gophermaps. If this argument is
+   removed, unveil(2) is totally disabled, since we cannot know what paths an
+   arbitrary popen(3)ed shell command might require. Additionally 'exec' is
+   passed to pledge(2).
+
+ * The `-nm` argument disables shared-memory usage. If this argument is
+   removed, pledge(2) will be totally disabled, as pledge(2) never allows
+   shared memory.
+
+ * The `-nu` argument disables personal gopher spaces. If this argument is
+   removed, we unveil(2) /home and /etc/pwd.db, and 'getpw' is passed to
+   pledge(2).
+
+If any of these arguments are not present, ${PKGSTEM} will warn via syslog(3).

--
Best Regards
Edd Barrett

http://www.theunixzoo.co.uk

Reply | Threaded
Open this post in threaded view
|

Re: pledge/unveil: net/gophernicus

Theo de Raadt-2
The proposed diff contains many problems, and demonstrate that pledge and
unveil are not well understood.

In particular, specific pledges open up various system files, so those do not
need to be opened via unveil.  Thos unveil calls are misguided.  This is a pretty
strange misunderstanding.

The lack of pledge "dns" but opening of resolv.conf shows a further
misunderstanding, and also a lack of testing.  DNS lookups cannot be
working with this diff.  If you open up /etc/resolv.conf, but don't allow
the opening of "dns sockets" by pledging "dns", it ain't gonna work.

As provided, this diff surely breaks the program.  And wasn't spotted
during testing.

>Having been interested in learning about pledge and unveil, I decided to
>try and tighten up net/gophernicus (just in time for the hipster
>resurgence of gopher you've all been waiting for :P ).
>
>It turned out to be harder than expected, due to some of the features
>and design decisions made upstream (shared memory, arbitrary popens,
>personal "gopherspaces"), but I think I have something workable.
>
>In short, it depends what features you have enabled as to how far we can
>tighten up security. The new section in the README should serve as an
>overview.
>
>This work is loosely based on:
>https://cryogenix.net/gophernicus.html
>
>(But note that I'm not addressing TLS support here)
>
>Posting for comments before I propose this to upstream. I'd rather not
>have to maintain this as local patches.
>
>Happy new year porters!
>
>
>Index: Makefile
>===================================================================
>RCS file: /cvs/ports/net/gophernicus/Makefile,v
>retrieving revision 1.17
>diff -u -p -r1.17 Makefile
>--- Makefile 4 Sep 2018 12:46:17 -0000 1.17
>+++ Makefile 31 Dec 2018 19:30:19 -0000
>@@ -3,7 +3,7 @@
> COMMENT= modern gopher server
> DISTNAME= gophernicus-2.5
> CATEGORIES= net
>-REVISION= 0
>+REVISION= 1
>
> HOMEPAGE= gopher://gophernicus.org/
> MAINTAINER = Brian Callahan <[hidden email]>
>Index: patches/patch-README
>===================================================================
>RCS file: patches/patch-README
>diff -N patches/patch-README
>--- /dev/null 1 Jan 1970 00:00:00 -0000
>+++ patches/patch-README 1 Jan 2019 16:01:17 -0000
>@@ -0,0 +1,17 @@
>+$OpenBSD$
>+
>+Pledge and unveil. Based upon:
>+https://github.com/0x16h/gophernicus/commit/1f61d46a5ebb061d5862a6a61e296b473c169ec6
>+
>+Index: README
>+--- README.orig
>++++ README
>+@@ -43,6 +43,8 @@ Command line options:
>+     -nm           Disable shared memory use (for debugging)
>+     -nr           Disable root user checking (for debugging)
>+     -np           Disable HAproxy proxy protocol
>++    -ne           Disable executable gophermaps
>++    -nu           Disable personal gopherspaces
>+
>+     -d            Debug to syslog (not for production use)
>+     -v            Display version number and build date
>Index: patches/patch-gophernicus_c
>===================================================================
>RCS file: patches/patch-gophernicus_c
>diff -N patches/patch-gophernicus_c
>--- /dev/null 1 Jan 1970 00:00:00 -0000
>+++ patches/patch-gophernicus_c 1 Jan 2019 15:58:47 -0000
>@@ -0,0 +1,174 @@
>+$OpenBSD$
>+
>+Pledge and unveil. Based upon:
>+https://github.com/0x16h/gophernicus/commit/1f61d46a5ebb061d5862a6a61e296b473c169ec6
>+
>+Index: gophernicus.c
>+--- gophernicus.c.orig
>++++ gophernicus.c
>+@@ -25,7 +25,6 @@
>+
>+ #include "gophernicus.h"
>+
>+-
>+ /*
>+  * Print gopher menu line
>+  */
>+@@ -219,7 +218,8 @@ void selector_to_path(state *st)
>+
>+ #ifdef HAVE_PASSWD
>+ /* Virtual userdir (~user -> /home/user/public_gopher)? */
>+- if (*(st->user_dir) && sstrncmp(st->req_selector, "/~") == MATCH) {
>++ if (st->opt_personal_spaces && *(st->user_dir) &&
>++    sstrncmp(st->req_selector, "/~") == MATCH) {
>+
>+ /* Parse userdir login name & path */;
>+ sstrlcpy(buf, st->req_selector + 2);
>+@@ -453,6 +453,8 @@ void init_state(state *st)
>+ st->opt_shm = TRUE;
>+ st->opt_root = TRUE;
>+ st->opt_proxy = TRUE;
>++ st->opt_execmaps = TRUE;
>++ st->opt_personal_spaces = TRUE;
>+ st->debug = FALSE;
>+
>+ /* Load default suffix -> filetype mappings */
>+@@ -488,6 +490,9 @@ int main(int argc, char *argv[])
>+ char local[BUFSIZE];
>+ int dummy;
>+ #endif
>++#ifdef __OpenBSD__
>++ char pledges[256];
>++#endif
>+
>+ /* Get the name of this binary */
>+ if ((c = strrchr(argv[0], '/'))) sstrlcpy(self, c + 1);
>+@@ -506,6 +511,78 @@ int main(int argc, char *argv[])
>+ /* Open syslog() */
>+ if (st.opt_syslog) openlog(self, LOG_PID, LOG_DAEMON);
>+
>++#ifdef __OpenBSD__
>++ /* unveil(2) support */
>++ if (st.opt_execmaps) {
>++ /*
>++ * We can't really unveil(2) if the user is expecting to shell-out
>++                 * to an arbitrary command.
>++ */
>++ if (st.opt_syslog) {
>++ syslog(LOG_WARNING,
>++    "executable gophermaps enabled, can't unveil(2)");
>++ }
>++ } else {
>++ /* These paths must always available */
>++ if (unveil("/tmp", "rcw") == -1)
>++ die(&st, NULL, "unveil");
>++ if (unveil("/etc/hosts", "r") == -1)
>++ die(&st, NULL, "unveil");
>++ if (unveil("/etc/resolv.conf", "r") == -1)
>++ die(&st, NULL, "unveil");
>++ if (unveil(st.server_root, "r") == -1)
>++ die(&st, NULL, "unveil");
>++
>++ /*
>++ * Personal spaces serve "gopher://host/1/~user" from
>++ * "/home/user/public_gopher" and also needs to read the
>++ * password database to get the user's ~/public_gopher.
>++ */
>++ if (st.opt_personal_spaces) {
>++ syslog(LOG_WARNING,
>++    "personal gopherspaces enabled, unveil(2) /home and /etc/pwd.db");
>++ if (unveil("/home", "r") == -1)
>++ die(&st, NULL, "unveil");
>++
>++ if (unveil("/etc/pwd.db", "r") == -1)
>++ die(&st, NULL, "unveil");
>++ }
>++ }
>++
>++ /* pledge(2) support */
>++ if (st.opt_shm) {
>++ /*
>++ * pledge(2) never allows shared memory.
>++ */
>++ if (st.opt_syslog) {
>++ syslog(LOG_WARNING,
>++    "shared-memory enabled, can't pledge(2)");
>++ }
>++ } else {
>++ strlcpy(pledges,
>++    "stdio rpath wpath cpath tmppath inet sendfd recvfd tty proc",
>++    sizeof(pledges));
>++
>++ /* Executable maps shell-out using popen(3) */
>++ if (st.opt_execmaps) {
>++ strlcat(pledges, " exec", sizeof(pledges));
>++ syslog(LOG_WARNING,
>++    "executable gophermaps enabled, adding 'exec' to pledge(2)");
>++ }
>++
>++ /* Personal spaces require getpwnam(3) and getpwent(3) */
>++ if (st.opt_personal_spaces) {
>++ strlcat(pledges, " getpw", sizeof(pledges));
>++ syslog(LOG_WARNING,
>++    "personal gopherspaces enabled, adding 'getpw' to pledge(2)");
>++ }
>++
>++ if (pledge(pledges, NULL) == -1)
>++ die(&st, NULL, "pledge");
>++ }
>++
>++#endif
>++
>+ /* Check if TCP wrappers have something to say about this connection */
>+ #ifdef HAVE_LIBWRAP
>+ if (sstrncmp(st.req_remote_addr, UNKNOWN_ADDR) != MATCH &&
>+@@ -527,30 +604,31 @@ int main(int argc, char *argv[])
>+
>+ /* Try to get shared memory */
>+ #ifdef HAVE_SHMEM
>+- if ((shmid = shmget(SHM_KEY, sizeof(shm_state), IPC_CREAT | SHM_MODE)) == ERROR) {
>++ if (st.opt_shm) {
>++ if ((shmid = shmget(SHM_KEY, sizeof(shm_state), IPC_CREAT | SHM_MODE)) == ERROR) {
>+
>+- /* Getting memory failed -> delete the old allocation */
>+- shmctl(shmid, IPC_RMID, &shm_ds);
>+- shm = NULL;
>+- }
>+- else {
>+- /* Map shared memory */
>+- if ((shm = (shm_state *) shmat(shmid, (void *) 0, 0)) == (void *) ERROR)
>++ /* Getting memory failed -> delete the old allocation */
>++ shmctl(shmid, IPC_RMID, &shm_ds);
>+ shm = NULL;
>++ }
>++ else {
>++ /* Map shared memory */
>++ if ((shm = (shm_state *) shmat(shmid, (void *) 0, 0)) == (void *) ERROR)
>++ shm = NULL;
>+
>+- /* Initialize mapped shared memory */
>+- if (shm && shm->start_time == 0) {
>+- shm->start_time = time(NULL);
>++ /* Initialize mapped shared memory */
>++ if (shm && shm->start_time == 0) {
>++ shm->start_time = time(NULL);
>+
>+- /* Keep server platform & description in shm */
>+- platform(&st);
>+- sstrlcpy(shm->server_platform, st.server_platform);
>+- sstrlcpy(shm->server_description, st.server_description);
>++ /* Keep server platform & description in shm */
>++ platform(&st);
>++ sstrlcpy(shm->server_platform, st.server_platform);
>++ sstrlcpy(shm->server_description, st.server_description);
>++ }
>+ }
>++ } else {
>++ shm = NULL;
>+ }
>+-
>+- /* For debugging shared memory issues */
>+- if (!st.opt_shm) shm = NULL;
>+
>+ /* Get server platform and description */
>+ if (shm) {
>Index: patches/patch-gophernicus_h
>===================================================================
>RCS file: patches/patch-gophernicus_h
>diff -N patches/patch-gophernicus_h
>--- /dev/null 1 Jan 1970 00:00:00 -0000
>+++ patches/patch-gophernicus_h 1 Jan 2019 15:58:48 -0000
>@@ -0,0 +1,17 @@
>+$OpenBSD$
>+
>+Pledge and unveil. Based upon:
>+https://github.com/0x16h/gophernicus/commit/1f61d46a5ebb061d5862a6a61e296b473c169ec6
>+
>+Index: gophernicus.h
>+--- gophernicus.h.orig
>++++ gophernicus.h
>+@@ -351,6 +351,8 @@ typedef struct {
>+ char opt_shm;
>+ char opt_root;
>+ char opt_proxy;
>++ char opt_execmaps;
>++ char opt_personal_spaces;
>+ char debug;
>+ } state;
>+
>Index: patches/patch-menu_c
>===================================================================
>RCS file: patches/patch-menu_c
>diff -N patches/patch-menu_c
>--- /dev/null 1 Jan 1970 00:00:00 -0000
>+++ patches/patch-menu_c 1 Jan 2019 15:58:49 -0000
>@@ -0,0 +1,26 @@
>+$OpenBSD$
>+
>+Pledge and unveil. Based upon:
>+https://github.com/0x16h/gophernicus/commit/1f61d46a5ebb061d5862a6a61e296b473c169ec6
>+
>+Index: menu.c
>+--- menu.c.orig
>++++ menu.c
>+@@ -306,8 +306,15 @@ int gophermap(state *st, char *mapfile, int depth)
>+
>+ /* Debug output */
>+ if (st->debug) {
>+- if (exe) syslog(LOG_INFO, "parsing executable gophermap \"%s\"", mapfile);
>+- else syslog(LOG_INFO, "parsing static gophermap \"%s\"", mapfile);
>++ if (exe) {
>++ if (st->opt_execmaps)
>++ syslog(LOG_INFO, "parsing executable gophermap: %s", mapfile);
>++ else {
>++ syslog(LOG_INFO, "ignoring executable gophermap: %s", mapfile);
>++ return OK;
>++ }
>++ } else
>++ syslog(LOG_INFO, "parsing static gophermap: %s", mapfile);
>+ }
>+
>+ /* Try to execute or open the mapfile */
>Index: patches/patch-options_c
>===================================================================
>RCS file: patches/patch-options_c
>diff -N patches/patch-options_c
>--- /dev/null 1 Jan 1970 00:00:00 -0000
>+++ patches/patch-options_c 1 Jan 2019 15:58:52 -0000
>@@ -0,0 +1,17 @@
>+$OpenBSD$
>+
>+Pledge and unveil. Based upon:
>+https://github.com/0x16h/gophernicus/commit/1f61d46a5ebb061d5862a6a61e296b473c169ec6
>+
>+Index: options.c
>+--- options.c.orig
>++++ options.c
>+@@ -143,6 +143,8 @@ void parse_args(state *st, int argc, char *argv[])
>+ if (*optarg == 'm') { st->opt_shm = FALSE; break; }
>+ if (*optarg == 'r') { st->opt_root = FALSE; break; }
>+ if (*optarg == 'p') { st->opt_proxy = FALSE; break; }
>++ if (*optarg == 'e') { st->opt_execmaps = FALSE; break; }
>++ if (*optarg == 'u') { st->opt_personal_spaces = FALSE; break; }
>+ break;
>+
>+ case 'd': st->debug = TRUE; break;
>Index: pkg/README
>===================================================================
>RCS file: /cvs/ports/net/gophernicus/pkg/README,v
>retrieving revision 1.5
>diff -u -p -r1.5 README
>--- pkg/README 4 Sep 2018 12:46:17 -0000 1.5
>+++ pkg/README 1 Jan 2019 16:14:42 -0000
>@@ -10,7 +10,7 @@ Setting up a gopher site
> After installing the gophernicus package, edit /etc/inetd.conf and add
> the following, all on one line:
>
>-gopher stream tcp nowait _gophernicus ${TRUEPREFIX}/libexec/in.gophernicus in.gophernicus -h "hostname"
>+gopher stream tcp nowait _gophernicus ${TRUEPREFIX}/libexec/in.gophernicus in.gophernicus -ne -nm -nu -h hostname
>
> Replace "hostname" with the system's hostname, this should be valid
> and resolvable as it is used to construct links.
>@@ -24,3 +24,25 @@ only serve files which are world-readabl
> server process is not enough.
>
> For more information, see ${TRUEPREFIX}/share/doc/gophernicus/README.
>+
>+pledge(2) / unveil(2)
>+=====================
>+
>+The above inetd.conf(8) line starts ${PKGSTEM} in the most secure mode of
>+operation with regards to pledge(2) and unveil(2). In this configuration, some
>+features are disabled in favour of tighter security:
>+
>+ * The `-ne` argument disables executable gophermaps. If this argument is
>+   removed, unveil(2) is totally disabled, since we cannot know what paths an
>+   arbitrary popen(3)ed shell command might require. Additionally 'exec' is
>+   passed to pledge(2).
>+
>+ * The `-nm` argument disables shared-memory usage. If this argument is
>+   removed, pledge(2) will be totally disabled, as pledge(2) never allows
>+   shared memory.
>+
>+ * The `-nu` argument disables personal gopher spaces. If this argument is
>+   removed, we unveil(2) /home and /etc/pwd.db, and 'getpw' is passed to
>+   pledge(2).
>+
>+If any of these arguments are not present, ${PKGSTEM} will warn via syslog(3).
>
>--
>Best Regards
>Edd Barrett
>
>http://www.theunixzoo.co.uk
>
>

Reply | Threaded
Open this post in threaded view
|

Re: pledge/unveil: net/gophernicus

Edd Barrett-3
Hey,

On Tue, Jan 01, 2019 at 12:30:04PM -0700, Theo de Raadt wrote:
> In particular, specific pledges open up various system files, so those
> do not need to be opened via unveil.

Gah, you're right!

There's no need to unveil /etc/resolv.conf or /etc/pwd.db.

> The lack of pledge "dns" but opening of resolv.conf shows a further
> misunderstanding, and also a lack of testing.

It turns out DNS is not required at all! So that's why it was working
when I was testing.

I also realised that none of 'tmppath', 'wpath' or 'cpath' pledges are
required either.

This brings us to the diff below. Anything else fishy?

(Does anyone on-list host a real gopher site that they could test with?)

Cheers!


Index: Makefile
===================================================================
RCS file: /cvs/ports/net/gophernicus/Makefile,v
retrieving revision 1.17
diff -u -p -r1.17 Makefile
--- Makefile 4 Sep 2018 12:46:17 -0000 1.17
+++ Makefile 31 Dec 2018 19:30:19 -0000
@@ -3,7 +3,7 @@
 COMMENT= modern gopher server
 DISTNAME= gophernicus-2.5
 CATEGORIES= net
-REVISION= 0
+REVISION= 1
 
 HOMEPAGE= gopher://gophernicus.org/
 MAINTAINER = Brian Callahan <[hidden email]>
Index: patches/patch-README
===================================================================
RCS file: patches/patch-README
diff -N patches/patch-README
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ patches/patch-README 1 Jan 2019 16:01:17 -0000
@@ -0,0 +1,17 @@
+$OpenBSD$
+
+Pledge and unveil. Based upon:
+https://github.com/0x16h/gophernicus/commit/1f61d46a5ebb061d5862a6a61e296b473c169ec6
+
+Index: README
+--- README.orig
++++ README
+@@ -43,6 +43,8 @@ Command line options:
+     -nm           Disable shared memory use (for debugging)
+     -nr           Disable root user checking (for debugging)
+     -np           Disable HAproxy proxy protocol
++    -ne           Disable executable gophermaps
++    -nu           Disable personal gopherspaces
+
+     -d            Debug to syslog (not for production use)
+     -v            Display version number and build date
Index: patches/patch-gophernicus_c
===================================================================
RCS file: patches/patch-gophernicus_c
diff -N patches/patch-gophernicus_c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ patches/patch-gophernicus_c 1 Jan 2019 21:26:28 -0000
@@ -0,0 +1,155 @@
+$OpenBSD$
+
+Pledge and unveil. Based upon:
+https://github.com/0x16h/gophernicus/commit/1f61d46a5ebb061d5862a6a61e296b473c169ec6
+
+Index: gophernicus.c
+--- gophernicus.c.orig
++++ gophernicus.c
+@@ -219,7 +219,8 @@ void selector_to_path(state *st)
+
+ #ifdef HAVE_PASSWD
+ /* Virtual userdir (~user -> /home/user/public_gopher)? */
+- if (*(st->user_dir) && sstrncmp(st->req_selector, "/~") == MATCH) {
++ if (st->opt_personal_spaces && *(st->user_dir) &&
++    sstrncmp(st->req_selector, "/~") == MATCH) {
+
+ /* Parse userdir login name & path */;
+ sstrlcpy(buf, st->req_selector + 2);
+@@ -453,6 +454,8 @@ void init_state(state *st)
+ st->opt_shm = TRUE;
+ st->opt_root = TRUE;
+ st->opt_proxy = TRUE;
++ st->opt_execmaps = TRUE;
++ st->opt_personal_spaces = TRUE;
+ st->debug = FALSE;
+
+ /* Load default suffix -> filetype mappings */
+@@ -488,6 +491,9 @@ int main(int argc, char *argv[])
+ char local[BUFSIZE];
+ int dummy;
+ #endif
++#ifdef __OpenBSD__
++ char pledges[256];
++#endif
+
+ /* Get the name of this binary */
+ if ((c = strrchr(argv[0], '/'))) sstrlcpy(self, c + 1);
+@@ -506,6 +512,67 @@ int main(int argc, char *argv[])
+ /* Open syslog() */
+ if (st.opt_syslog) openlog(self, LOG_PID, LOG_DAEMON);
+
++#ifdef __OpenBSD__
++ /* unveil(2) support */
++ if (st.opt_execmaps) {
++ /*
++ * We can't really unveil(2) if the user is expecting to shell-out
++ * to an arbitrary command.
++ */
++ if (st.opt_syslog) {
++ syslog(LOG_WARNING,
++    "executable gophermaps enabled, can't unveil(2)");
++ }
++ } else {
++ /* Always unveil the server root */
++ if (unveil(st.server_root, "r") == -1)
++ die(&st, NULL, "unveil");
++
++ /*
++ * Personal spaces serve "gopher://host/1/~user" from
++ * "/home/user/public_gopher".
++ */
++ if (st.opt_personal_spaces) {
++ syslog(LOG_WARNING,
++    "personal gopherspaces enabled, unveil(2) /home");
++ if (unveil("/home", "r") == -1)
++ die(&st, NULL, "unveil");
++ }
++ }
++
++ /* pledge(2) support */
++ if (st.opt_shm) {
++ /*
++ * pledge(2) never allows shared memory.
++ */
++ if (st.opt_syslog) {
++ syslog(LOG_WARNING,
++    "shared-memory enabled, can't pledge(2)");
++ }
++ } else {
++ strlcpy(pledges,
++    "stdio rpath inet sendfd recvfd tty proc",
++    sizeof(pledges));
++
++ /* Executable maps shell-out using popen(3) */
++ if (st.opt_execmaps) {
++ strlcat(pledges, " exec", sizeof(pledges));
++ syslog(LOG_WARNING,
++    "executable gophermaps enabled, adding 'exec' to pledge(2)");
++ }
++
++ /* Personal spaces require getpwnam(3) and getpwent(3) */
++ if (st.opt_personal_spaces) {
++ strlcat(pledges, " getpw", sizeof(pledges));
++ syslog(LOG_WARNING,
++    "personal gopherspaces enabled, adding 'getpw' to pledge(2)");
++ }
++
++ if (pledge(pledges, NULL) == -1)
++ die(&st, NULL, "pledge");
++ }
++#endif
++
+ /* Check if TCP wrappers have something to say about this connection */
+ #ifdef HAVE_LIBWRAP
+ if (sstrncmp(st.req_remote_addr, UNKNOWN_ADDR) != MATCH &&
+@@ -527,30 +594,31 @@ int main(int argc, char *argv[])
+
+ /* Try to get shared memory */
+ #ifdef HAVE_SHMEM
+- if ((shmid = shmget(SHM_KEY, sizeof(shm_state), IPC_CREAT | SHM_MODE)) == ERROR) {
++ if (st.opt_shm) {
++ if ((shmid = shmget(SHM_KEY, sizeof(shm_state), IPC_CREAT | SHM_MODE)) == ERROR) {
+
+- /* Getting memory failed -> delete the old allocation */
+- shmctl(shmid, IPC_RMID, &shm_ds);
+- shm = NULL;
+- }
+- else {
+- /* Map shared memory */
+- if ((shm = (shm_state *) shmat(shmid, (void *) 0, 0)) == (void *) ERROR)
++ /* Getting memory failed -> delete the old allocation */
++ shmctl(shmid, IPC_RMID, &shm_ds);
+ shm = NULL;
++ }
++ else {
++ /* Map shared memory */
++ if ((shm = (shm_state *) shmat(shmid, (void *) 0, 0)) == (void *) ERROR)
++ shm = NULL;
+
+- /* Initialize mapped shared memory */
+- if (shm && shm->start_time == 0) {
+- shm->start_time = time(NULL);
++ /* Initialize mapped shared memory */
++ if (shm && shm->start_time == 0) {
++ shm->start_time = time(NULL);
+
+- /* Keep server platform & description in shm */
+- platform(&st);
+- sstrlcpy(shm->server_platform, st.server_platform);
+- sstrlcpy(shm->server_description, st.server_description);
++ /* Keep server platform & description in shm */
++ platform(&st);
++ sstrlcpy(shm->server_platform, st.server_platform);
++ sstrlcpy(shm->server_description, st.server_description);
++ }
+ }
++ } else {
++ shm = NULL;
+ }
+-
+- /* For debugging shared memory issues */
+- if (!st.opt_shm) shm = NULL;
+
+ /* Get server platform and description */
+ if (shm) {
Index: patches/patch-gophernicus_h
===================================================================
RCS file: patches/patch-gophernicus_h
diff -N patches/patch-gophernicus_h
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ patches/patch-gophernicus_h 1 Jan 2019 15:58:48 -0000
@@ -0,0 +1,17 @@
+$OpenBSD$
+
+Pledge and unveil. Based upon:
+https://github.com/0x16h/gophernicus/commit/1f61d46a5ebb061d5862a6a61e296b473c169ec6
+
+Index: gophernicus.h
+--- gophernicus.h.orig
++++ gophernicus.h
+@@ -351,6 +351,8 @@ typedef struct {
+ char opt_shm;
+ char opt_root;
+ char opt_proxy;
++ char opt_execmaps;
++ char opt_personal_spaces;
+ char debug;
+ } state;
+
Index: patches/patch-menu_c
===================================================================
RCS file: patches/patch-menu_c
diff -N patches/patch-menu_c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ patches/patch-menu_c 1 Jan 2019 15:58:49 -0000
@@ -0,0 +1,26 @@
+$OpenBSD$
+
+Pledge and unveil. Based upon:
+https://github.com/0x16h/gophernicus/commit/1f61d46a5ebb061d5862a6a61e296b473c169ec6
+
+Index: menu.c
+--- menu.c.orig
++++ menu.c
+@@ -306,8 +306,15 @@ int gophermap(state *st, char *mapfile, int depth)
+
+ /* Debug output */
+ if (st->debug) {
+- if (exe) syslog(LOG_INFO, "parsing executable gophermap \"%s\"", mapfile);
+- else syslog(LOG_INFO, "parsing static gophermap \"%s\"", mapfile);
++ if (exe) {
++ if (st->opt_execmaps)
++ syslog(LOG_INFO, "parsing executable gophermap: %s", mapfile);
++ else {
++ syslog(LOG_INFO, "ignoring executable gophermap: %s", mapfile);
++ return OK;
++ }
++ } else
++ syslog(LOG_INFO, "parsing static gophermap: %s", mapfile);
+ }
+
+ /* Try to execute or open the mapfile */
Index: patches/patch-options_c
===================================================================
RCS file: patches/patch-options_c
diff -N patches/patch-options_c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ patches/patch-options_c 1 Jan 2019 15:58:52 -0000
@@ -0,0 +1,17 @@
+$OpenBSD$
+
+Pledge and unveil. Based upon:
+https://github.com/0x16h/gophernicus/commit/1f61d46a5ebb061d5862a6a61e296b473c169ec6
+
+Index: options.c
+--- options.c.orig
++++ options.c
+@@ -143,6 +143,8 @@ void parse_args(state *st, int argc, char *argv[])
+ if (*optarg == 'm') { st->opt_shm = FALSE; break; }
+ if (*optarg == 'r') { st->opt_root = FALSE; break; }
+ if (*optarg == 'p') { st->opt_proxy = FALSE; break; }
++ if (*optarg == 'e') { st->opt_execmaps = FALSE; break; }
++ if (*optarg == 'u') { st->opt_personal_spaces = FALSE; break; }
+ break;
+
+ case 'd': st->debug = TRUE; break;
Index: pkg/README
===================================================================
RCS file: /cvs/ports/net/gophernicus/pkg/README,v
retrieving revision 1.5
diff -u -p -r1.5 README
--- pkg/README 4 Sep 2018 12:46:17 -0000 1.5
+++ pkg/README 1 Jan 2019 21:20:00 -0000
@@ -10,7 +10,7 @@ Setting up a gopher site
 After installing the gophernicus package, edit /etc/inetd.conf and add
 the following, all on one line:
 
-gopher stream tcp nowait _gophernicus ${TRUEPREFIX}/libexec/in.gophernicus in.gophernicus -h "hostname"
+gopher stream tcp nowait _gophernicus ${TRUEPREFIX}/libexec/in.gophernicus in.gophernicus -ne -nm -nu -h hostname
 
 Replace "hostname" with the system's hostname, this should be valid
 and resolvable as it is used to construct links.
@@ -24,3 +24,24 @@ only serve files which are world-readabl
 server process is not enough.
 
 For more information, see ${TRUEPREFIX}/share/doc/gophernicus/README.
+
+pledge(2) / unveil(2)
+=====================
+
+The above inetd.conf(8) line starts ${PKGSTEM} in the most secure mode of
+operation with regards to pledge(2) and unveil(2). In this configuration, some
+features are disabled in favour of tighter security:
+
+ * The `-ne` argument disables executable gophermaps. If this argument is
+   removed, unveil(2) is totally disabled, since we cannot know what paths an
+   arbitrary popen(3)ed shell command might require. Additionally 'exec' is
+   passed to pledge(2).
+
+ * The `-nm` argument disables shared-memory usage. If this argument is
+   removed, pledge(2) will be totally disabled, as pledge(2) never allows
+   shared memory.
+
+ * The `-nu` argument disables personal gopher spaces. If this argument is
+   removed, we unveil(2) /home and 'getpw' is passed to pledge(2).
+
+If any of these arguments are not present, ${PKGSTEM} will warn via syslog(3).

--
Best Regards
Edd Barrett

http://www.theunixzoo.co.uk

Reply | Threaded
Open this post in threaded view
|

Re: pledge/unveil: net/gophernicus

Edd Barrett-3
Hi,

On Tue, Jan 01, 2019 at 09:31:29PM +0000, Edd Barrett wrote:
> Gah, you're right!
>
> There's no need to unveil /etc/resolv.conf or /etc/pwd.db.
>
> ...

> It turns out DNS is not required at all! So that's why it was working
> when I was testing.
>
> I also realised that none of 'tmppath', 'wpath' or 'cpath' pledges are
> required either.
>
> This brings us to the diff below. Anything else fishy?

Has anyone else had a chance to look at this now that Theo's comments
are (I think/hope) addressed?

I'm not looking for OKs, but if it "looks good", I'd like to discuss the
changes with upsteam.

Cheers

--
Best Regards
Edd Barrett

http://www.theunixzoo.co.uk

Reply | Threaded
Open this post in threaded view
|

Re: pledge/unveil: net/gophernicus

Paco Esteban
On Fri, 18 Jan 2019, Edd Barrett wrote:

> Has anyone else had a chance to look at this now that Theo's comments
> are (I think/hope) addressed?
>
> I'm not looking for OKs, but if it "looks good", I'd like to discuss the
> changes with upsteam.

Just installed it on my personal server.
It seems to be working just fine. I could not find any issues, although
my testing was not so exhaustive.

Cheers,

--
Paco Esteban
https://onna.be/gpgkey.asc