From: flu0r1ne Date: Fri, 29 Sep 2023 08:07:54 +0000 (-0500) Subject: Fix Capability Management, Retain CAP_NET_ADMIN X-Git-Tag: v0.96~21^2~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1b519cde3e1e23b11a2ab94c43b0d10ae5a9e588;p=thirdparty%2Fmtr.git Fix Capability Management, Retain CAP_NET_ADMIN Modify the capability-dropping logic to specifically retain CAP_NET_ADMIN if it is initially provided, in adherence to least-privilege principles. Details: 1. Update the `drop_excess_capabilities` function to only drop capabilities that are unnecessary, retaining CAP_NET_ADMIN when needed for setting the socket mark. 2. Introduce logic in `set_socket_mark` to temporarily elevate CAP_NET_ADMIN into the effective set for the duration of the packet mark setting operation. --- diff --git a/packet/construct_unix.c b/packet/construct_unix.c index e09d705..a9d6d91 100644 --- a/packet/construct_unix.c +++ b/packet/construct_unix.c @@ -32,6 +32,10 @@ #define SOL_IP IPPROTO_IP #endif +#ifdef HAVE_LIBCAP +#include +#endif + /* A source of data for computing a checksum */ struct checksum_source_t { const void *data; @@ -278,6 +282,67 @@ int construct_udp6_packet( return 0; } +#define SET_MARK_N_ADDED_CAPS 1 + +/* Set the socket mark */ +static int set_socket_mark(int socket, unsigned int mark) { + int result = -1; + + // Add CAP_NET_ADMIN to the effective set if libcap is present +#ifdef HAVE_LIBCAP + cap_t cap = NULL; + static cap_value_t cap_add[SET_MARK_N_ADDED_CAPS] = { CAP_NET_ADMIN }; + + // Get the capabilities of the current process + cap = cap_get_proc(); + if (cap == NULL) { + goto cleanup_and_exit; + } + + // Set the required capability flag + if (cap_set_flag(cap, CAP_EFFECTIVE, SET_MARK_N_ADDED_CAPS, cap_add, + CAP_SET)) { + goto cleanup_and_exit; + } + + // Apply the modified capabilities to the current process + if (cap_set_proc(cap)) { + goto cleanup_and_exit; + } +#endif /* ifdef HAVE_LIBPCAP */ + + // Set the socket mark + if (setsockopt(socket, SOL_SOCKET, SO_MARK, &mark, sizeof(mark))) { + goto cleanup_and_exit; + } + + // Drop CAP_NET_ADMIN from the effective set if libcap is present +#ifdef HAVE_LIBCAP + + // Clear the CAP_NET_ADMIN capability flag + if (cap_set_flag(cap, CAP_EFFECTIVE, SET_MARK_N_ADDED_CAPS, cap_add, + CAP_CLEAR)) { + goto cleanup_and_exit; + } + + // Apply the modified capabilities to the current process + if (cap_set_proc(cap)) { + goto cleanup_and_exit; + } +#endif /* ifdef HAVE_LIBPCAP */ + + result = 0; // Success + +cleanup_and_exit: + +#ifdef HAVE_LIBCAP + cap_free(cap); +#endif /* ifdef HAVE_LIBPCAP */ + + return result; +} + + /* Set the socket options for an outgoing stream protocol socket based on the packet parameters. @@ -341,8 +406,7 @@ int set_stream_socket_options( } #ifdef SO_MARK if (param->routing_mark) { - if (setsockopt(stream_socket, SOL_SOCKET, - SO_MARK, ¶m->routing_mark, sizeof(int))) { + if (set_socket_mark(stream_socket, param->routing_mark)) { return -1; } } @@ -360,6 +424,7 @@ int set_stream_socket_options( return 0; } + /* Open a TCP or SCTP socket, respecting the probe paramters as much as we can, and use it as an outgoing probe. @@ -577,8 +642,7 @@ int construct_ip4_packet( */ #ifdef SO_MARK if (param->routing_mark) { - if (setsockopt(send_socket, SOL_SOCKET, - SO_MARK, ¶m->routing_mark, sizeof(int))) { + if (set_socket_mark(send_socket, param->routing_mark)) { return -1; } } @@ -750,9 +814,7 @@ int construct_ip6_packet( } #ifdef SO_MARK if (param->routing_mark) { - if (setsockopt(send_socket, - SOL_SOCKET, SO_MARK, ¶m->routing_mark, - sizeof(int))) { + if (set_socket_mark(send_socket, param->routing_mark)) { return -1; } } diff --git a/packet/packet.c b/packet/packet.c index 3821d91..ba37d25 100644 --- a/packet/packet.c +++ b/packet/packet.c @@ -35,15 +35,75 @@ #include "wait.h" +#define N_ENTRIES(array) \ + (sizeof((array)) / sizeof(*(array))) + +#ifdef HAVE_LIBCAP +static +void drop_excess_capabilities() { + cap_value_t cap_permitted[] = { +#ifdef SO_MARK + /* + By default, the root user has all capabilities, which poses a security risk. + Since the socket has already been opened, we only need CAP_NET_ADMIN to set + the fwmark. This capability must remain in the permitted set so that it can + be added to the effective set when needed. + */ + CAP_NET_ADMIN +#endif /* ifdef SOMARK */ + }; + + cap_t current_cap = cap_get_proc(); + cap_t wanted_cap = cap_get_proc(); + + if(!current_cap || !wanted_cap) { + goto pcap_error; + } + + // Clear all capabilities from the 'wanted_cap' set + if(cap_clear(wanted_cap)) { + goto pcap_error; + } + + // Retain only the necessary capabilities defined in 'cap_permitted' in the permitted set. + // This approach ensures the principle of least privilege. + // If the user has dropped capabilities, the code assumes those features will not be needed. + for(unsigned i = 0; i < N_ENTRIES(cap_permitted); i++) { + cap_flag_value_t is_set; + + if(cap_get_flag(current_cap, cap_permitted[i], CAP_PERMITTED, &is_set)) { + goto pcap_error; + } + + if(cap_set_flag(wanted_cap, CAP_PERMITTED, 1, &cap_permitted[i], is_set)) { + goto pcap_error; + } + } + + // Update the process's capabilities to match 'wanted_cap' + if(cap_set_proc(wanted_cap)) { + goto pcap_error; + } + + if(cap_free(current_cap) || cap_free(wanted_cap)) { + goto pcap_error; + } + + return; + +pcap_error: + + cap_free(current_cap); + cap_free(wanted_cap); + error(EXIT_FAILURE, errno, "Failed to drop capabilities"); +} +#endif /* ifdef HAVE_LIBCAP */ + /* Drop SUID privileges. To be used after acquiring raw sockets. */ static int drop_elevated_permissions( void) { -#ifdef HAVE_LIBCAP - cap_t cap; -#endif - /* Drop any suid permissions granted */ if (setgid(getgid()) || setuid(getuid())) { return -1; @@ -55,19 +115,9 @@ int drop_elevated_permissions( /* Drop all process capabilities. - This will revoke anything granted by a commandline 'setcap' */ #ifdef HAVE_LIBCAP - cap = cap_get_proc(); - if (cap == NULL) { - return -1; - } - if (cap_clear(cap)) { - return -1; - } - if (cap_set_proc(cap)) { - return -1; - } + drop_excess_capabilities(); #endif return 0;