Skip to content

Commit 4a1939a

Browse files
authored
Merge pull request #487 from flu0r1ne/adjust-capability-handling
Adjust capability handling
2 parents 74d312d + 54abd96 commit 4a1939a

File tree

3 files changed

+204
-26
lines changed

3 files changed

+204
-26
lines changed

packet/construct_unix.c

Lines changed: 108 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
#include "construct_unix.h"
2020

21+
#include "utils.h"
22+
2123
#include <errno.h>
2224
#include <stdio.h>
2325
#include <string.h>
@@ -32,6 +34,10 @@
3234
#define SOL_IP IPPROTO_IP
3335
#endif
3436

37+
#ifdef HAVE_LIBCAP
38+
#include <sys/capability.h>
39+
#endif
40+
3541
/* A source of data for computing a checksum */
3642
struct checksum_source_t {
3743
const void *data;
@@ -278,6 +284,102 @@ int construct_udp6_packet(
278284
return 0;
279285
}
280286

287+
/*
288+
This defines a common interface which elevates privileges on
289+
platforms with LIBCAP and acts as a NOOP on platforms without
290+
it.
291+
*/
292+
#ifdef HAVE_LIBCAP
293+
294+
typedef cap_value_t mayadd_cap_value_t;
295+
#define MAYADD_CAP_NET_RAW CAP_NET_RAW
296+
#define MAYADD_CAP_NET_ADMIN CAP_NET_ADMIN
297+
298+
#else /* ifdef HAVE_LIBCAP */
299+
300+
typedef int mayadd_cap_value_t;
301+
#define MAYADD_CAP_NET_RAW ((mayadd_cap_value_t) 0)
302+
#define MAYADD_CAP_NET_ADMIN ((mayadd_cap_value_t) 0)
303+
304+
#endif /* ifdef HAVE_LIBCAP */
305+
306+
UNUSED static
307+
int set_privileged_socket_opt(int socket, int option_name,
308+
void const * option_value, socklen_t option_len,
309+
UNUSED mayadd_cap_value_t required_cap) {
310+
311+
int result = -1;
312+
313+
// Add CAP_NET_ADMIN to the effective set if libcap is present
314+
#ifdef HAVE_LIBCAP
315+
static cap_value_t cap_add[1];
316+
cap_add[0] = required_cap;
317+
318+
// Get the capabilities of the current process
319+
cap_t cap = cap_get_proc();
320+
if (cap == NULL) {
321+
goto cleanup_and_exit;
322+
}
323+
324+
// Set the required capability flag
325+
if (cap_set_flag(cap, CAP_EFFECTIVE, N_ENTRIES(cap_add), cap_add,
326+
CAP_SET)) {
327+
goto cleanup_and_exit;
328+
}
329+
330+
// Apply the modified capabilities to the current process
331+
if (cap_set_proc(cap)) {
332+
goto cleanup_and_exit;
333+
}
334+
#endif /* ifdef HAVE_LIBCAP */
335+
336+
// Set the socket mark
337+
int set_sock_err = setsockopt(socket, SOL_SOCKET, option_name, option_value, option_len);
338+
339+
// Drop CAP_NET_ADMIN from the effective set if libcap is present
340+
#ifdef HAVE_LIBCAP
341+
342+
// Clear the CAP_NET_ADMIN capability flag
343+
if (cap_set_flag(cap, CAP_EFFECTIVE, N_ENTRIES(cap_add), cap_add,
344+
CAP_CLEAR)) {
345+
goto cleanup_and_exit;
346+
}
347+
348+
// Apply the modified capabilities to the current process
349+
if (cap_set_proc(cap)) {
350+
goto cleanup_and_exit;
351+
}
352+
#endif /* ifdef HAVE_LIBCAP */
353+
354+
if(!set_sock_err) {
355+
result = 0; // Success
356+
}
357+
358+
#ifdef HAVE_LIBCAP
359+
cleanup_and_exit:
360+
cap_free(cap);
361+
#endif /* ifdef HAVE_LIBCAP */
362+
363+
return result;
364+
}
365+
366+
/* Set the socket mark */
367+
#ifdef SO_MARK
368+
static
369+
int set_socket_mark(int socket, unsigned int mark) {
370+
return set_privileged_socket_opt(socket, SO_MARK, &mark, sizeof(mark),
371+
MAYADD_CAP_NET_ADMIN);
372+
}
373+
#endif /* ifdef SO_MARK */
374+
375+
#ifdef SO_BINDTODEVICE
376+
static
377+
int set_bind_to_device(int socket, char const * device) {
378+
return set_privileged_socket_opt(socket, SO_BINDTODEVICE, device,
379+
strlen(device), MAYADD_CAP_NET_RAW);
380+
}
381+
#endif /* ifdef SO_BINDTODEVICE */
382+
281383
/*
282384
Set the socket options for an outgoing stream protocol socket based on
283385
the packet parameters.
@@ -341,17 +443,15 @@ int set_stream_socket_options(
341443
}
342444
#ifdef SO_MARK
343445
if (param->routing_mark) {
344-
if (setsockopt(stream_socket, SOL_SOCKET,
345-
SO_MARK, &param->routing_mark, sizeof(int))) {
446+
if (set_socket_mark(stream_socket, param->routing_mark)) {
346447
return -1;
347448
}
348449
}
349450
#endif
350451

351452
#ifdef SO_BINDTODEVICE
352453
if (param->local_device) {
353-
if (setsockopt(stream_socket, SOL_SOCKET,
354-
SO_BINDTODEVICE, param->local_device, strlen(param->local_device))) {
454+
if (set_bind_to_device(stream_socket, param->local_device)) {
355455
return -1;
356456
}
357457
}
@@ -360,6 +460,7 @@ int set_stream_socket_options(
360460
return 0;
361461
}
362462

463+
363464
/*
364465
Open a TCP or SCTP socket, respecting the probe paramters as much as
365466
we can, and use it as an outgoing probe.
@@ -577,17 +678,15 @@ int construct_ip4_packet(
577678
*/
578679
#ifdef SO_MARK
579680
if (param->routing_mark) {
580-
if (setsockopt(send_socket, SOL_SOCKET,
581-
SO_MARK, &param->routing_mark, sizeof(int))) {
681+
if (set_socket_mark(send_socket, param->routing_mark)) {
582682
return -1;
583683
}
584684
}
585685
#endif
586686

587687
#ifdef SO_BINDTODEVICE
588688
if (param->local_device) {
589-
if (setsockopt(send_socket, SOL_SOCKET,
590-
SO_BINDTODEVICE, param->local_device, strlen(param->local_device))) {
689+
if(set_bind_to_device(send_socket, param->local_device)) {
591690
return -1;
592691
}
593692
}
@@ -750,9 +849,7 @@ int construct_ip6_packet(
750849
}
751850
#ifdef SO_MARK
752851
if (param->routing_mark) {
753-
if (setsockopt(send_socket,
754-
SOL_SOCKET, SO_MARK, &param->routing_mark,
755-
sizeof(int))) {
852+
if (set_socket_mark(send_socket, param->routing_mark)) {
756853
return -1;
757854
}
758855
}

packet/packet.c

Lines changed: 82 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,94 @@
3333
#include <sys/capability.h>
3434
#endif
3535

36+
#include "utils.h"
37+
3638
#include "wait.h"
3739

40+
#ifdef HAVE_LIBCAP
41+
static
42+
void drop_excess_capabilities() {
43+
44+
/*
45+
By default, the root user has all capabilities, which poses a security risk.
46+
47+
Some capabilities must be retained in the permitted set so that it can be added
48+
to the effective set when needed.
49+
*/
50+
cap_value_t cap_permitted[] = {
51+
#ifdef SO_MARK
52+
/*
53+
CAP_NET_ADMIN is needed to set the routing mark (SO_MARK) on a socket
54+
*/
55+
CAP_NET_ADMIN,
56+
#endif /* ifdef SOMARK */
57+
58+
#ifdef SO_BINDTODEVICE
59+
/*
60+
The CAP_NET_RAW capability is necessary for binding to a network device using
61+
the SO_BINDTODEVICE socket option. Although this capability is not needed for
62+
the initial bind operation, it is required when calling setsockopt after data has
63+
been sent.
64+
65+
Given the current architecture, the socket is re-bound to the device every time
66+
a probe is sent. Therefore, CAP_NET_RAW is required when specifying an interface
67+
using the -I or --interface options.
68+
*/
69+
CAP_NET_RAW,
70+
#endif /* ifdef SO_BINDTODEVICE */
71+
};
72+
73+
cap_t current_cap = cap_get_proc();
74+
cap_t wanted_cap = cap_get_proc();
75+
76+
if(!current_cap || !wanted_cap) {
77+
goto pcap_error;
78+
}
79+
80+
// Clear all capabilities from the 'wanted_cap' set
81+
if(cap_clear(wanted_cap)) {
82+
goto pcap_error;
83+
}
84+
85+
// Retain only the necessary capabilities defined in 'cap_permitted' in the permitted set.
86+
// This approach ensures the principle of least privilege.
87+
// If the user has dropped capabilities, the code assumes those features will not be needed.
88+
for(unsigned i = 0; i < N_ENTRIES(cap_permitted); i++) {
89+
cap_flag_value_t is_set;
90+
91+
if(cap_get_flag(current_cap, cap_permitted[i], CAP_PERMITTED, &is_set)) {
92+
goto pcap_error;
93+
}
94+
95+
if(cap_set_flag(wanted_cap, CAP_PERMITTED, 1, &cap_permitted[i], is_set)) {
96+
goto pcap_error;
97+
}
98+
}
99+
100+
// Update the process's capabilities to match 'wanted_cap'
101+
if(cap_set_proc(wanted_cap)) {
102+
goto pcap_error;
103+
}
104+
105+
if(cap_free(current_cap) || cap_free(wanted_cap)) {
106+
goto pcap_error;
107+
}
108+
109+
return;
110+
111+
pcap_error:
112+
113+
cap_free(current_cap);
114+
cap_free(wanted_cap);
115+
error(EXIT_FAILURE, errno, "Failed to drop capabilities");
116+
}
117+
#endif /* ifdef HAVE_LIBCAP */
118+
38119
/* Drop SUID privileges. To be used after acquiring raw sockets. */
39120
static
40121
int drop_elevated_permissions(
41122
void)
42123
{
43-
#ifdef HAVE_LIBCAP
44-
cap_t cap;
45-
#endif
46-
47124
/* Drop any suid permissions granted */
48125
if (setgid(getgid()) || setuid(getuid())) {
49126
return -1;
@@ -55,19 +132,9 @@ int drop_elevated_permissions(
55132

56133
/*
57134
Drop all process capabilities.
58-
This will revoke anything granted by a commandline 'setcap'
59135
*/
60136
#ifdef HAVE_LIBCAP
61-
cap = cap_get_proc();
62-
if (cap == NULL) {
63-
return -1;
64-
}
65-
if (cap_clear(cap)) {
66-
return -1;
67-
}
68-
if (cap_set_proc(cap)) {
69-
return -1;
70-
}
137+
drop_excess_capabilities();
71138
#endif
72139

73140
return 0;

packet/utils.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#ifndef _UTILS_H
2+
#define _UTILS_H
3+
4+
// Fend off -Wunused-parameter
5+
#if defined(__GNUC__)
6+
# define UNUSED __attribute__((__unused__))
7+
#else
8+
# define UNUSED
9+
#endif
10+
11+
// Number of entries in a fixed-length array
12+
#define N_ENTRIES(x) (sizeof(x) / sizeof(*x))
13+
14+
#endif

0 commit comments

Comments
 (0)