Skip to content

Commit

Permalink
Merge pull request #487 from flu0r1ne/adjust-capability-handling
Browse files Browse the repository at this point in the history
Adjust capability handling
  • Loading branch information
rewolff authored Oct 3, 2023
2 parents 74d312d + 54abd96 commit 4a1939a
Show file tree
Hide file tree
Showing 3 changed files with 204 additions and 26 deletions.
119 changes: 108 additions & 11 deletions packet/construct_unix.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

#include "construct_unix.h"

#include "utils.h"

#include <errno.h>
#include <stdio.h>
#include <string.h>
Expand All @@ -32,6 +34,10 @@
#define SOL_IP IPPROTO_IP
#endif

#ifdef HAVE_LIBCAP
#include <sys/capability.h>
#endif

/* A source of data for computing a checksum */
struct checksum_source_t {
const void *data;
Expand Down Expand Up @@ -278,6 +284,102 @@ int construct_udp6_packet(
return 0;
}

/*
This defines a common interface which elevates privileges on
platforms with LIBCAP and acts as a NOOP on platforms without
it.
*/
#ifdef HAVE_LIBCAP

typedef cap_value_t mayadd_cap_value_t;
#define MAYADD_CAP_NET_RAW CAP_NET_RAW
#define MAYADD_CAP_NET_ADMIN CAP_NET_ADMIN

#else /* ifdef HAVE_LIBCAP */

typedef int mayadd_cap_value_t;
#define MAYADD_CAP_NET_RAW ((mayadd_cap_value_t) 0)
#define MAYADD_CAP_NET_ADMIN ((mayadd_cap_value_t) 0)

#endif /* ifdef HAVE_LIBCAP */

UNUSED static
int set_privileged_socket_opt(int socket, int option_name,
void const * option_value, socklen_t option_len,
UNUSED mayadd_cap_value_t required_cap) {

int result = -1;

// Add CAP_NET_ADMIN to the effective set if libcap is present
#ifdef HAVE_LIBCAP
static cap_value_t cap_add[1];
cap_add[0] = required_cap;

// Get the capabilities of the current process
cap_t cap = cap_get_proc();
if (cap == NULL) {
goto cleanup_and_exit;
}

// Set the required capability flag
if (cap_set_flag(cap, CAP_EFFECTIVE, N_ENTRIES(cap_add), 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_LIBCAP */

// Set the socket mark
int set_sock_err = setsockopt(socket, SOL_SOCKET, option_name, option_value, option_len);

// 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, N_ENTRIES(cap_add), 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_LIBCAP */

if(!set_sock_err) {
result = 0; // Success
}

#ifdef HAVE_LIBCAP
cleanup_and_exit:
cap_free(cap);
#endif /* ifdef HAVE_LIBCAP */

return result;
}

/* Set the socket mark */
#ifdef SO_MARK
static
int set_socket_mark(int socket, unsigned int mark) {
return set_privileged_socket_opt(socket, SO_MARK, &mark, sizeof(mark),
MAYADD_CAP_NET_ADMIN);
}
#endif /* ifdef SO_MARK */

#ifdef SO_BINDTODEVICE
static
int set_bind_to_device(int socket, char const * device) {
return set_privileged_socket_opt(socket, SO_BINDTODEVICE, device,
strlen(device), MAYADD_CAP_NET_RAW);
}
#endif /* ifdef SO_BINDTODEVICE */

/*
Set the socket options for an outgoing stream protocol socket based on
the packet parameters.
Expand Down Expand Up @@ -341,17 +443,15 @@ int set_stream_socket_options(
}
#ifdef SO_MARK
if (param->routing_mark) {
if (setsockopt(stream_socket, SOL_SOCKET,
SO_MARK, &param->routing_mark, sizeof(int))) {
if (set_socket_mark(stream_socket, param->routing_mark)) {
return -1;
}
}
#endif

#ifdef SO_BINDTODEVICE
if (param->local_device) {
if (setsockopt(stream_socket, SOL_SOCKET,
SO_BINDTODEVICE, param->local_device, strlen(param->local_device))) {
if (set_bind_to_device(stream_socket, param->local_device)) {
return -1;
}
}
Expand All @@ -360,6 +460,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.
Expand Down Expand Up @@ -577,17 +678,15 @@ int construct_ip4_packet(
*/
#ifdef SO_MARK
if (param->routing_mark) {
if (setsockopt(send_socket, SOL_SOCKET,
SO_MARK, &param->routing_mark, sizeof(int))) {
if (set_socket_mark(send_socket, param->routing_mark)) {
return -1;
}
}
#endif

#ifdef SO_BINDTODEVICE
if (param->local_device) {
if (setsockopt(send_socket, SOL_SOCKET,
SO_BINDTODEVICE, param->local_device, strlen(param->local_device))) {
if(set_bind_to_device(send_socket, param->local_device)) {
return -1;
}
}
Expand Down Expand Up @@ -750,9 +849,7 @@ int construct_ip6_packet(
}
#ifdef SO_MARK
if (param->routing_mark) {
if (setsockopt(send_socket,
SOL_SOCKET, SO_MARK, &param->routing_mark,
sizeof(int))) {
if (set_socket_mark(send_socket, param->routing_mark)) {
return -1;
}
}
Expand Down
97 changes: 82 additions & 15 deletions packet/packet.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,94 @@
#include <sys/capability.h>
#endif

#include "utils.h"

#include "wait.h"

#ifdef HAVE_LIBCAP
static
void drop_excess_capabilities() {

/*
By default, the root user has all capabilities, which poses a security risk.
Some capabilities must be retained in the permitted set so that it can be added
to the effective set when needed.
*/
cap_value_t cap_permitted[] = {
#ifdef SO_MARK
/*
CAP_NET_ADMIN is needed to set the routing mark (SO_MARK) on a socket
*/
CAP_NET_ADMIN,
#endif /* ifdef SOMARK */

#ifdef SO_BINDTODEVICE
/*
The CAP_NET_RAW capability is necessary for binding to a network device using
the SO_BINDTODEVICE socket option. Although this capability is not needed for
the initial bind operation, it is required when calling setsockopt after data has
been sent.
Given the current architecture, the socket is re-bound to the device every time
a probe is sent. Therefore, CAP_NET_RAW is required when specifying an interface
using the -I or --interface options.
*/
CAP_NET_RAW,
#endif /* ifdef SO_BINDTODEVICE */
};

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;
Expand All @@ -55,19 +132,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;
Expand Down
14 changes: 14 additions & 0 deletions packet/utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#ifndef _UTILS_H
#define _UTILS_H

// Fend off -Wunused-parameter
#if defined(__GNUC__)
# define UNUSED __attribute__((__unused__))
#else
# define UNUSED
#endif

// Number of entries in a fixed-length array
#define N_ENTRIES(x) (sizeof(x) / sizeof(*x))

#endif

0 comments on commit 4a1939a

Please sign in to comment.