diff --git a/CMakeLists.txt b/CMakeLists.txt index 2cac7749..506a7b55 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -495,6 +495,9 @@ CHECK_INCLUDE_FILE(sys/stat.h LWS_HAVE_SYS_STAT_H) CHECK_INCLUDE_FILE(sys/types.h LWS_HAVE_SYS_TYPES_H) CHECK_INCLUDE_FILE(unistd.h LWS_HAVE_UNISTD_H) CHECK_INCLUDE_FILE(vfork.h LWS_HAVE_VFORK_H) +CHECK_INCLUDE_FILE(sys/capability.h LWS_HAVE_SYS_CAPABILITY_H) + +CHECK_LIBRARY_EXISTS(cap cap_set_flag "" LWS_HAVE_LIBCAP) if (LWS_WITH_LIBUV) CHECK_INCLUDE_FILE(uv-version.h LWS_HAVE_UV_VERSION_H) @@ -1016,6 +1019,12 @@ if (UNIX) list(APPEND LIB_LIST m) endif() +if (LWS_HAVE_LIBCAP) + list(APPEND LIB_LIST cap ) +endif() + + + # Setup the linking for all libs. foreach (lib ${LWS_LIBRARIES}) target_link_libraries(${lib} ${LIB_LIST}) @@ -1767,6 +1776,8 @@ message(" LWS_WITH_ZIP_FOPS = ${LWS_WITH_ZIP_FOPS}") message(" LWS_AVOID_SIGPIPE_IGN = ${LWS_AVOID_SIGPIPE_IGN}") message(" LWS_WITH_STATS = ${LWS_WITH_STATS}") message(" LWS_WITH_SOCKS5 = ${LWS_WITH_SOCKS5}") +message(" LWS_HAVE_SYS_CAPABILITY_H = ${LWS_HAVE_SYS_CAPABILITY_H}") +message(" LWS_HAVE_LIBCAP = ${LWS_HAVE_LIBCAP}") message("---------------------------------------------------------------------") diff --git a/README.build.md b/README.build.md index ca9c528e..dd3494aa 100644 --- a/README.build.md +++ b/README.build.md @@ -122,12 +122,25 @@ and libnsl, and only builds in 64bit mode. $ make ``` +@section lcap Linux Capabilities + +On Linux, lws now lets you retain selected root capabilities when dropping +privileges. If libcap-dev or similar package is installed providing +sys/capabilities.h, and libcap or similar package is installed providing +libcap.so, CMake will enable the capability features. + +The context creation info struct .caps[] and .count_caps members can then +be set by user code to enable selected root capabilities to survive the +transition to running under an unprivileged user. + @section cmq Quirk of cmake When changing cmake options, for some reason the only way to get it to see the changes sometimes is delete the contents of your build directory and do the cmake from scratch. +deleting build/CMakeCache.txt may be enough. + @section cmw Building on Windows (Visual Studio) diff --git a/lib/context.c b/lib/context.c index e1d84796..248e29b3 100644 --- a/lib/context.c +++ b/lib/context.c @@ -892,6 +892,11 @@ lws_create_context(struct lws_context_creation_info *info) context->uid = info->uid; context->gid = info->gid; +#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP) + memcpy(context->caps, info->caps, sizeof(context->caps)); + context->count_caps = info->count_caps; +#endif + /* * drop any root privs for this process * to listen on port < 1023 we would have needed root, but now we are diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index 53cb12b0..88f283a0 100755 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -2220,6 +2220,11 @@ lws_finalize_startup(struct lws_context *context) info.uid = context->uid; info.gid = context->gid; +#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP) + memcpy(info.caps, context->caps, sizeof(info.caps)); + info.count_caps = context->count_caps; +#endif + if (lws_check_opt(context->options, LWS_SERVER_OPTION_EXPLICIT_VHOSTS)) lws_plat_drop_app_privileges(&info); diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index 4866ad38..b7385547 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -102,6 +102,9 @@ struct sockaddr_in; #else /* NOT WIN32 */ #include +#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP) +#include +#endif #if defined(__NetBSD__) || defined(__FreeBSD__) #include @@ -1976,6 +1979,18 @@ struct lws_context_creation_info { * If proxy auth is required, use format "username:password\@server:port" */ unsigned int socks_proxy_port; /**< VHOST: If socks_proxy_address was non-NULL, uses this port */ +#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP) + cap_value_t caps[4]; + /**< CONTEXT: array holding Linux capabilities you want to + * continue to be available to the server after it transitions + * to a noprivileged user. Usually none are needed but for, eg, + * .bind_iface, CAP_NET_RAW is required. This gives you a way + * to still have the capability but drop root. + */ + char count_caps; + /**< CONTEXT: count of Linux capabilities in .caps[]. 0 means + * no capabilities will be inherited from root (the default) */ +#endif /* Add new things just above here ---^ * This is part of the ABI, don't needlessly break compatibility diff --git a/lib/lws-plat-unix.c b/lib/lws-plat-unix.c index b2679b65..ae73dfc4 100644 --- a/lib/lws-plat-unix.c +++ b/lib/lws-plat-unix.c @@ -279,9 +279,29 @@ lws_plat_set_socket_options(struct lws_vhost *vhost, int fd) return 0; } +#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP) +static void +_lws_plat_apply_caps(int mode, cap_value_t *cv, int count) +{ + cap_t caps = cap_get_proc(); + + if (!count) + return; + + cap_set_flag(caps, mode, count, cv, CAP_SET); + cap_set_proc(caps); + prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0); + cap_free(caps); +} +#endif + LWS_VISIBLE void lws_plat_drop_app_privileges(struct lws_context_creation_info *info) { +#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP) + int n; +#endif + if (info->gid != -1) if (setgid(info->gid)) lwsl_warn("setgid: %s\n", strerror(LWS_ERRNO)); @@ -290,11 +310,25 @@ lws_plat_drop_app_privileges(struct lws_context_creation_info *info) struct passwd *p = getpwuid(info->uid); if (p) { + +#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP) + _lws_plat_apply_caps(CAP_PERMITTED, info->caps, info->count_caps); +#endif + initgroups(p->pw_name, info->gid); if (setuid(info->uid)) lwsl_warn("setuid: %s\n", strerror(LWS_ERRNO)); else lwsl_notice("Set privs to user '%s'\n", p->pw_name); + +#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP) + _lws_plat_apply_caps(CAP_EFFECTIVE, info->caps, info->count_caps); + + if (info->count_caps) + for (n = 0; n < info->count_caps; n++) + lwsl_notice(" RETAINING CAPABILITY %d\n", (int)info->caps[n]); +#endif + } else lwsl_warn("getpwuid: unable to find uid %d", info->uid); } diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index dcee1608..48940bd6 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -935,6 +935,11 @@ struct lws_context { const struct lws_protocol_vhost_options *reject_service_keywords; lws_reload_func deprecation_cb; +#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP) + cap_value_t caps[4]; + char count_caps; +#endif + #if defined(LWS_USE_LIBEV) lws_ev_signal_cb_t * lws_ev_sigint_cb; #endif diff --git a/lws_config.h.in b/lws_config.h.in index c9a0a1ff..d7092a1a 100644 --- a/lws_config.h.in +++ b/lws_config.h.in @@ -136,6 +136,9 @@ #cmakedefine LWS_WITH_STATS #cmakedefine LWS_WITH_SOCKS5 +#cmakedefine LWS_HAVE_SYS_CAPABILITY_H +#cmakedefine LWS_HAVE_LIBCAP + /* OpenSSL various APIs */ #cmakedefine LWS_HAVE_TLS_CLIENT_METHOD