Skip to content

Instantly share code, notes, and snippets.

@gagern
Created February 9, 2017 12:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gagern/00c6168af466964022715737fd41ea61 to your computer and use it in GitHub Desktop.
Save gagern/00c6168af466964022715737fd41ea61 to your computer and use it in GitHub Desktop.
Run apache as non-root on privileged port
/*
* Copyright 2013 Martin von Gagern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* DOCUMENTATION
*
* Grant permissions to this file via
* setcap CAP_SETFCAP,CAP_FOWNER,CAP_NET_BIND_SERVICE+p <filename>
*
* This file can serve as a wrapper around apache to allow apache
* processes run as non-root users to bind to privileged ports. In
* the normal course of events, this process will make the required
* capability CAP_NET_BIND_SERVICE effective before executing the
* apache binary (passing along command line arguments). Apache will
* inherit this capability and therefore be able to bind to port 80.
*
* For the above to work, the apache binary itself must have the
* CAP_NET_BIND_SERVICE capability in its CAP_INHERITABLE and
* CAP_EFFECTIVE sets. Otherwise, the capability won't be inherited,
* or won't get activated automatically. Since a package upgrade might
* break any marks the system administrator manually placed on the
* apache binary, this wrapper here is designed to check the binary
* for the presence of the required flags, and to add them if they are
* missing. This is the reason why this wrapper should be marked with
* CAP_SETFCAP (to change file capabilities) and CAP_FOWNER (to
* circumvent ownership tests in doing so) as well, as stated above.
*
* Benefits over a simple call like
* setcap CAP_NET_BIND_SERVICE+pe /usr/sbin/apache2
* are the fact that this approach here integrates better with
* automatic upgrades, since it can re-add the required flags
* automatically. Furthermore, the administrator can be more
* restrictive in granting execute permissions to this wrapper,
* without fear of breaking other uses of the apache2 binary, or
* loosing those changed permissions upon package update.
*
* Known security implications:
*
* - CAP_NET_BIND_SERVICE will be active throughout the Apache
* process. The normal mechanism of dropping privileges via a setuid
* call won't apply since the UID is non-zero to start with. Apache
* currently has no code to drop capabilities if run as non-root.
* Bits of code run in the apache process, like e.g. a PHP script,
* might explot this to run malicious services on privileged ports.
* To avoid this, a patched version of Apache is required, which
* drops capabilities instead of calling suid when run as non-root.
*
* - The wrapper will automatically add CAP_NET_BIND_SERVICE to the
* inheritable set of the apache binary. If some other process, by
* some other means, gained that capability, it too would be allowed
* to execute the apache binary with this capability in place. So
* any execute restrictions placed on this wrapper here might be
* circumvented that way. To fix this, the wrapper could be extended
* to automatically restrict execute permissions on the apache
* binary to those of the wrapper itself, but this would be going
* pretty far for an automatic operation, and might cause ugly
* surprises. In cases where users can launch arbitrary binaries
* with the CAP_NET_BIND_SERVICE capability in their inheritable
* set, the setcap call on the apache binary itself might be
* preferable, even though it means more work for the system
* administrator.
*/
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/capability.h>
#include <sys/types.h>
#include <sys/stat.h>
/* Error checking, reporting and program termination.
* Error messages will contain source file and line number.
*/
#define SRCLOC2(file, line) (file ":" #line)
#define SRCLOC1(file, line) SRCLOC2(file, line)
#define CHK(call, errval) \
do { if ((call) == errval) { \
perror(SRCLOC1(__FILE__, __LINE__)); \
exit(EXIT_FAILURE); \
} } while (0)
/* Location of the binary to wrap. */
const char apache[] = "/usr/sbin/apache2";
const cap_value_t net_bind_service[] = { CAP_NET_BIND_SERVICE };
const cap_value_t change_fcaps[] = { CAP_SETFCAP, CAP_FOWNER };
/* Check file capabilities of apache binary, fix them if required. */
static void setcap() {
int fd;
cap_t fcaps;
cap_flag_value_t valI, valE;
/* We use a file descriptor, to avoid races, and to automatically
resolve symlinks. O_PATH would seem nicer than O_RDONLY, but
doesn't work in experiments. */
CHK(fd = open(apache, O_RDONLY), -1);
fcaps = cap_get_fd(fd);
if (fcaps == NULL) {
if (errno == ENODATA) { /* This is the one error we expect and handle */
CHK(fcaps = cap_init(), NULL); /* new empty set of capabilities */
}
else {
CHK(NULL, NULL); /* Will report the error */
}
}
CHK(cap_get_flag(fcaps, CAP_NET_BIND_SERVICE, CAP_INHERITABLE, &valI), -1);
CHK(cap_get_flag(fcaps, CAP_NET_BIND_SERVICE, CAP_EFFECTIVE, &valE), -1);
if (valI != CAP_SET || valE != CAP_SET) {
/* We have to change file caps, and need some effective caps to do so */
cap_t pcaps1, pcaps2;
CHK(cap_set_flag(fcaps, CAP_INHERITABLE, 1, net_bind_service, CAP_SET), -1);
CHK(cap_set_flag(fcaps, CAP_EFFECTIVE, 1, net_bind_service, CAP_SET), -1);
CHK(pcaps1 = cap_get_proc(), NULL);
CHK(pcaps2 = cap_dup(pcaps1), NULL);
CHK(cap_set_flag(pcaps2, CAP_EFFECTIVE, 2, change_fcaps, CAP_SET), -1);
CHK(cap_set_proc(pcaps2), -1); /* gain change_fcaps */
cap_free(pcaps2);
CHK(cap_set_fd(fd, fcaps), -1); /* actually change the file caps */
CHK(cap_set_proc(pcaps1), -1); /* drop change_fcaps again */
cap_free(pcaps1);
}
cap_free(fcaps);
close(fd);
}
int main(int argc, char* argv[]) {
cap_t pcaps;
setcap();
CHK(argv[0] = strdup(apache), NULL);
argv[argc] = NULL;
CHK(pcaps = cap_init(), NULL); /* don't keep unneccessary caps */
CHK(cap_set_flag(pcaps, CAP_INHERITABLE, 1, net_bind_service, CAP_SET), -1);
CHK(cap_set_proc(pcaps), -1); /* inherit net_bind_service, drop rest */
CHK(execv(apache, argv), -1); /* execute apache, should not return */
return EXIT_FAILURE;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment