È possibile installare il binario con CAP_NET_RAW
(e CAP_NET_BIND_SERVICE
se vengono utilizzate le porte ≤ 1024); setcap 'cap_net_raw=ep' yourdaemon
come root. Per IP, SO_BROADCAST
non richiede alcuna funzionalità (in particolare, CAP_NET_BROADCAST
non viene utilizzato per IP).
(per le capacità esatti necessari, si veda ad esempio net/core/sock.c:sock_setbindtodevice(), net/core/sock.c:sock_setsockopt(), e include/net/sock.h:sock_set_flag() nei sorgenti del kernel Linux per la verifica.)
Tuttavia, i demoni sono in genere iniziato come root. Qui, quanto sopra non sarebbe sufficiente, come la modifica dell'ID utente per il processo (per eliminare i privilegi) anche clears the effective capabilities. Tuttavia, anch'io preferisco i miei servizi per funzionare con privilegi limitati.
vorrei scegliere tra due approcci di base:
richiedono che il demone viene eseguito da root, o con CAP_NET_RAW
(ed eventualmente CAP_NET_BIND_SERVICE
) capacità.
Uso prctl()
, setgroups()
o initgroups()
, setresuid()
, setresgid()
, e capacità da libcap, cap_init()
, cap_set_flag()
e cap_set_proc()
rimozione dei privilegi passando a un utente dedicato e gruppo, ma mantenendo il CAP_NET_RAW
(e facoltativamente CAP_NET_BIND_SERVICE
) e loro solo .
Ciò consente al daemon di rispondere ad es. Segnale HUP senza riavvio completo, poiché dispone dei privilegi necessari per enumerare le interfacce e leggere i propri file di configurazione per aprire i socket per le nuove interfacce.
Utilizzare un "loader" con privilegi, che apre tutti i socket necessari, elimina i privilegi ed esegue il daemon effettivo.
Il demone dovrebbe ottenere i dettagli di socket e interfaccia come parametri della riga di comando, o forse tramite input standard. Il demone è completamente privo di privilegi.
Sfortunatamente, se vengono aperte nuove interfacce o la configurazione viene modificata, il daemon non può fare molto eccetto uscire. (Non può nemmeno eseguire il caricatore privilegiata, perché i privilegi sono già stati eliminati.)
Il primo approccio è più comune, e più facile da implementare nella pratica; specialmente se il demone dovrebbe essere eseguito solo da root. (Ricordate, il demone può rispondere alle modifiche di configurazione, poiché ha le capacità necessarie ma non i privilegi di root in generale.) Ho usato solo il secondo approccio per i binari "black box" di cui non mi fido.
Ecco alcuni esempi di codice.
privileges.h
: #ifndef PRIVILEGES_H #define PRIVILEGES_H
#define NEED_CAP_NET_ADMIN (1U << 0)
#define NEED_CAP_NET_BIND_SERVICE (1U << 1)
#define NEED_CAP_NET_RAW (1U << 2)
extern int drop_privileges(const char *const user, const unsigned int capabilities);
#endif /* PRIVILEGES_H */
privileges.c
:
#define _GNU_SOURCE
#define _BSD_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/capability.h>
#include <sys/prctl.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>
#include "privileges.h"
/* Only three NEED_CAP_ constants defined. */
#define MAX_CAPABILITIES 3
static int permit_effective(cap_t caps, const unsigned int capabilities)
{
cap_value_t value[MAX_CAPABILITIES];
int values = 0;
if (capabilities & NEED_CAP_NET_ADMIN)
value[values++] = CAP_NET_ADMIN;
if (capabilities & NEED_CAP_NET_BIND_SERVICE)
value[values++] = CAP_NET_BIND_SERVICE;
if (capabilities & NEED_CAP_NET_RAW)
value[values++] = CAP_NET_RAW;
if (values < 1)
return 0;
if (cap_set_flag(caps, CAP_PERMITTED, values, value, CAP_SET) == -1)
return errno;
if (cap_set_flag(caps, CAP_EFFECTIVE, values, value, CAP_SET) == -1)
return errno;
return 0;
}
static int add_privileges(cap_t caps)
{
cap_value_t value[3] = { CAP_SETPCAP, CAP_SETUID, CAP_SETGID };
if (cap_set_flag(caps, CAP_PERMITTED, sizeof value/sizeof value[0], value, CAP_SET) == -1)
return errno;
if (cap_set_flag(caps, CAP_EFFECTIVE, sizeof value/sizeof value[0], value, CAP_SET) == -1)
return errno;
return 0;
}
int drop_privileges(const char *const user, const unsigned int capabilities)
{
uid_t uid;
gid_t gid;
cap_t caps;
/* Make sure user is neither NULL nor empty. */
if (!user || !user[0])
return errno = EINVAL;
/* Find the user. */
{
struct passwd *pw;
pw = getpwnam(user);
if (!pw
#ifdef UID_MIN
|| pw->pw_uid < (uid_t)UID_MIN
#endif
#ifdef UID_MAX
|| pw->pw_uid > (uid_t)UID_MAX
#endif
#ifdef GID_MIN
|| pw->pw_gid < (gid_t)GID_MIN
#endif
#ifdef GID_MAX
|| pw->pw_gid > (gid_t)GID_MAX
#endif
)
return errno = EINVAL;
uid = pw->pw_uid;
gid = pw->pw_gid;
endpwent();
}
/* Install privileged capabilities. */
caps = cap_init();
if (!caps)
return errno = ENOMEM;
if (permit_effective(caps, capabilities)) {
const int cause = errno;
cap_free(caps);
return errno = cause;
}
if (add_privileges(caps)) {
const int cause = errno;
cap_free(caps);
return errno = cause;
}
if (cap_set_proc(caps) == -1) {
const int cause = errno;
cap_free(caps);
return errno = cause;
}
cap_free(caps);
/* Retain permitted capabilities over the identity change. */
prctl(PR_SET_KEEPCAPS, 1UL, 0UL,0UL,0UL);
if (setresgid(gid, gid, gid) == -1)
return errno = EPERM;
if (initgroups(user, gid) == -1)
return errno = EPERM;
if (setresuid(uid, uid, uid) == -1)
return errno = EPERM;
/* Install unprivileged capabilities. */
caps = cap_init();
if (!caps)
return errno = ENOMEM;
if (permit_effective(caps, capabilities)) {
const int cause = errno;
cap_free(caps);
return errno = cause;
}
if (cap_set_proc(caps) == -1) {
const int cause = errno;
cap_free(caps);
return errno = cause;
}
cap_free(caps);
/* Reset standard KEEPCAPS behaviour. */
prctl(PR_SET_KEEPCAPS, 0UL, 0UL,0UL,0UL);
/* Done. */
return 0;
}
udp-broadcast.h
:
#ifndef UDP_BROADCAST_H
#define UDP_BROADCAST_H
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
struct udp_socket {
struct sockaddr_in broadcast; /* Broadcast address */
unsigned int if_index; /* Interface index */
int descriptor; /* Socket descriptor */
};
extern int open_udp_broadcast(struct udp_socket *const udpsocket,
const char *const interface,
int const port);
extern int udp_broadcast(const struct udp_socket *const udpsocket,
const void *const data,
const size_t size,
const int flags);
extern size_t udp_receive(const struct udp_socket *const udpsocket,
void *const data,
const size_t size_max,
const int flags,
struct sockaddr_in *const from_addr,
struct sockaddr_in *const to_addr,
struct sockaddr_in *const hdr_addr,
unsigned int *const if_index);
#endif /* UDP_BROADCAST_H */
udp-broadcast.c
:
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <net/if.h>
#include <errno.h>
#include "udp-broadcast.h"
int udp_broadcast(const struct udp_socket *const udpsocket,
const void *const data,
const size_t size,
const int flags)
{
ssize_t n;
if (!udpsocket || udpsocket->broadcast.sin_family != AF_INET)
return errno = EINVAL;
if (!data || size < 1)
return 0;
n = sendto(udpsocket->descriptor, data, size, flags,
(const struct sockaddr *)&(udpsocket->broadcast),
sizeof (struct sockaddr_in));
if (n == (ssize_t)-1)
return errno;
if (n == (ssize_t)size)
return 0;
return errno = EIO;
}
size_t udp_receive(const struct udp_socket *const udpsocket,
void *const data,
const size_t size_max,
const int flags,
struct sockaddr_in *const from_addr,
struct sockaddr_in *const to_addr,
struct sockaddr_in *const hdr_addr,
unsigned int *const if_index)
{
char ancillary[512];
struct msghdr msg;
struct iovec iov[1];
struct cmsghdr *cmsg;
ssize_t n;
if (!data || size_max < 1 || !udpsocket) {
errno = EINVAL;
return (size_t)0;
}
/* Clear results, just in case. */
if (from_addr) {
memset(from_addr, 0, sizeof *from_addr);
from_addr->sin_family = AF_UNSPEC;
}
if (to_addr) {
memset(to_addr, 0, sizeof *to_addr);
to_addr->sin_family = AF_UNSPEC;
}
if (hdr_addr) {
memset(hdr_addr, 0, sizeof *hdr_addr);
hdr_addr->sin_family = AF_UNSPEC;
}
if (if_index)
*if_index = 0U;
iov[0].iov_base = data;
iov[0].iov_len = size_max;
if (from_addr) {
msg.msg_name = from_addr;
msg.msg_namelen = sizeof (struct sockaddr_in);
} else {
msg.msg_name = NULL;
msg.msg_namelen = 0;
}
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_control = ancillary;
msg.msg_controllen = sizeof ancillary;
msg.msg_flags = 0;
n = recvmsg(udpsocket->descriptor, &msg, flags);
if (n == (ssize_t)-1)
return (size_t)0; /* errno set by recvmsg(). */
if (n < (ssize_t)1) {
errno = EIO;
return (size_t)0;
}
/* Populate data from ancillary message, if requested. */
if (to_addr || hdr_addr || if_index)
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg))
if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) {
const struct in_pktinfo *const info = CMSG_DATA(cmsg);
if (!info)
continue;
if (if_index)
*if_index = info->ipi_ifindex;
if (to_addr) {
to_addr->sin_family = AF_INET;
to_addr->sin_port = udpsocket->broadcast.sin_port; /* This is a guess. */
to_addr->sin_addr = info->ipi_spec_dst;
}
if (hdr_addr) {
hdr_addr->sin_family = AF_INET;
hdr_addr->sin_port = udpsocket->broadcast.sin_port; /* A guess, again. */
hdr_addr->sin_addr = info->ipi_addr;
}
}
errno = 0;
return (size_t)n;
}
int open_udp_broadcast(struct udp_socket *const udpsocket,
const char *const interface,
int const port)
{
const size_t interface_len = (interface) ? strlen(interface) : 0;
const int set_flag = 1;
int sockfd;
if (udpsocket) {
memset(udpsocket, 0, sizeof *udpsocket);
udpsocket->broadcast.sin_family = AF_INET;
udpsocket->broadcast.sin_addr.s_addr = INADDR_BROADCAST;
if (port >= 1 && port <= 65535)
udpsocket->broadcast.sin_port = htons(port);
udpsocket->descriptor = -1;
}
if (!udpsocket || interface_len < 1 || port < 1 || port > 65535)
return errno = EINVAL;
/* Generic UDP socket. */
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == -1)
return errno;
/* Set SO_REUSEADDR if possible. */
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &set_flag, sizeof set_flag);
/* Set IP_FREEBIND if possible. */
setsockopt(sockfd, IPPROTO_IP, IP_FREEBIND, &set_flag, sizeof set_flag);
/* We need broadcast capability. */
if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &set_flag, sizeof set_flag) == -1) {
const int real_errno = errno;
close(sockfd);
return errno = real_errno;
}
/* We want the IP_PKTINFO ancillary messages, to determine target address
* and interface index. */
if (setsockopt(sockfd, IPPROTO_IP, IP_PKTINFO, &set_flag, sizeof set_flag) == -1) {
const int real_errno = errno;
close(sockfd);
return errno = real_errno;
}
/* We bind to the broadcast address. */
if (bind(sockfd, (const struct sockaddr *)&(udpsocket->broadcast), sizeof udpsocket->broadcast) == -1) {
const int real_errno = errno;
close(sockfd);
return errno = real_errno;
}
/* Finally, we bind to the specified interface. */
if (setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, interface, interface_len) == -1) {
const int real_errno = errno;
close(sockfd);
return errno = real_errno;
}
udpsocket->descriptor = sockfd;
udpsocket->if_index = if_nametoindex(interface);
errno = 0;
return 0;
}
main.c
:
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <stdio.h>
#include <netdb.h>
#include <errno.h>
#include "privileges.h"
#include "udp-broadcast.h"
static volatile sig_atomic_t done_triggered = 0;
static volatile sig_atomic_t reload_triggered = 0;
static void done_handler(int signum)
{
__sync_bool_compare_and_swap(&done_triggered, (sig_atomic_t)0, (sig_atomic_t)signum);
}
static void reload_handler(int signum)
{
__sync_bool_compare_and_swap(&reload_triggered, (sig_atomic_t)0, (sig_atomic_t)signum);
}
static int install_handler(const int signum, void (*handler)(int))
{
struct sigaction act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_handler = handler;
act.sa_flags = 0;
if (sigaction(signum, &act, NULL) == -1)
return errno;
return 0;
}
/* Return 0 if done_triggered or reload_triggered, nonzero otherwise.
* Always clears reload_triggered.
*/
static inline int keep_running(void)
{
if (done_triggered)
return 0;
return !__sync_fetch_and_and(&reload_triggered, (sig_atomic_t)0);
}
static const char *ipv4_address(const void *const addr)
{
static char buffer[16];
char *end = buffer + sizeof buffer;
unsigned char byte[4];
if (!addr)
return "(none)";
memcpy(byte, addr, 4);
*(--end) = '\0';
do {
*(--end) = '0' + (byte[3] % 10);
byte[3] /= 10U;
} while (byte[3]);
*(--end) = '.';
do {
*(--end) = '0' + (byte[2] % 10);
byte[2] /= 10U;
} while (byte[2]);
*(--end) = '.';
do {
*(--end) = '0' + (byte[1] % 10);
byte[1] /= 10U;
} while (byte[1]);
*(--end) = '.';
do {
*(--end) = '0' + (byte[0] % 10);
byte[0] /= 10U;
} while (byte[0]);
return (const char *)end;
}
int main(int argc, char *argv[])
{
int port;
char dummy;
/* Check usage. */
if (argc != 4 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s USERNAME INTERFACE PORT\n", argv[0]);
fprintf(stderr, "Where:\n");
fprintf(stderr, " USERNAME is the unprivileged user to run as,\n");
fprintf(stderr, " INTERFACE is the interface to bind to, and\n");
fprintf(stderr, " PORT is the UDP/IPv4 port number to use.\n");
fprintf(stderr, "\n");
return EXIT_FAILURE;
}
/* Parse the port into a number. */
if (sscanf(argv[3], "%d %c", &port, &dummy) != 1 || port < 1 || port > 65535) {
struct servent *serv = getservbyname(argv[3], "udp");
if (serv && serv->s_port > 1 && serv->s_port < 65536) {
port = serv->s_port;
endservent();
} else {
endservent();
fprintf(stderr, "%s: Invalid port.\n", argv[3]);
return EXIT_FAILURE;
}
}
/* Drop privileges. */
if (drop_privileges(argv[1], NEED_CAP_NET_RAW)) {
fprintf(stderr, "%s.\n", strerror(errno));
return EXIT_FAILURE;
}
/* Install signal handlers. */
if (install_handler(SIGINT, done_handler) ||
install_handler(SIGTERM, done_handler) ||
install_handler(SIGHUP, reload_handler) ||
install_handler(SIGUSR1, reload_handler)) {
fprintf(stderr, "Cannot install signal handlers: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
fprintf(stderr, "Send a SIGINT (Ctrl+C) or SIGTERM to stop the service:\n");
fprintf(stderr, "\tkill -SIGTERM %ld\n", (long)getpid());
fprintf(stderr, "Send a SIGHUP or SIGUSR1 to have the service reload and rebroadcast:\n");
fprintf(stderr, "\tkill -SIGHUP %ld\n", (long)getpid());
fprintf(stderr, "Privileges dropped successfully.\n\n");
fflush(stderr);
while (!done_triggered) {
struct udp_socket s;
if (open_udp_broadcast(&s, argv[2], port)) {
fprintf(stderr, "%s port %s: %s.\n", argv[2], argv[3], strerror(errno));
return EXIT_FAILURE;
}
if (udp_broadcast(&s, "Hello?", 6, MSG_NOSIGNAL)) {
fprintf(stderr, "%s port %s: Broadcast failed: %s.\n", argv[2], argv[3], strerror(errno));
close(s.descriptor);
return EXIT_FAILURE;
}
if (s.if_index)
fprintf(stderr, "Broadcast sent using interface %s (index %u); waiting for responses.\n", argv[2], s.if_index);
else
fprintf(stderr, "Broadcast sent using interface %s; waiting for responses.\n", argv[2]);
fflush(stderr);
while (keep_running()) {
struct sockaddr_in from_addr, to_addr, hdr_addr;
unsigned char data[512];
unsigned int if_index;
size_t size, i;
size = udp_receive(&s, data, sizeof data, 0, &from_addr, &to_addr, &hdr_addr, &if_index);
if (size > 0) {
printf("Received %zu bytes:", size);
for (i = 0; i < size; i++)
if (i & 15)
printf(" %02x", data[i]);
else
printf("\n\t%02x", data[i]);
if (if_index)
printf("\n\t Index: %u", if_index);
printf("\n\t From: %s", ipv4_address(&from_addr.sin_addr));
printf("\n\t To: %s", ipv4_address(&to_addr.sin_addr));
printf("\n\tHeader: %s", ipv4_address(&hdr_addr.sin_addr));
printf("\n");
fflush(stdout);
} else
if (errno != EINTR) {
fprintf(stderr, "%s\n", strerror(errno));
break;
}
}
close(s.descriptor);
}
fprintf(stderr, "Exiting.\n");
return EXIT_SUCCESS;
}
compilazione utilizzando
gcc -Wall -Wextra -O2 -c privileges.c
gcc -Wall -Wextra -O2 -c udp-broadcast.c
gcc -Wall -Wextra -O2 -c main.c
gcc -Wall -Wextra main.o udp-broadcast.o privileges.o -lcap -o example
ed eseguire il example
come root, specificando un nome utente senza privilegi per l'esecuzione come l'interfaccia proteggere, e il numero di porta UDP come parametri:
sudo ./example yourdaemonuser eth0 4000
In questo momento ho solo un laptop in uso, quindi il lato di ricezione è fondamentalmente non testato. So che CAP_NET_RAW
è sufficiente qui (kernel Linux 4.2.0-27 su x86-64) e che le mandate di trasmissione UDP appaiono come in uscita dall'indirizzo dell'interfaccia ethernet a 255.255.255.255:port
, ma non ho un'altra macchina da inviare esempio di risposte al demone (che sarebbe facile usando, ad esempio, NetCat: printf 'Response!' | nc -u4 -q2y interface-address port
).
Si prega di notare che la qualità del codice sopra è solo grado di prova iniziale. Dal momento che non ho bisogno di questo da solo per nulla, e volevo solo verificare che non stavo parlando dal mio sedere, non ho speso alcuno sforzo per rendere il codice pulito o affidabile.
Domande? Commenti?
Molto difficile rispondere semplicemente senza un'analisi adeguata. Prova a creare un semplice mokeup per completare la tua analisi. (Non so in quale settore industriale funzioni questo servizio, ma ho paura della sicurezza ...) – rom1nux
il protocollo è concepito per l'installazione iniziale e non consente di modificare l'impostazione IP quando il sistema è in modalità "Esegui". – TabascoEye
Penso che un minimo silenzioso rende 'SO_RCVBUFSIZE = 0' non utile. Puoi usare 'shutdown (sockfd, SHUT_RD)' per disabilitare ulteriori letture, ma sarei incline a scrivere correttamente il programma :) –