1
0
mirror of https://github.com/gophernicus/gophernicus.git synced 2024-11-03 04:27:17 -05:00

Add OpenBSD pledge(2) and unveil(2) support.

See the README for semantics.
This commit is contained in:
Edd Barrett 2019-04-21 12:58:16 +01:00
parent 403d031507
commit 5d05d61ba9
4 changed files with 154 additions and 24 deletions

View File

@ -53,6 +53,9 @@ are made.
-L text|file Set or load server location for caps.txt
-A admin Set admin email for caps.txt
-U paths Specify a colon-separated list of extra unveil(2) paths
(OpenBSD only).
-nv Disable virtual hosting
-nl Disable parent directory links
-nh Disable menu header (title)
@ -108,6 +111,32 @@ The `-nx` option prevents execution of any script or external file,
and the `-nu` option suppresses scanning for and serving of `~user`
directories (which are normally at `~/public_html/` for each user).
### OpenBSD-specific Security
If you are running Gophernicus on OpenBSD, you may (depending on what features
you want to use) be able to take advantage of unveil(2) and pledge(2).
If you run without executable map support (i.e. you run with `-nx`) then
unveil(2) will be enabled and the server root will automatically be unveiled.
If run with personal gopherspaces enabled (i.e. you run without `-nu`), then
the password database (`/etc/pwd.db`) will automatically be unveiled, but you
will have to manually unveil the filesystem path(s) from which to serve
personal gopherspaces (see `-U`).
Running with `-nm -nu -nx` results in the strictest set of pledge(2) promises.
If you have executable maps enabled (i.e. you run without `-nx`), then the
promises are relaxed to allow `exec`. If you have personal gopherspaces enabled
(i.e. you run without `-nu`), then the promises are relaxed to allow `getpw`.
If you have shared memory enabled (i.e. you run without `-nm`), then pledge(2)
support cannot be used at all.
In short, you probably want to run Gophernicus with `-nm -nu -nx` and then
remove the flags that would otherwise disable the features you want.
To see what is going on with regards to pledge(2) and unveil(2), run
Gophernicus with `-d` (to turn on debug logging) and look in your system logs.
## Gophermaps
By default all gopher menus are automatically generated from the

View File

@ -448,6 +448,11 @@ void init_state(state *st)
strclear(st->server_platform);
strclear(st->server_admin);
#ifdef __OpenBSD__
st->extra_unveil_paths = NULL;
#endif
/* Session */
st->session_timeout = DEFAULT_SESSION_TIMEOUT;
st->session_max_kbytes = DEFAULT_SESSION_MAX_KBYTES;
@ -498,13 +503,17 @@ int main(int argc, char *argv[])
#ifdef HAVE_SHMEM
struct shmid_ds shm_ds;
shm_state *shm;
int shmid;
int shmid = -1;
#endif
#ifdef ENABLE_HAPROXY1
char remote[BUFSIZE];
char local[BUFSIZE];
int dummy;
#endif
#ifdef __OpenBSD__
char pledges[256];
char *extra_unveil;
#endif
/* Get the name of this binary */
if ((c = strrchr(argv[0], '/'))) sstrlcpy(self, c + 1);
@ -523,6 +532,87 @@ int main(int argc, char *argv[])
/* Open syslog() */
if (st.opt_syslog) openlog(self, LOG_PID, LOG_DAEMON);
#ifdef __OpenBSD__
/* unveil(2) support.
*
* We only enable unveil(2) if the user isn't expecting to shell-out to
* arbitrary commands.
*/
if (st.opt_exec) {
if (st.extra_unveil_paths != NULL) {
die(&st, NULL, "-U and executable maps cannot co-exist");
}
if (st.debug)
syslog(LOG_INFO, "executable gophermaps are enabled, no unveil(2)");
} else {
if (unveil(st.server_root, "r") == -1)
die(&st, NULL, "unveil");
/*
* If we want personal gopherspaces, then we have to unveil(2) the user
* database. This isn't actually needed if pledge(2) is enabled, as the
* 'getpw' promise will ensure access to this file, but it doesn't hurt
* to unveil it anyway.
*/
if (st.opt_personal_spaces) {
if (st.debug)
syslog(LOG_INFO, "unveiling /etc/pwd.db");
if (unveil("/etc/pwd.db", "r") == -1)
die(&st, NULL, "unveil");
}
/* Any extra unveil paths that the user has specified */
char *p = st.extra_unveil_paths;
while (p != NULL) {
extra_unveil = strsep(&p, ":");
if (*extra_unveil == '\0')
continue; /* empty path */
if (st.debug)
syslog(LOG_INFO, "unveiling extra path: %s\n", extra_unveil);
if (unveil(extra_unveil, "r") == -1)
die(&st, NULL, "unveil");
}
if (unveil(NULL, NULL) == -1)
die(&st, NULL, "unveil");
}
/* pledge(2) support */
if (st.opt_shm) {
/* pledge(2) never allows shared memory */
if (st.debug)
syslog(LOG_INFO, "shared-memory enabled, can't pledge(2)");
} else {
strlcpy(pledges,
"stdio rpath inet sendfd recvfd proc",
sizeof(pledges));
/* Executable maps shell-out using popen(3) */
if (st.opt_exec) {
strlcat(pledges, " exec", sizeof(pledges));
if (st.debug) {
syslog(LOG_INFO,
"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));
if (st.debug) {
syslog(LOG_INFO,
"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 &&
@ -544,30 +634,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);
/* 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);
/* 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;
}
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);
/* Keep server platform & description in shm */
platform(&st);
sstrlcpy(shm->server_platform, st.server_platform);
sstrlcpy(shm->server_description, st.server_description);
}
}
/* For debugging shared memory issues */
if (!st.opt_shm) shm = NULL;
/* Get server platform and description */
if (shm) {

View File

@ -342,6 +342,10 @@ typedef struct {
srewrite rewrite[MAX_REWRITE];
int rewrite_count;
#ifdef __OpenBSD__
char *extra_unveil_paths;
#endif
/* Session */
int session_timeout;
int session_max_kbytes;

View File

@ -101,7 +101,11 @@ void parse_args(state *st, int argc, char *argv[])
int opt;
/* Parse args */
while ((opt = getopt(argc, argv, "h:p:T:r:t:g:a:c:u:m:l:w:o:s:i:k:f:e:R:D:L:A:P:n:dbv?-")) != ERROR) {
while ((opt = getopt(argc, argv,
#ifdef __OpenBSD__
"U:" /* extra unveil(2) paths are OpenBSD only */
#endif
"h:p:T:r:t:g:a:c:u:m:l:w:o:s:i:k:f:e:R:D:L:A:P:n:dbv?-")) != ERROR) {
switch(opt) {
case 'h': sstrlcpy(st->server_host, optarg); break;
case 'p': st->server_port = atoi(optarg); break;
@ -133,7 +137,9 @@ void parse_args(state *st, int argc, char *argv[])
case 'D': sstrlcpy(st->server_description, optarg); break;
case 'L': sstrlcpy(st->server_location, optarg); break;
case 'A': sstrlcpy(st->server_admin, optarg); break;
#ifdef __OpenBSD__
case 'U': st->extra_unveil_paths = optarg; break;
#endif
case 'n':
if (*optarg == 'v') { st->opt_vhost = FALSE; break; }
if (*optarg == 'l') { st->opt_parent = FALSE; break; }