sites

public wiki contents of suckless.org
git clone git://git.suckless.org/sites
Log | Files | Refs

commit f6eca21e35a8128be173496553bea71323f8b916
parent 099ce388993818f600c4e41172f71e20520dce7e
Author: Rogo <goryachev.romochka@gmail.com>
Date:   Thu, 12 Feb 2026 16:20:29 +0300

[quark][patches][noroot] noroot patch is more usable and safe now

Add quark-noroot-20260211-5ad0df9.diff patch which makes quark safe and
completly usable without root or special priviliges.

Fixes:
* Make internal path relative to the current working directory or matched
  vhost ones.
* Remove process-related calls, because process capabilities and
  credentials better be edited before executing quark anyway, and
  setpgrp(2) breaks shell usability.

Diffstat:
Mtools.suckless.org/quark/patches/noroot/index.md | 25++++++++++++++++++++++---
Atools.suckless.org/quark/patches/noroot/quark-noroot-20260211-5ad0df9.diff | 394+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 416 insertions(+), 3 deletions(-)

diff --git a/tools.suckless.org/quark/patches/noroot/index.md b/tools.suckless.org/quark/patches/noroot/index.md @@ -5,17 +5,36 @@ Description ----------- This patch removes the necessity and ability to run quark as root. quark will neither chroot(2) into the serving directory nor change the UID, -GID or ownership of the UNIX-domain socket file. +GID or ownership of the UNIX-domain socket file. It will only serve +files under its current working directory; absolute paths and ".." are +normalized into relative ones to the current working directory or +vhost ones. -As this patch removes security features from quark, it should not be -used for serving content to untrusted parties. +Process related functions, like 'setpgrp(2)' and 'setrlimit(2)', are also +removed because your system already has better tools to manage process +credentials and capabilities. For example, to bind quark on tcp port +80, you can set ambient capability `CAP_NET_BIND_SERVICE` from root, +cd into www-root, setuid into unpriviliged user and exec into quark. + +quark already normalizes URL paths for ".." directories, thus internal +path that is passed to filesystem-operating functions can safely be +constructed with "./" or vhost prefix. + +The only way to leave the working directory is through symbolic links, +but quark doesn't create any new files, so it's safe. Besides, symbolic +links are usefull for pointing to the files outside of www-root without +a need to bind-mount other directories, hardlinks or copy all the +files. chroot(2) limits you from doing all that, besides creating more +problems from changing process root directory. This patch has not been tested with a UNIX-domain socket file. Download -------- * [quark-noroot-20191003-3c7049e.diff](quark-noroot-20191003-3c7049e.diff) +* [quark-noroot-20260211-5ad0df9.diff](quark-noroot-20260211-5ad0df9.diff) Author ------ * Richard Ulmer <codesoap AT mailbox DOT org> +* Rogo <goryachev.romochka@gmail.com> diff --git a/tools.suckless.org/quark/patches/noroot/quark-noroot-20260211-5ad0df9.diff b/tools.suckless.org/quark/patches/noroot/quark-noroot-20260211-5ad0df9.diff @@ -0,0 +1,394 @@ +From 8d41e52f7a22b13008e111dbd5edbfc190f253ad Mon Sep 17 00:00:00 2001 +From: Rogo <goryachev.romochka@gmail.com> +Date: Wed, 11 Feb 2026 02:31:33 +0300 +Subject: [PATCH 1/4] applying quark-noroot-20191003-3c7049e.diff + +--- + main.c | 92 ++------------------------------------------------------- + quark.1 | 21 +------------ + sock.c | 15 ++-------- + sock.h | 2 +- + 4 files changed, 7 insertions(+), 123 deletions(-) + +diff --git a/main.c b/main.c +index 1e88536..d4969fb 100644 +--- a/main.c ++++ b/main.c +@@ -1,8 +1,7 @@ + /* See LICENSE file for copyright and license details. */ + #include <errno.h> +-#include <grp.h> + #include <limits.h> +-#include <pwd.h> ++#include <netinet/in.h> + #include <regex.h> + #include <signal.h> + #include <stddef.h> +@@ -54,7 +53,7 @@ handlesignals(void(*hdl)(int)) + static void + usage(void) + { +- const char *opts = "[-u user] [-g group] [-n num] [-d dir] [-l] " ++ const char *opts = "[-n num] [-d dir] [-l] " + "[-i file] [-v vhost] ... [-m map] ..."; + + die("usage: %s -p port [-h host] %s\n" +@@ -65,9 +64,6 @@ usage(void) + int + main(int argc, char *argv[]) + { +- struct group *grp = NULL; +- struct passwd *pwd = NULL; +- struct rlimit rlim; + struct server srv = { + .docindex = "index.html", + }; +@@ -80,16 +76,11 @@ main(int argc, char *argv[]) + size_t nthreads = 4; + size_t nslots = 64; + char *servedir = "."; +- char *user = "nobody"; +- char *group = "nogroup"; + + ARGBEGIN { + case 'd': + servedir = EARGF(usage()); + break; +- case 'g': +- group = EARGF(usage()); +- break; + case 'h': + srv.host = EARGF(usage()); + break; +@@ -134,9 +125,6 @@ main(int argc, char *argv[]) + case 'U': + udsname = EARGF(usage()); + break; +- case 'u': +- user = EARGF(usage()); +- break; + case 'v': + if (spacetok(EARGF(usage()), tok, 4) || !tok[0] || !tok[1] || + !tok[2]) { +@@ -178,42 +166,10 @@ main(int argc, char *argv[]) + } + } + +- /* validate user and group */ +- errno = 0; +- if (!user || !(pwd = getpwnam(user))) { +- die("getpwnam '%s': %s", user ? user : "null", +- errno ? strerror(errno) : "Entry not found"); +- } +- errno = 0; +- if (!group || !(grp = getgrnam(group))) { +- die("getgrnam '%s': %s", group ? group : "null", +- errno ? strerror(errno) : "Entry not found"); +- } +- + /* open a new process group */ + setpgid(0, 0); + + handlesignals(sigcleanup); +- +- /* +- * set the maximum number of open file descriptors as needed +- * - 3 initial fd's +- * - nthreads fd's for the listening socket +- * - (nthreads * nslots) fd's for the connection-fd +- * - (5 * nthreads) fd's for general purpose thread-use +- */ +- rlim.rlim_cur = rlim.rlim_max = 3 + nthreads + nthreads * nslots + +- 5 * nthreads; +- if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) { +- if (errno == EPERM) { +- die("You need to run as root or have " +- "CAP_SYS_RESOURCE set, or are asking for more " +- "file descriptors than the system can offer"); +- } else { +- die("setrlimit:"); +- } +- } +- + /* + * create the (non-blocking) listening socket + * +@@ -228,7 +184,7 @@ main(int argc, char *argv[]) + * kernel by changing epoll-sheduling from a FIFO- to a + * LIFO-model, especially as it doesn't affect performance + */ +- insock = udsname ? sock_get_uds(udsname, pwd->pw_uid, grp->gr_gid) : ++ insock = udsname ? sock_get_uds(udsname) : + sock_get_ips(srv.host, srv.port); + if (sock_set_nonblocking(insock)) { + return 1; +@@ -287,51 +243,9 @@ main(int argc, char *argv[]) + eunveil(servedir, "r"); + eunveil(NULL, NULL); + +- /* chroot */ + if (chdir(servedir) < 0) { + die("chdir '%s':", servedir); + } +- if (chroot(".") < 0) { +- if (errno == EPERM) { +- die("You need to run as root or have " +- "CAP_SYS_CHROOT set"); +- } else { +- die("chroot:"); +- } +- } +- +- /* drop root */ +- if (pwd->pw_uid == 0 || grp->gr_gid == 0) { +- die("Won't run under root %s for hopefully obvious reasons", +- (pwd->pw_uid == 0) ? (grp->gr_gid == 0) ? +- "user and group" : "user" : "group"); +- } +- +- if (setgroups(1, &(grp->gr_gid)) < 0) { +- if (errno == EPERM) { +- die("You need to run as root or have " +- "CAP_SETGID set"); +- } else { +- die("setgroups:"); +- } +- } +- if (setgid(grp->gr_gid) < 0) { +- if (errno == EPERM) { +- die("You need to run as root or have " +- "CAP_SETGID set"); +- } else { +- die("setgid:"); +- } +- +- } +- if (setuid(pwd->pw_uid) < 0) { +- if (errno == EPERM) { +- die("You need to run as root or have " +- "CAP_SETUID set"); +- } else { +- die("setuid:"); +- } +- } + + if (udsname) { + epledge("stdio rpath proc unix", NULL); +diff --git a/quark.1 b/quark.1 +index d752cc7..93126b5 100644 +--- a/quark.1 ++++ b/quark.1 +@@ -46,13 +46,8 @@ hidden files and directories. + .It Fl d Ar dir + Serve + .Ar dir +-after chrooting into it. ++after changing into it. + The default is ".". +-.It Fl g Ar group +-Set group ID when dropping privileges, and in socket mode the group of the +-socket file, to the ID of +-.Ar group . +-The default is "nogroup". + .It Fl h Ar host + Use + .Ar host +@@ -94,20 +89,6 @@ redirects on non-standard ports. + Create the UNIX-domain socket + .Ar file , + listen on it for incoming connections and remove it on exit. +-.It Fl s Ar num +-Set the number of connection slots per worker thread to +-.Ar num . +-The default is 64. +-.It Fl t Ar num +-Set the number of worker threads to +-.Ar num . +-The default is 4. +-.It Fl u Ar user +-Set user ID when dropping privileges, +-and in socket mode the user of the socket file, +-to the ID of +-.Ar user . +-The default is "nobody". + .It Fl v Ar vhost + Add the virtual host specified by + .Ar vhost , +diff --git a/sock.c b/sock.c +index ebbbf65..bff1b38 100644 +--- a/sock.c ++++ b/sock.c +@@ -70,14 +70,13 @@ sock_get_ips(const char *host, const char* port) + } + + int +-sock_get_uds(const char *udsname, uid_t uid, gid_t gid) ++sock_get_uds(const char *udsname) + { + struct sockaddr_un addr = { + .sun_family = AF_UNIX, + }; + size_t udsnamelen; +- int insock, sockmode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | +- S_IROTH | S_IWOTH; ++ int insock; + + if ((insock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { + die("socket:"); +@@ -97,16 +96,6 @@ sock_get_uds(const char *udsname, uid_t uid, gid_t gid) + die("listen:"); + } + +- if (chmod(udsname, sockmode) < 0) { +- sock_rem_uds(udsname); +- die("chmod '%s':", udsname); +- } +- +- if (chown(udsname, uid, gid) < 0) { +- sock_rem_uds(udsname); +- die("chown '%s':", udsname); +- } +- + return insock; + } + +diff --git a/sock.h b/sock.h +index 22ae303..765016d 100644 +--- a/sock.h ++++ b/sock.h +@@ -7,8 +7,8 @@ + #include <sys/types.h> + + int sock_get_ips(const char *, const char *); +-int sock_get_uds(const char *, uid_t, gid_t); + void sock_rem_uds(const char *); ++int sock_get_uds(const char *); + int sock_set_timeout(int, int); + int sock_set_nonblocking(int); + int sock_get_inaddr_str(const struct sockaddr_storage *, char *, size_t); +-- +2.52.0 + + +From 56bde27402884fd051cbe9e5def222b1d4fb5fdb Mon Sep 17 00:00:00 2001 +From: Rogo <goryachev.romochka@gmail.com> +Date: Wed, 11 Feb 2026 02:44:40 +0300 +Subject: [PATCH 2/4] Remove setrlimit() code + +User other tools to setup process limits, capabilities, credentials. +--- + main.c | 28 ---------------------------- + 1 file changed, 28 deletions(-) + +diff --git a/main.c b/main.c +index d4969fb..8bb7023 100644 +--- a/main.c ++++ b/main.c +@@ -211,34 +211,6 @@ main(int argc, char *argv[]) + die("signal: Failed to set SIG_IGN on SIGPIPE"); + } + +- /* +- * try increasing the thread-limit by the number +- * of threads we need (which is the only reliable +- * workaround I know given the thread-limit is per user +- * rather than per process), but ignore EPERM errors, +- * because this most probably means the user has already +- * set the value to the kernel's limit, and there's not +- * much we can do in any other case. +- * There's also no danger of overflow as the value +- * returned by getrlimit() is way below the limits of the +- * rlim_t datatype. +- */ +- if (getrlimit(RLIMIT_NPROC, &rlim) < 0) { +- die("getrlimit:"); +- } +- if (rlim.rlim_max == RLIM_INFINITY) { +- if (rlim.rlim_cur != RLIM_INFINITY) { +- /* try increasing current limit by nthreads */ +- rlim.rlim_cur += nthreads; +- } +- } else { +- /* try increasing current and hard limit by nthreads */ +- rlim.rlim_cur = rlim.rlim_max += nthreads; +- } +- if (setrlimit(RLIMIT_NPROC, &rlim) < 0 && errno != EPERM) { +- die("setrlimit()"); +- } +- + /* limit ourselves to reading the servedir and block further unveils */ + eunveil(servedir, "r"); + eunveil(NULL, NULL); +-- +2.52.0 + + +From a31e262d003b0ffdc4bdeff8d6690cd11d5e2f45 Mon Sep 17 00:00:00 2001 +From: Rogo <goryachev.romochka@gmail.com> +Date: Wed, 11 Feb 2026 07:13:49 +0300 +Subject: [PATCH 3/4] Make internal_path relative to "." or vhost dir + +Absolute pathnames for filesystem paths only work with chroot. + +Without even dealing with other problems that abuse of chroot for +"sandboxing" introduces, it makes impossible to run quark as unpriviliged +user. URL path is already sanitized for ".." dirs, and quark doesn't +create new files and links. +--- + http.c | 8 ++------ + 1 file changed, 2 insertions(+), 6 deletions(-) + +diff --git a/http.c b/http.c +index 36f8b1c..9d17cfb 100644 +--- a/http.c ++++ b/http.c +@@ -776,16 +776,12 @@ http_prepare_response(const struct request *req, struct response *res, + * path and the virtual host while ignoring query and fragment + * (valid according to RFC 3986) + */ +- if (esnprintf(res->internal_path, sizeof(res->internal_path), "/%s/%s", +- (srv->vhost && res->vhost) ? res->vhost->dir : "", ++ if (esnprintf(res->internal_path, sizeof(res->internal_path), "%s/%s", ++ (srv->vhost && res->vhost) ? res->vhost->dir : ".", + res->path)) { + s = S_REQUEST_TOO_LARGE; + goto err; + } +- if ((tmps = path_normalize(res->internal_path, NULL))) { +- s = tmps; +- goto err; +- } + if (stat(res->internal_path, &st) < 0) { + s = (errno == EACCES) ? S_FORBIDDEN : S_NOT_FOUND; + goto err; +-- +2.52.0 + + +From c67c92dcbdc00f5a16179da8d46525c70a28d219 Mon Sep 17 00:00:00 2001 +From: Rogo <goryachev.romochka@gmail.com> +Date: Wed, 11 Feb 2026 07:25:23 +0300 +Subject: [PATCH 4/4] Remove "setpgid(0, 0)"; stop breaking ^C behavior in + shell + +--- + main.c | 3 --- + 1 file changed, 3 deletions(-) + +diff --git a/main.c b/main.c +index 8bb7023..776fb13 100644 +--- a/main.c ++++ b/main.c +@@ -166,9 +166,6 @@ main(int argc, char *argv[]) + } + } + +- /* open a new process group */ +- setpgid(0, 0); +- + handlesignals(sigcleanup); + /* + * create the (non-blocking) listening socket +-- +2.52.0 +