From 08ee0a83a1d5b7e6c1c2f1a907a8c3bae4dd45f9 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Thu, 29 Jul 2021 12:46:06 +0200 Subject: systemd: update code from upstream (2021-07-29) This is a direct dump from systemd git. ====== SYSTEMD_DIR=../systemd COMMIT=7925d693a79cf1e4a3d1539402b3160bb9a3ab12 ( cd "$SYSTEMD_DIR" git checkout "$COMMIT" git reset --hard git clean -fdx ) git ls-files -z :/src/libnm-systemd-core/src/ \ :/src/libnm-systemd-shared/src/ \ :/src/libnm-std-aux/unaligned.h | \ xargs -0 rm -f nm_copy_sd_shared() { mkdir -p "./src/libnm-systemd-shared/$(dirname "$1")" cp "$SYSTEMD_DIR/$1" "./src/libnm-systemd-shared/$1" } nm_copy_sd_core() { mkdir -p "./src/libnm-systemd-core/$(dirname "$1")" cp "$SYSTEMD_DIR/$1" "./src/libnm-systemd-core/$1" } nm_copy_sd_stdaux() { mkdir -p "./src/libnm-std-aux/" cp "$SYSTEMD_DIR/$1" "./src/libnm-std-aux/${1##*/}" } nm_copy_sd_core "src/libsystemd-network/arp-util.c" nm_copy_sd_core "src/libsystemd-network/arp-util.h" nm_copy_sd_core "src/libsystemd-network/dhcp-identifier.c" nm_copy_sd_core "src/libsystemd-network/dhcp-identifier.h" nm_copy_sd_core "src/libsystemd-network/dhcp-internal.h" nm_copy_sd_core "src/libsystemd-network/dhcp-lease-internal.h" nm_copy_sd_core "src/libsystemd-network/dhcp-network.c" nm_copy_sd_core "src/libsystemd-network/dhcp-option.c" nm_copy_sd_core "src/libsystemd-network/dhcp-packet.c" nm_copy_sd_core "src/libsystemd-network/dhcp-protocol.h" nm_copy_sd_core "src/libsystemd-network/dhcp6-internal.h" nm_copy_sd_core "src/libsystemd-network/dhcp6-lease-internal.h" nm_copy_sd_core "src/libsystemd-network/dhcp6-network.c" nm_copy_sd_core "src/libsystemd-network/dhcp6-option.c" nm_copy_sd_core "src/libsystemd-network/dhcp6-protocol.h" nm_copy_sd_core "src/libsystemd-network/lldp-internal.h" nm_copy_sd_core "src/libsystemd-network/lldp-neighbor.c" nm_copy_sd_core "src/libsystemd-network/lldp-neighbor.h" nm_copy_sd_core "src/libsystemd-network/lldp-network.c" nm_copy_sd_core "src/libsystemd-network/lldp-network.h" nm_copy_sd_core "src/libsystemd-network/network-common.c" nm_copy_sd_core "src/libsystemd-network/network-common.h" nm_copy_sd_core "src/libsystemd-network/network-internal.c" nm_copy_sd_core "src/libsystemd-network/network-internal.h" nm_copy_sd_core "src/libsystemd-network/sd-dhcp-client.c" nm_copy_sd_core "src/libsystemd-network/sd-dhcp-lease.c" nm_copy_sd_core "src/libsystemd-network/sd-dhcp6-client.c" nm_copy_sd_core "src/libsystemd-network/sd-dhcp6-lease.c" nm_copy_sd_core "src/libsystemd-network/sd-ipv4acd.c" nm_copy_sd_core "src/libsystemd-network/sd-ipv4ll.c" nm_copy_sd_core "src/libsystemd-network/sd-lldp.c" nm_copy_sd_core "src/libsystemd/sd-event/event-source.h" nm_copy_sd_core "src/libsystemd/sd-event/event-util.c" nm_copy_sd_core "src/libsystemd/sd-event/event-util.h" nm_copy_sd_core "src/libsystemd/sd-event/sd-event.c" nm_copy_sd_core "src/libsystemd/sd-id128/id128-util.c" nm_copy_sd_core "src/libsystemd/sd-id128/id128-util.h" nm_copy_sd_core "src/libsystemd/sd-id128/sd-id128.c" nm_copy_sd_core "src/systemd/_sd-common.h" nm_copy_sd_core "src/systemd/sd-dhcp-client.h" nm_copy_sd_core "src/systemd/sd-dhcp-lease.h" nm_copy_sd_core "src/systemd/sd-dhcp-option.h" nm_copy_sd_core "src/systemd/sd-dhcp6-client.h" nm_copy_sd_core "src/systemd/sd-dhcp6-lease.h" nm_copy_sd_core "src/systemd/sd-dhcp6-option.h" nm_copy_sd_core "src/systemd/sd-event.h" nm_copy_sd_core "src/systemd/sd-id128.h" nm_copy_sd_core "src/systemd/sd-ipv4acd.h" nm_copy_sd_core "src/systemd/sd-ipv4ll.h" nm_copy_sd_core "src/systemd/sd-lldp.h" nm_copy_sd_core "src/systemd/sd-ndisc.h" nm_copy_sd_shared "src/basic/alloc-util.c" nm_copy_sd_shared "src/basic/alloc-util.h" nm_copy_sd_shared "src/basic/async.h" nm_copy_sd_shared "src/basic/cgroup-util.h" nm_copy_sd_shared "src/basic/dns-def.h" nm_copy_sd_shared "src/basic/env-file.c" nm_copy_sd_shared "src/basic/env-file.h" nm_copy_sd_shared "src/basic/env-util.c" nm_copy_sd_shared "src/basic/env-util.h" nm_copy_sd_shared "src/basic/errno-util.h" nm_copy_sd_shared "src/basic/escape.c" nm_copy_sd_shared "src/basic/escape.h" nm_copy_sd_shared "src/basic/ether-addr-util.c" nm_copy_sd_shared "src/basic/ether-addr-util.h" nm_copy_sd_shared "src/basic/extract-word.c" nm_copy_sd_shared "src/basic/extract-word.h" nm_copy_sd_shared "src/basic/fd-util.c" nm_copy_sd_shared "src/basic/fd-util.h" nm_copy_sd_shared "src/basic/fileio.c" nm_copy_sd_shared "src/basic/fileio.h" nm_copy_sd_shared "src/basic/format-util.c" nm_copy_sd_shared "src/basic/format-util.h" nm_copy_sd_shared "src/basic/fs-util.c" nm_copy_sd_shared "src/basic/fs-util.h" nm_copy_sd_shared "src/basic/hash-funcs.c" nm_copy_sd_shared "src/basic/hash-funcs.h" nm_copy_sd_shared "src/basic/hashmap.c" nm_copy_sd_shared "src/basic/hashmap.h" nm_copy_sd_shared "src/basic/hexdecoct.c" nm_copy_sd_shared "src/basic/hexdecoct.h" nm_copy_sd_shared "src/basic/hostname-util.c" nm_copy_sd_shared "src/basic/hostname-util.h" nm_copy_sd_shared "src/basic/in-addr-util.c" nm_copy_sd_shared "src/basic/in-addr-util.h" nm_copy_sd_shared "src/basic/io-util.c" nm_copy_sd_shared "src/basic/io-util.h" nm_copy_sd_shared "src/basic/list.h" nm_copy_sd_shared "src/basic/log.h" nm_copy_sd_shared "src/basic/macro.h" nm_copy_sd_shared "src/basic/memory-util.c" nm_copy_sd_shared "src/basic/memory-util.h" nm_copy_sd_shared "src/basic/mempool.c" nm_copy_sd_shared "src/basic/mempool.h" nm_copy_sd_shared "src/basic/missing_fcntl.h" nm_copy_sd_shared "src/basic/missing_random.h" nm_copy_sd_shared "src/basic/missing_socket.h" nm_copy_sd_shared "src/basic/missing_stat.h" nm_copy_sd_shared "src/basic/missing_syscall.h" nm_copy_sd_shared "src/basic/missing_type.h" nm_copy_sd_shared "src/basic/ordered-set.c" nm_copy_sd_shared "src/basic/ordered-set.h" nm_copy_sd_shared "src/basic/parse-util.c" nm_copy_sd_shared "src/basic/parse-util.h" nm_copy_sd_shared "src/basic/path-util.c" nm_copy_sd_shared "src/basic/path-util.h" nm_copy_sd_shared "src/basic/prioq.c" nm_copy_sd_shared "src/basic/prioq.h" nm_copy_sd_shared "src/basic/process-util.c" nm_copy_sd_shared "src/basic/process-util.h" nm_copy_sd_shared "src/basic/random-util.c" nm_copy_sd_shared "src/basic/random-util.h" nm_copy_sd_shared "src/basic/ratelimit.c" nm_copy_sd_shared "src/basic/ratelimit.h" nm_copy_sd_shared "src/basic/set.h" nm_copy_sd_shared "src/basic/signal-util.c" nm_copy_sd_shared "src/basic/signal-util.h" nm_copy_sd_shared "src/basic/siphash24.h" nm_copy_sd_shared "src/basic/socket-util.c" nm_copy_sd_shared "src/basic/socket-util.h" nm_copy_sd_shared "src/basic/sort-util.h" nm_copy_sd_shared "src/basic/sparse-endian.h" nm_copy_sd_shared "src/basic/stat-util.c" nm_copy_sd_shared "src/basic/stat-util.h" nm_copy_sd_shared "src/basic/stdio-util.h" nm_copy_sd_shared "src/basic/string-table.c" nm_copy_sd_shared "src/basic/string-table.h" nm_copy_sd_shared "src/basic/string-util.c" nm_copy_sd_shared "src/basic/string-util.h" nm_copy_sd_shared "src/basic/strv.c" nm_copy_sd_shared "src/basic/strv.h" nm_copy_sd_shared "src/basic/strxcpyx.c" nm_copy_sd_shared "src/basic/strxcpyx.h" nm_copy_sd_shared "src/basic/time-util.c" nm_copy_sd_shared "src/basic/time-util.h" nm_copy_sd_shared "src/basic/tmpfile-util.c" nm_copy_sd_shared "src/basic/tmpfile-util.h" nm_copy_sd_shared "src/basic/umask-util.h" nm_copy_sd_shared "src/basic/user-util.h" nm_copy_sd_shared "src/basic/utf8.c" nm_copy_sd_shared "src/basic/utf8.h" nm_copy_sd_shared "src/basic/util.c" nm_copy_sd_shared "src/basic/util.h" nm_copy_sd_shared "src/fundamental/macro-fundamental.h" nm_copy_sd_shared "src/fundamental/string-util-fundamental.c" nm_copy_sd_shared "src/fundamental/string-util-fundamental.h" nm_copy_sd_shared "src/fundamental/type.h" nm_copy_sd_shared "src/shared/dns-domain.c" nm_copy_sd_shared "src/shared/dns-domain.h" nm_copy_sd_shared "src/shared/log-link.h" nm_copy_sd_shared "src/shared/web-util.c" nm_copy_sd_shared "src/shared/web-util.h" nm_copy_sd_stdaux "src/basic/unaligned.h" --- .../src/libsystemd-network/arp-util.c | 92 ++-- .../src/libsystemd-network/arp-util.h | 28 +- .../src/libsystemd-network/sd-dhcp-client.c | 153 +++---- .../src/libsystemd-network/sd-dhcp6-client.c | 10 +- .../src/libsystemd-network/sd-ipv4acd.c | 159 ++++--- .../src/libsystemd-network/sd-ipv4ll.c | 34 +- .../src/libsystemd/sd-event/sd-event.c | 14 +- .../src/libsystemd/sd-id128/sd-id128.c | 29 +- src/libnm-systemd-core/src/systemd/sd-ipv4acd.h | 2 + src/libnm-systemd-core/src/systemd/sd-ipv4ll.h | 2 + src/libnm-systemd-shared/src/basic/escape.c | 51 ++- src/libnm-systemd-shared/src/basic/escape.h | 10 +- .../src/basic/ether-addr-util.c | 10 + .../src/basic/ether-addr-util.h | 6 +- src/libnm-systemd-shared/src/basic/extract-word.c | 56 ++- src/libnm-systemd-shared/src/basic/extract-word.h | 8 +- src/libnm-systemd-shared/src/basic/fd-util.c | 501 ++++----------------- src/libnm-systemd-shared/src/basic/fd-util.h | 11 +- src/libnm-systemd-shared/src/basic/fileio.c | 53 ++- src/libnm-systemd-shared/src/basic/fileio.h | 1 + src/libnm-systemd-shared/src/basic/format-util.h | 17 +- src/libnm-systemd-shared/src/basic/fs-util.c | 183 ++++---- src/libnm-systemd-shared/src/basic/fs-util.h | 4 +- src/libnm-systemd-shared/src/basic/hashmap.c | 5 +- src/libnm-systemd-shared/src/basic/hashmap.h | 2 +- src/libnm-systemd-shared/src/basic/hexdecoct.c | 48 +- src/libnm-systemd-shared/src/basic/hexdecoct.h | 6 +- src/libnm-systemd-shared/src/basic/macro.h | 11 +- src/libnm-systemd-shared/src/basic/parse-util.c | 3 +- src/libnm-systemd-shared/src/basic/path-util.c | 10 +- src/libnm-systemd-shared/src/basic/path-util.h | 6 +- src/libnm-systemd-shared/src/basic/process-util.c | 136 +----- src/libnm-systemd-shared/src/basic/process-util.h | 8 +- src/libnm-systemd-shared/src/basic/signal-util.c | 35 ++ src/libnm-systemd-shared/src/basic/signal-util.h | 3 + src/libnm-systemd-shared/src/basic/socket-util.c | 27 +- src/libnm-systemd-shared/src/basic/socket-util.h | 1 + src/libnm-systemd-shared/src/basic/time-util.c | 231 ++++++---- src/libnm-systemd-shared/src/basic/time-util.h | 46 +- 39 files changed, 970 insertions(+), 1042 deletions(-) diff --git a/src/libnm-systemd-core/src/libsystemd-network/arp-util.c b/src/libnm-systemd-core/src/libsystemd-network/arp-util.c index 327fb2ff3e..d8d94ab5cb 100644 --- a/src/libnm-systemd-core/src/libsystemd-network/arp-util.c +++ b/src/libnm-systemd-core/src/libsystemd-network/arp-util.c @@ -8,11 +8,13 @@ #include #include "arp-util.h" +#include "ether-addr-util.h" #include "fd-util.h" +#include "in-addr-util.h" #include "unaligned.h" #include "util.h" -int arp_network_bind_raw_socket(int ifindex, be32_t address, const struct ether_addr *eth_mac) { +int arp_update_filter(int fd, const struct in_addr *a, const struct ether_addr *eth_mac) { struct sock_filter filter[] = { BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */ BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(struct ether_arp), 1, 0), /* packet >= arp packet ? */ @@ -46,13 +48,13 @@ int arp_network_bind_raw_socket(int ifindex, be32_t address, const struct ether_ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ /* Sender Protocol Address or Target Protocol Address must be equal to the one we care about */ - BPF_STMT(BPF_LD + BPF_IMM, htobe32(address)), /* A <- clients IP */ + BPF_STMT(BPF_LD + BPF_IMM, htobe32(a->s_addr)), /* A <- clients IP */ BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_spa)), /* A <- SPA */ BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* X xor A */ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */ BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */ - BPF_STMT(BPF_LD + BPF_IMM, htobe32(address)), /* A <- clients IP */ + BPF_STMT(BPF_LD + BPF_IMM, htobe32(a->s_addr)), /* A <- clients IP */ BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_tpa)), /* A <- TPA */ BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* X xor A */ @@ -61,15 +63,25 @@ int arp_network_bind_raw_socket(int ifindex, be32_t address, const struct ether_ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ }; struct sock_fprog fprog = { - .len = ELEMENTSOF(filter), - .filter = (struct sock_filter*) filter + .len = ELEMENTSOF(filter), + .filter = (struct sock_filter*) filter, }; + + assert(fd >= 0); + + if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)) < 0) + return -errno; + + return 0; +} + +int arp_network_bind_raw_socket(int ifindex, const struct in_addr *a, const struct ether_addr *eth_mac) { union sockaddr_union link = { - .ll.sll_family = AF_PACKET, + .ll.sll_family = AF_PACKET, .ll.sll_protocol = htobe16(ETH_P_ARP), - .ll.sll_ifindex = ifindex, - .ll.sll_halen = ETH_ALEN, - .ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, + .ll.sll_ifindex = ifindex, + .ll.sll_halen = ETH_ALEN, + .ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, }; _cleanup_close_ int s = -1; int r; @@ -80,59 +92,57 @@ int arp_network_bind_raw_socket(int ifindex, be32_t address, const struct ether_ if (s < 0) return -errno; - r = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); + r = arp_update_filter(s, a, eth_mac); if (r < 0) - return -errno; + return r; - r = bind(s, &link.sa, sizeof(link.ll)); - if (r < 0) + if (bind(s, &link.sa, sizeof(link.ll)) < 0) return -errno; return TAKE_FD(s); } -static int arp_send_packet(int fd, int ifindex, - be32_t pa, const struct ether_addr *ha, - bool announce) { +int arp_send_packet( + int fd, + int ifindex, + const struct in_addr *pa, + const struct ether_addr *ha, + bool announce) { + union sockaddr_union link = { - .ll.sll_family = AF_PACKET, + .ll.sll_family = AF_PACKET, .ll.sll_protocol = htobe16(ETH_P_ARP), - .ll.sll_ifindex = ifindex, - .ll.sll_halen = ETH_ALEN, - .ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, + .ll.sll_ifindex = ifindex, + .ll.sll_halen = ETH_ALEN, + .ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, }; struct ether_arp arp = { - .ea_hdr.ar_hrd = htobe16(ARPHRD_ETHER), /* HTYPE */ - .ea_hdr.ar_pro = htobe16(ETHERTYPE_IP), /* PTYPE */ - .ea_hdr.ar_hln = ETH_ALEN, /* HLEN */ - .ea_hdr.ar_pln = sizeof(be32_t), /* PLEN */ - .ea_hdr.ar_op = htobe16(ARPOP_REQUEST), /* REQUEST */ + .ea_hdr.ar_hrd = htobe16(ARPHRD_ETHER), /* HTYPE */ + .ea_hdr.ar_pro = htobe16(ETHERTYPE_IP), /* PTYPE */ + .ea_hdr.ar_hln = ETH_ALEN, /* HLEN */ + .ea_hdr.ar_pln = sizeof(struct in_addr), /* PLEN */ + .ea_hdr.ar_op = htobe16(ARPOP_REQUEST), /* REQUEST */ }; - int r; + ssize_t n; assert(fd >= 0); - assert(pa != 0); + assert(ifindex > 0); + assert(pa); + assert(in4_addr_is_set(pa)); assert(ha); + assert(!ether_addr_is_null(ha)); memcpy(&arp.arp_sha, ha, ETH_ALEN); - memcpy(&arp.arp_tpa, &pa, sizeof(pa)); + memcpy(&arp.arp_tpa, pa, sizeof(struct in_addr)); if (announce) - memcpy(&arp.arp_spa, &pa, sizeof(pa)); + memcpy(&arp.arp_spa, pa, sizeof(struct in_addr)); - r = sendto(fd, &arp, sizeof(struct ether_arp), 0, &link.sa, sizeof(link.ll)); - if (r < 0) + n = sendto(fd, &arp, sizeof(struct ether_arp), 0, &link.sa, sizeof(link.ll)); + if (n < 0) return -errno; + if (n != sizeof(struct ether_arp)) + return -EIO; return 0; } - -int arp_send_probe(int fd, int ifindex, - be32_t pa, const struct ether_addr *ha) { - return arp_send_packet(fd, ifindex, pa, ha, false); -} - -int arp_send_announcement(int fd, int ifindex, - be32_t pa, const struct ether_addr *ha) { - return arp_send_packet(fd, ifindex, pa, ha, true); -} diff --git a/src/libnm-systemd-core/src/libsystemd-network/arp-util.h b/src/libnm-systemd-core/src/libsystemd-network/arp-util.h index 2dac8cfbaa..e8615a158d 100644 --- a/src/libnm-systemd-core/src/libsystemd-network/arp-util.h +++ b/src/libnm-systemd-core/src/libsystemd-network/arp-util.h @@ -6,13 +6,31 @@ ***/ #include +#include #include "socket-util.h" #include "sparse-endian.h" -int arp_network_bind_raw_socket(int index, be32_t address, const struct ether_addr *eth_mac); +int arp_update_filter(int fd, const struct in_addr *a, const struct ether_addr *eth_mac); +int arp_network_bind_raw_socket(int ifindex, const struct in_addr *a, const struct ether_addr *eth_mac); -int arp_send_probe(int fd, int ifindex, - be32_t pa, const struct ether_addr *ha); -int arp_send_announcement(int fd, int ifindex, - be32_t pa, const struct ether_addr *ha); +int arp_send_packet( + int fd, + int ifindex, + const struct in_addr *pa, + const struct ether_addr *ha, + bool announce); +static inline int arp_send_probe( + int fd, + int ifindex, + const struct in_addr *pa, + const struct ether_addr *ha) { + return arp_send_packet(fd, ifindex, pa, ha, false); +} +static inline int arp_send_announcement( + int fd, + int ifindex, + const struct in_addr *pa, + const struct ether_addr *ha) { + return arp_send_packet(fd, ifindex, pa, ha, true); +} diff --git a/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp-client.c b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp-client.c index ff021f4eae..7fe660466f 100644 --- a/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp-client.c @@ -238,7 +238,7 @@ int sd_dhcp_client_set_callback( int sd_dhcp_client_set_request_broadcast(sd_dhcp_client *client, int broadcast) { assert_return(client, -EINVAL); - client->request_broadcast = !!broadcast; + client->request_broadcast = broadcast; return 0; } @@ -1587,9 +1587,17 @@ static int client_handle_forcerenew(sd_dhcp_client *client, DHCPMessage *force, if (r != DHCP_FORCERENEW) return -ENOMSG; +#if 0 log_dhcp_client(client, "FORCERENEW"); return 0; +#else + /* FIXME: Ignore FORCERENEW requests until we implement RFC3118 (Authentication for DHCP + * Messages) and/or RFC6704 (Forcerenew Nonce Authentication), as unauthenticated FORCERENEW + * requests causes a security issue (TALOS-2020-1142, CVE-2020-13529). */ + log_dhcp_client(client, "Received FORCERENEW, ignoring."); + return -ENOMSG; +#endif } static bool lease_equal(const sd_dhcp_lease *a, const sd_dhcp_lease *b) { @@ -1678,7 +1686,6 @@ static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack, size_t le static int client_set_lease_timeouts(sd_dhcp_client *client) { usec_t time_now; - char time_string[FORMAT_TIMESPAN_MAX]; int r; assert(client); @@ -1740,7 +1747,7 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) { return 0; log_dhcp_client(client, "lease expires in %s", - format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->expire_time - time_now, USEC_PER_SEC)); + FORMAT_TIMESPAN(client->expire_time - time_now, USEC_PER_SEC)); /* arm T2 timeout */ r = event_reset_time(client->event, &client->timeout_t2, @@ -1756,7 +1763,7 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) { return 0; log_dhcp_client(client, "T2 expires in %s", - format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->t2_time - time_now, USEC_PER_SEC)); + FORMAT_TIMESPAN(client->t2_time - time_now, USEC_PER_SEC)); /* arm T1 timeout */ r = event_reset_time(client->event, &client->timeout_t1, @@ -1769,15 +1776,14 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) { if (client->t1_time > time_now) log_dhcp_client(client, "T1 expires in %s", - format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->t1_time - time_now, USEC_PER_SEC)); + FORMAT_TIMESPAN(client->t1_time - time_now, USEC_PER_SEC)); return 0; } static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, int len) { DHCP_CLIENT_DONT_DESTROY(client); - char time_string[FORMAT_TIMESPAN_MAX]; - int r = 0, notify_event = 0; + int r, notify_event; assert(client); assert(client->event); @@ -1787,22 +1793,19 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, i case DHCP_STATE_SELECTING: r = client_handle_offer(client, message, len); - if (r >= 0) { - - client->state = DHCP_STATE_REQUESTING; - client->attempt = 0; + if (r == -ENOMSG) + return 0; /* invalid message, let's ignore it */ + if (r < 0) + goto error; - r = event_reset_time(client->event, &client->timeout_resend, - clock_boottime_or_monotonic(), - 0, 0, - client_timeout_resend, client, - client->event_priority, "dhcp4-resend-timer", true); - if (r < 0) - goto error; - } else if (r == -ENOMSG) - /* invalid message, let's ignore it */ - return 0; + client->state = DHCP_STATE_REQUESTING; + client->attempt = 0; + r = event_reset_time(client->event, &client->timeout_resend, + clock_boottime_or_monotonic(), + 0, 0, + client_timeout_resend, client, + client->event_priority, "dhcp4-resend-timer", true); break; case DHCP_STATE_REBOOTING: @@ -1811,47 +1814,9 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, i case DHCP_STATE_REBINDING: r = client_handle_ack(client, message, len); - if (r >= 0) { - client->start_delay = 0; - (void) event_source_disable(client->timeout_resend); - client->receive_message = - sd_event_source_unref(client->receive_message); - client->fd = safe_close(client->fd); - - if (IN_SET(client->state, DHCP_STATE_REQUESTING, - DHCP_STATE_REBOOTING)) - notify_event = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE; - else if (r != SD_DHCP_CLIENT_EVENT_IP_ACQUIRE) - notify_event = r; - - client->state = DHCP_STATE_BOUND; - client->attempt = 0; - - client->last_addr = client->lease->address; - - r = client_set_lease_timeouts(client); - if (r < 0) { - log_dhcp_client(client, "could not set lease timeouts"); - goto error; - } - - r = dhcp_network_bind_udp_socket(client->ifindex, client->lease->address, client->port, client->ip_service_type); - if (r < 0) { - log_dhcp_client(client, "could not bind UDP socket"); - goto error; - } - - client->fd = r; - - client_initialize_io_events(client, client_receive_message_udp); - - if (notify_event) { - client_notify(client, notify_event); - if (client->state == DHCP_STATE_STOPPED) - return 0; - } - - } else if (r == -EADDRNOTAVAIL) { + if (r == -ENOMSG) + return 0; /* invalid message, let's ignore it */ + if (r == -EADDRNOTAVAIL) { /* got a NAK, let's restart the client */ client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED); @@ -1863,39 +1828,75 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, i if (r < 0) goto error; - log_dhcp_client(client, "REBOOT in %s", format_timespan(time_string, FORMAT_TIMESPAN_MAX, - client->start_delay, USEC_PER_SEC)); + log_dhcp_client(client, "REBOOT in %s", FORMAT_TIMESPAN(client->start_delay, USEC_PER_SEC)); client->start_delay = CLAMP(client->start_delay * 2, RESTART_AFTER_NAK_MIN_USEC, RESTART_AFTER_NAK_MAX_USEC); - - return 0; - } else if (r == -ENOMSG) - /* invalid message, let's ignore it */ return 0; + } + if (r < 0) + goto error; + if (IN_SET(client->state, DHCP_STATE_REQUESTING, DHCP_STATE_REBOOTING)) + notify_event = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE; + else + notify_event = r; + + client->start_delay = 0; + (void) event_source_disable(client->timeout_resend); + client->receive_message = sd_event_source_unref(client->receive_message); + client->fd = safe_close(client->fd); + + client->state = DHCP_STATE_BOUND; + client->attempt = 0; + + client->last_addr = client->lease->address; + + r = client_set_lease_timeouts(client); + if (r < 0) { + log_dhcp_client(client, "could not set lease timeouts"); + goto error; + } + + r = dhcp_network_bind_udp_socket(client->ifindex, client->lease->address, client->port, client->ip_service_type); + if (r < 0) { + log_dhcp_client(client, "could not bind UDP socket"); + goto error; + } + + client->fd = r; + + client_initialize_io_events(client, client_receive_message_udp); + + if (IN_SET(client->state, DHCP_STATE_RENEWING, DHCP_STATE_REBINDING) && + notify_event == SD_DHCP_CLIENT_EVENT_IP_ACQUIRE) + /* FIXME: hmm, maybe this is a bug... */ + log_dhcp_client(client, "client_handle_ack() returned SD_DHCP_CLIENT_EVENT_IP_ACQUIRE while DHCP client is %s the address, skipping callback.", + client->state == DHCP_STATE_RENEWING ? "renewing" : "rebinding"); + else + client_notify(client, notify_event); break; case DHCP_STATE_BOUND: r = client_handle_forcerenew(client, message, len); - if (r >= 0) { - r = client_timeout_t1(NULL, 0, client); - if (r < 0) - goto error; - } else if (r == -ENOMSG) - /* invalid message, let's ignore it */ - return 0; + if (r == -ENOMSG) + return 0; /* invalid message, let's ignore it */ + if (r < 0) + goto error; + r = client_timeout_t1(NULL, 0, client); break; case DHCP_STATE_INIT: case DHCP_STATE_INIT_REBOOT: - + r = 0; break; case DHCP_STATE_STOPPED: r = -EINVAL; goto error; + default: + assert_not_reached("invalid state"); } error: diff --git a/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c index e8c47f429a..079f154acd 100644 --- a/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c @@ -946,7 +946,6 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userda usec_t time_now, init_retransmit_time = 0, max_retransmit_time = 0; usec_t max_retransmit_duration = 0; uint8_t max_retransmit_count = 0; - char time_string[FORMAT_TIMESPAN_MAX]; assert(s); assert(client); @@ -1042,7 +1041,7 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userda } log_dhcp6_client(client, "Next retransmission in %s", - format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->retransmit_time, USEC_PER_SEC)); + FORMAT_TIMESPAN(client->retransmit_time, USEC_PER_SEC)); r = event_reset_time(client->event, &client->timeout_resend, clock_boottime_or_monotonic(), @@ -1566,7 +1565,6 @@ static int client_get_lifetime(sd_dhcp6_client *client, uint32_t *lifetime_t1, static int client_start(sd_dhcp6_client *client, enum DHCP6State state) { int r; usec_t timeout, time_now; - char time_string[FORMAT_TIMESPAN_MAX]; uint32_t lifetime_t1, lifetime_t2; assert_return(client, -EINVAL); @@ -1639,8 +1637,7 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state) { timeout = client_timeout_compute_random(lifetime_t1 * USEC_PER_SEC); - log_dhcp6_client(client, "T1 expires in %s", - format_timespan(time_string, FORMAT_TIMESPAN_MAX, timeout, USEC_PER_SEC)); + log_dhcp6_client(client, "T1 expires in %s", FORMAT_TIMESPAN(timeout, USEC_PER_SEC)); r = event_reset_time(client->event, &client->timeout_t1, clock_boottime_or_monotonic(), @@ -1652,8 +1649,7 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state) { timeout = client_timeout_compute_random(lifetime_t2 * USEC_PER_SEC); - log_dhcp6_client(client, "T2 expires in %s", - format_timespan(time_string, FORMAT_TIMESPAN_MAX, timeout, USEC_PER_SEC)); + log_dhcp6_client(client, "T2 expires in %s", FORMAT_TIMESPAN(timeout, USEC_PER_SEC)); r = event_reset_time(client->event, &client->timeout_t2, clock_boottime_or_monotonic(), diff --git a/src/libnm-systemd-core/src/libsystemd-network/sd-ipv4acd.c b/src/libnm-systemd-core/src/libsystemd-network/sd-ipv4acd.c index b4af60741a..c6aa9aa11a 100644 --- a/src/libnm-systemd-core/src/libsystemd-network/sd-ipv4acd.c +++ b/src/libnm-systemd-core/src/libsystemd-network/sd-ipv4acd.c @@ -18,6 +18,7 @@ #include "fd-util.h" #include "in-addr-util.h" #include "log-link.h" +#include "memory-util.h" #include "network-common.h" #include "random-util.h" #include "siphash24.h" @@ -64,7 +65,7 @@ struct sd_ipv4acd { sd_event_source *timer_event_source; usec_t defend_window; - be32_t address; + struct in_addr address; /* External */ struct ether_addr mac_addr; @@ -72,7 +73,9 @@ struct sd_ipv4acd { sd_event *event; int event_priority; sd_ipv4acd_callback_t callback; - void* userdata; + void *userdata; + sd_ipv4acd_check_mac_callback_t check_mac_callback; + void *check_mac_userdata; }; #define log_ipv4acd_errno(acd, error, fmt, ...) \ @@ -208,18 +211,6 @@ static int ipv4acd_set_next_wakeup(sd_ipv4acd *acd, usec_t usec, usec_t random_u acd->event_priority, "ipv4acd-timer", true); } -static bool ipv4acd_arp_conflict(sd_ipv4acd *acd, struct ether_arp *arp) { - assert(acd); - assert(arp); - - /* see the BPF */ - if (memcmp(arp->arp_spa, &acd->address, sizeof(acd->address)) == 0) - return true; - - /* the TPA matched instead of the SPA, this is not a conflict */ - return false; -} - static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) { sd_ipv4acd *acd = userdata; int r = 0; @@ -229,38 +220,32 @@ static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) switch (acd->state) { case IPV4ACD_STATE_STARTED: + acd->defend_window = 0; + ipv4acd_set_state(acd, IPV4ACD_STATE_WAITING_PROBE, true); if (acd->n_conflict >= MAX_CONFLICTS) { - char ts[FORMAT_TIMESPAN_MAX]; - log_ipv4acd(acd, "Max conflicts reached, delaying by %s", format_timespan(ts, sizeof(ts), RATE_LIMIT_INTERVAL_USEC, 0)); - + log_ipv4acd(acd, "Max conflicts reached, delaying by %s", + FORMAT_TIMESPAN(RATE_LIMIT_INTERVAL_USEC, 0)); r = ipv4acd_set_next_wakeup(acd, RATE_LIMIT_INTERVAL_USEC, PROBE_WAIT_USEC); - if (r < 0) - goto fail; - } else { + } else r = ipv4acd_set_next_wakeup(acd, 0, PROBE_WAIT_USEC); - if (r < 0) - goto fail; - } + if (r < 0) + goto fail; break; case IPV4ACD_STATE_WAITING_PROBE: case IPV4ACD_STATE_PROBING: /* Send a probe */ - r = arp_send_probe(acd->fd, acd->ifindex, acd->address, &acd->mac_addr); + r = arp_send_probe(acd->fd, acd->ifindex, &acd->address, &acd->mac_addr); if (r < 0) { log_ipv4acd_errno(acd, r, "Failed to send ARP probe: %m"); goto fail; - } else { - _cleanup_free_ char *address = NULL; - union in_addr_union addr = { .in.s_addr = acd->address }; - - (void) in_addr_to_string(AF_INET, &addr, &address); - log_ipv4acd(acd, "Probing %s", strna(address)); } + log_ipv4acd(acd, "Probing "IPV4_ADDRESS_FMT_STR, IPV4_ADDRESS_FMT_VAL(acd->address)); + if (acd->n_iteration < PROBE_NUM - 2) { ipv4acd_set_state(acd, IPV4ACD_STATE_PROBING, false); @@ -286,12 +271,13 @@ static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) _fallthrough_; case IPV4ACD_STATE_WAITING_ANNOUNCE: /* Send announcement packet */ - r = arp_send_announcement(acd->fd, acd->ifindex, acd->address, &acd->mac_addr); + r = arp_send_announcement(acd->fd, acd->ifindex, &acd->address, &acd->mac_addr); if (r < 0) { log_ipv4acd_errno(acd, r, "Failed to send ARP announcement: %m"); goto fail; - } else - log_ipv4acd(acd, "ANNOUNCE"); + } + + log_ipv4acd(acd, "Announcing "IPV4_ADDRESS_FMT_STR, IPV4_ADDRESS_FMT_VAL(acd->address)); ipv4acd_set_state(acd, IPV4ACD_STATE_ANNOUNCING, false); @@ -317,16 +303,45 @@ fail: return 0; } -static void ipv4acd_on_conflict(sd_ipv4acd *acd) { - _cleanup_free_ char *address = NULL; - union in_addr_union addr = { .in.s_addr = acd->address }; +static bool ipv4acd_arp_conflict(sd_ipv4acd *acd, const struct ether_arp *arp, bool announced) { + assert(acd); + assert(arp); + + /* RFC 5227 section 2.1.1. + * "the host receives any ARP packet (Request *or* Reply) on the interface where the probe is + * being performed, where the packet's 'sender IP address' is the address being probed for, + * then the host MUST treat this address as being in use by some other host" */ + if (memcmp(arp->arp_spa, &acd->address, sizeof(struct in_addr)) == 0) + return true; + + if (announced) + /* the TPA matched instead of SPA, this is not a conflict */ + return false; + + /* "any ARP Probe where the packet's 'target IP address' is the address being probed for, and + * the packet's 'sender hardware address' is not the hardware address of any of the host's + * interfaces, then the host SHOULD similarly treat this as an address conflict" */ + if (arp->ea_hdr.ar_op != htobe16(ARPOP_REQUEST)) + return false; /* not ARP Request, ignoring. */ + if (memeqzero(arp->arp_spa, sizeof(struct in_addr)) == 0) + return false; /* not ARP Probe, ignoring. */ + if (memcmp(arp->arp_tpa, &acd->address, sizeof(struct in_addr)) != 0) + return false; /* target IP address does not match, BPF code is broken? */ + + if (acd->check_mac_callback && + acd->check_mac_callback(acd, (const struct ether_addr*) arp->arp_sha, acd->check_mac_userdata) > 0) + /* sender hardware is one of the host's interfaces, ignoring. */ + return true; + + return true; /* conflict! */ +} +static void ipv4acd_on_conflict(sd_ipv4acd *acd) { assert(acd); acd->n_conflict++; - (void) in_addr_to_string(AF_INET, &addr, &address); - log_ipv4acd(acd, "Conflict on %s (%u)", strna(address), acd->n_conflict); + log_ipv4acd(acd, "Conflict on "IPV4_ADDRESS_FMT_STR" (%u)", IPV4_ADDRESS_FMT_VAL(acd->address), acd->n_conflict); ipv4acd_reset(acd); ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_CONFLICT); @@ -365,7 +380,7 @@ static int ipv4acd_on_packet( case IPV4ACD_STATE_ANNOUNCING: case IPV4ACD_STATE_RUNNING: - if (ipv4acd_arp_conflict(acd, &packet)) { + if (ipv4acd_arp_conflict(acd, &packet, true)) { usec_t ts; assert_se(sd_event_now(acd->event, clock_boottime_or_monotonic(), &ts) >= 0); @@ -373,12 +388,13 @@ static int ipv4acd_on_packet( /* Defend address */ if (ts > acd->defend_window) { acd->defend_window = ts + DEFEND_INTERVAL_USEC; - r = arp_send_announcement(acd->fd, acd->ifindex, acd->address, &acd->mac_addr); + r = arp_send_announcement(acd->fd, acd->ifindex, &acd->address, &acd->mac_addr); if (r < 0) { log_ipv4acd_errno(acd, r, "Failed to send ARP announcement: %m"); goto fail; - } else - log_ipv4acd(acd, "DEFEND"); + } + + log_ipv4acd(acd, "Defending "IPV4_ADDRESS_FMT_STR, IPV4_ADDRESS_FMT_VAL(acd->address)); } else ipv4acd_on_conflict(acd); @@ -388,8 +404,8 @@ static int ipv4acd_on_packet( case IPV4ACD_STATE_WAITING_PROBE: case IPV4ACD_STATE_PROBING: case IPV4ACD_STATE_WAITING_ANNOUNCE: - /* BPF ensures this packet indicates a conflict */ - ipv4acd_on_conflict(acd); + if (ipv4acd_arp_conflict(acd, &packet, false)) + ipv4acd_on_conflict(acd); break; default: @@ -438,12 +454,24 @@ const char *sd_ipv4acd_get_ifname(sd_ipv4acd *acd) { } int sd_ipv4acd_set_mac(sd_ipv4acd *acd, const struct ether_addr *addr) { + int r; + assert_return(acd, -EINVAL); assert_return(addr, -EINVAL); - assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY); + assert_return(!ether_addr_is_null(addr), -EINVAL); acd->mac_addr = *addr; + if (!sd_ipv4acd_is_running(acd)) + return 0; + + assert(acd->fd >= 0); + r = arp_update_filter(acd->fd, &acd->address, &acd->mac_addr); + if (r < 0) { + ipv4acd_reset(acd); + return r; + } + return 0; } @@ -483,21 +511,51 @@ int sd_ipv4acd_set_callback(sd_ipv4acd *acd, sd_ipv4acd_callback_t cb, void *use return 0; } +int sd_ipv4acd_set_check_mac_callback(sd_ipv4acd *acd, sd_ipv4acd_check_mac_callback_t cb, void *userdata) { + assert_return(acd, -EINVAL); + + acd->check_mac_callback = cb; + acd->check_mac_userdata = userdata; + return 0; +} + int sd_ipv4acd_set_address(sd_ipv4acd *acd, const struct in_addr *address) { + int r; + assert_return(acd, -EINVAL); assert_return(address, -EINVAL); - assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY); + assert_return(in4_addr_is_set(address), -EINVAL); - acd->address = address->s_addr; + if (in4_addr_equal(&acd->address, address)) + return 0; + + acd->address = *address; + + if (!sd_ipv4acd_is_running(acd)) + return 0; + + assert(acd->fd >= 0); + r = arp_update_filter(acd->fd, &acd->address, &acd->mac_addr); + if (r < 0) + goto fail; + + r = ipv4acd_set_next_wakeup(acd, 0, 0); + if (r < 0) + goto fail; + ipv4acd_set_state(acd, IPV4ACD_STATE_STARTED, true); return 0; + +fail: + ipv4acd_reset(acd); + return r; } int sd_ipv4acd_get_address(sd_ipv4acd *acd, struct in_addr *address) { assert_return(acd, -EINVAL); assert_return(address, -EINVAL); - address->s_addr = acd->address; + *address = acd->address; return 0; } @@ -514,16 +572,15 @@ int sd_ipv4acd_start(sd_ipv4acd *acd, bool reset_conflicts) { assert_return(acd, -EINVAL); assert_return(acd->event, -EINVAL); assert_return(acd->ifindex > 0, -EINVAL); - assert_return(acd->address != 0, -EINVAL); + assert_return(in4_addr_is_set(&acd->address), -EINVAL); assert_return(!ether_addr_is_null(&acd->mac_addr), -EINVAL); assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY); - r = arp_network_bind_raw_socket(acd->ifindex, acd->address, &acd->mac_addr); + r = arp_network_bind_raw_socket(acd->ifindex, &acd->address, &acd->mac_addr); if (r < 0) return r; CLOSE_AND_REPLACE(acd->fd, r); - acd->defend_window = 0; if (reset_conflicts) acd->n_conflict = 0; diff --git a/src/libnm-systemd-core/src/libsystemd-network/sd-ipv4ll.c b/src/libnm-systemd-core/src/libsystemd-network/sd-ipv4ll.c index 8053afee93..7b9d63ca3c 100644 --- a/src/libnm-systemd-core/src/libsystemd-network/sd-ipv4ll.c +++ b/src/libnm-systemd-core/src/libsystemd-network/sd-ipv4ll.c @@ -46,7 +46,10 @@ struct sd_ipv4ll { be32_t claimed_address; sd_ipv4ll_callback_t callback; - void* userdata; + void *userdata; + + sd_ipv4ll_check_mac_callback_t check_mac_callback; + void *check_mac_userdata; }; #define log_ipv4ll_errno(ll, error, fmt, ...) \ @@ -60,7 +63,8 @@ struct sd_ipv4ll { sd_ipv4ll_get_ifname(ll), \ 0, fmt, ##__VA_ARGS__) -static void ipv4ll_on_acd(sd_ipv4acd *ll, int event, void *userdata); +static void ipv4ll_on_acd(sd_ipv4acd *acd, int event, void *userdata); +static int ipv4ll_check_mac(sd_ipv4acd *acd, const struct ether_addr *mac, void *userdata); static sd_ipv4ll *ipv4ll_free(sd_ipv4ll *ll) { assert(ll); @@ -91,6 +95,10 @@ int sd_ipv4ll_new(sd_ipv4ll **ret) { if (r < 0) return r; + r = sd_ipv4acd_set_check_mac_callback(ll->acd, ipv4ll_check_mac, ll); + if (r < 0) + return r; + *ret = TAKE_PTR(ll); return 0; @@ -137,7 +145,7 @@ int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr) { assert_return(ll, -EINVAL); assert_return(addr, -EINVAL); - assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY); + assert_return(!ether_addr_is_null(addr), -EINVAL); r = sd_ipv4acd_set_mac(ll->acd, addr); if (r < 0) @@ -168,6 +176,15 @@ int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_callback_t cb, void *userdat return 0; } +int sd_ipv4ll_set_check_mac_callback(sd_ipv4ll *ll, sd_ipv4ll_check_mac_callback_t cb, void *userdata) { + assert_return(ll, -EINVAL); + + ll->check_mac_callback = cb; + ll->check_mac_userdata = userdata; + + return 0; +} + int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address) { assert_return(ll, -EINVAL); assert_return(address, -EINVAL); @@ -351,3 +368,14 @@ void ipv4ll_on_acd(sd_ipv4acd *acd, int event, void *userdata) { error: ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_STOP); } + +static int ipv4ll_check_mac(sd_ipv4acd *acd, const struct ether_addr *mac, void *userdata) { + sd_ipv4ll *ll = userdata; + + assert(ll); + + if (ll->check_mac_callback) + return ll->check_mac_callback(ll, mac, ll->check_mac_userdata); + + return 0; +} diff --git a/src/libnm-systemd-core/src/libsystemd/sd-event/sd-event.c b/src/libnm-systemd-core/src/libsystemd/sd-event/sd-event.c index cc9d9f5d9b..e9199deb41 100644 --- a/src/libnm-systemd-core/src/libsystemd/sd-event/sd-event.c +++ b/src/libnm-systemd-core/src/libsystemd/sd-event/sd-event.c @@ -85,6 +85,11 @@ DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(event_source_type, int); SOURCE_DEFER, \ SOURCE_INOTIFY) +/* This is used to assert that we didn't pass an unexpected source type to event_source_time_prioq_put(). + * Time sources and ratelimited sources can be passed, so effectively this is the same as the + * EVENT_SOURCE_CAN_RATE_LIMIT() macro. */ +#define EVENT_SOURCE_USES_TIME_PRIOQ(t) EVENT_SOURCE_CAN_RATE_LIMIT(t) + struct sd_event { unsigned n_ref; @@ -1198,6 +1203,7 @@ static int event_source_time_prioq_put( assert(s); assert(d); + assert(EVENT_SOURCE_USES_TIME_PRIOQ(s->type)); r = prioq_put(d->earliest, s, &s->earliest_index); if (r < 0) @@ -2974,6 +2980,7 @@ static int event_arm_timer( d->needs_rearm = false; a = prioq_peek(d->earliest); + assert(!a || EVENT_SOURCE_USES_TIME_PRIOQ(a->type)); if (!a || a->enabled == SD_EVENT_OFF || time_event_source_next(a) == USEC_INFINITY) { if (d->fd < 0) @@ -2991,7 +2998,8 @@ static int event_arm_timer( } b = prioq_peek(d->latest); - assert_se(b && b->enabled != SD_EVENT_OFF); + assert(!b || EVENT_SOURCE_USES_TIME_PRIOQ(b->type)); + assert(b && b->enabled != SD_EVENT_OFF); t = sleep_between(e, time_event_source_next(a), time_event_source_latest(b)); if (d->next == t) @@ -3071,6 +3079,8 @@ static int process_timer( for (;;) { s = prioq_peek(d->earliest); + assert(!s || EVENT_SOURCE_USES_TIME_PRIOQ(s->type)); + if (!s || time_event_source_next(s) > n) break; @@ -3631,6 +3641,8 @@ static int dispatch_exit(sd_event *e) { assert(e); p = prioq_peek(e->exit); + assert(!p || p->type == SOURCE_EXIT); + if (!p || event_source_is_offline(p)) { e->state = SD_EVENT_FINISHED; return 0; diff --git a/src/libnm-systemd-core/src/libsystemd/sd-id128/sd-id128.c b/src/libnm-systemd-core/src/libsystemd/sd-id128/sd-id128.c index d5de935c77..28ae10a198 100644 --- a/src/libnm-systemd-core/src/libsystemd/sd-id128/sd-id128.c +++ b/src/libnm-systemd-core/src/libsystemd/sd-id128/sd-id128.c @@ -4,6 +4,11 @@ #include #include +#if HAVE_OPENSSL +#include +#include +#endif + #include "sd-id128.h" #include "alloc-util.h" @@ -11,7 +16,9 @@ #include "hexdecoct.h" #include "id128-util.h" #include "io-util.h" +#if !HAVE_OPENSSL #include "khash.h" +#endif #include "macro.h" #include "missing_syscall.h" #include "random-util.h" @@ -271,13 +278,28 @@ _public_ int sd_id128_randomize(sd_id128_t *ret) { } static int get_app_specific(sd_id128_t base, sd_id128_t app_id, sd_id128_t *ret) { - _cleanup_(khash_unrefp) khash *h = NULL; sd_id128_t result; - const void *p; - int r; assert(ret); +#if HAVE_OPENSSL + /* We prefer doing this in-process, since we this means we are not dependent on kernel configuration, + * and this also works in locked down container environments. But some distros don't like OpenSSL's + * license and its (in-) compatibility with GPL2, hence also support khash */ + uint8_t md[256/8]; + if (!HMAC(EVP_sha256(), + &base, sizeof(base), + (const unsigned char*) &app_id, sizeof(app_id), + md, NULL)) + return -ENOTRECOVERABLE; + + /* Take only the first half. */ + memcpy(&result, md, MIN(sizeof(md), sizeof(result))); +#else + _cleanup_(khash_unrefp) khash *h = NULL; + const void *p; + int r; + r = khash_new_with_key(&h, "hmac(sha256)", &base, sizeof(base)); if (r < 0) return r; @@ -292,6 +314,7 @@ static int get_app_specific(sd_id128_t base, sd_id128_t app_id, sd_id128_t *ret) /* We chop off the trailing 16 bytes */ memcpy(&result, p, MIN(khash_get_size(h), sizeof(result))); +#endif *ret = id128_make_v4_uuid(result); return 0; diff --git a/src/libnm-systemd-core/src/systemd/sd-ipv4acd.h b/src/libnm-systemd-core/src/systemd/sd-ipv4acd.h index 1e89a81b31..3213db553c 100644 --- a/src/libnm-systemd-core/src/systemd/sd-ipv4acd.h +++ b/src/libnm-systemd-core/src/systemd/sd-ipv4acd.h @@ -36,11 +36,13 @@ enum { typedef struct sd_ipv4acd sd_ipv4acd; typedef void (*sd_ipv4acd_callback_t)(sd_ipv4acd *acd, int event, void *userdata); +typedef int (*sd_ipv4acd_check_mac_callback_t)(sd_ipv4acd *acd, const struct ether_addr *mac, void *userdata); int sd_ipv4acd_detach_event(sd_ipv4acd *acd); int sd_ipv4acd_attach_event(sd_ipv4acd *acd, sd_event *event, int64_t priority); int sd_ipv4acd_get_address(sd_ipv4acd *acd, struct in_addr *address); int sd_ipv4acd_set_callback(sd_ipv4acd *acd, sd_ipv4acd_callback_t cb, void *userdata); +int sd_ipv4acd_set_check_mac_callback(sd_ipv4acd *acd, sd_ipv4acd_check_mac_callback_t cb, void *userdata); int sd_ipv4acd_set_mac(sd_ipv4acd *acd, const struct ether_addr *addr); int sd_ipv4acd_set_ifindex(sd_ipv4acd *acd, int interface_index); int sd_ipv4acd_get_ifindex(sd_ipv4acd *acd); diff --git a/src/libnm-systemd-core/src/systemd/sd-ipv4ll.h b/src/libnm-systemd-core/src/systemd/sd-ipv4ll.h index bf5596ab61..27263bc837 100644 --- a/src/libnm-systemd-core/src/systemd/sd-ipv4ll.h +++ b/src/libnm-systemd-core/src/systemd/sd-ipv4ll.h @@ -36,11 +36,13 @@ enum { typedef struct sd_ipv4ll sd_ipv4ll; typedef void (*sd_ipv4ll_callback_t)(sd_ipv4ll *ll, int event, void *userdata); +typedef int (*sd_ipv4ll_check_mac_callback_t)(sd_ipv4ll *ll, const struct ether_addr *mac, void *userdata); int sd_ipv4ll_detach_event(sd_ipv4ll *ll); int sd_ipv4ll_attach_event(sd_ipv4ll *ll, sd_event *event, int64_t priority); int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address); int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_callback_t cb, void *userdata); +int sd_ipv4ll_set_check_mac_callback(sd_ipv4ll *ll, sd_ipv4ll_check_mac_callback_t cb, void *userdata); int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr); int sd_ipv4ll_set_ifindex(sd_ipv4ll *ll, int interface_index); int sd_ipv4ll_get_ifindex(sd_ipv4ll *ll); diff --git a/src/libnm-systemd-shared/src/basic/escape.c b/src/libnm-systemd-shared/src/basic/escape.c index 2a3a0e31a1..4bb98f9342 100644 --- a/src/libnm-systemd-shared/src/basic/escape.c +++ b/src/libnm-systemd-shared/src/basic/escape.c @@ -8,6 +8,7 @@ #include "escape.h" #include "hexdecoct.h" #include "macro.h" +#include "strv.h" #include "utf8.h" int cescape_char(char c, char *buf) { @@ -288,10 +289,12 @@ int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit, return r; } -int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret) { - char *r, *t; +ssize_t cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret) { + _cleanup_free_ char *ans = NULL; + char *t; const char *f; size_t pl; + int r; assert(s); assert(ret); @@ -300,18 +303,17 @@ int cunescape_length_with_prefix(const char *s, size_t length, const char *prefi pl = strlen_ptr(prefix); - r = new(char, pl+length+1); - if (!r) + ans = new(char, pl+length+1); + if (!ans) return -ENOMEM; if (prefix) - memcpy(r, prefix, pl); + memcpy(ans, prefix, pl); - for (f = s, t = r + pl; f < s + length; f++) { + for (f = s, t = ans + pl; f < s + length; f++) { size_t remaining; bool eight_bit = false; char32_t u; - int k; remaining = s + length - f; assert(remaining > 0); @@ -329,23 +331,21 @@ int cunescape_length_with_prefix(const char *s, size_t length, const char *prefi continue; } - free(r); return -EINVAL; } - k = cunescape_one(f + 1, remaining - 1, &u, &eight_bit, flags & UNESCAPE_ACCEPT_NUL); - if (k < 0) { + r = cunescape_one(f + 1, remaining - 1, &u, &eight_bit, flags & UNESCAPE_ACCEPT_NUL); + if (r < 0) { if (flags & UNESCAPE_RELAX) { /* Invalid escape code, let's take it literal then */ *(t++) = '\\'; continue; } - free(r); - return k; + return r; } - f += k; + f += r; if (eight_bit) /* One byte? Set directly as specified */ *(t++) = u; @@ -356,8 +356,9 @@ int cunescape_length_with_prefix(const char *s, size_t length, const char *prefi *t = 0; - *ret = r; - return t - r; + assert(t >= ans); /* Let static analyzers know that the answer is non-negative. */ + *ret = TAKE_PTR(ans); + return t - *ret; } char* xescape_full(const char *s, const char *bad, size_t console_width, XEscapeFlags flags) { @@ -542,3 +543,23 @@ char* shell_maybe_quote(const char *s, ShellEscapeFlags flags) { return str_realloc(buf); } + +char* quote_command_line(char **argv) { + _cleanup_free_ char *result = NULL; + + assert(argv); + + char **a; + STRV_FOREACH(a, argv) { + _cleanup_free_ char *t = NULL; + + t = shell_maybe_quote(*a, SHELL_ESCAPE_EMPTY); + if (!t) + return NULL; + + if (!strextend_with_separator(&result, " ", t)) + return NULL; + } + + return TAKE_PTR(result); +} diff --git a/src/libnm-systemd-shared/src/basic/escape.h b/src/libnm-systemd-shared/src/basic/escape.h index 907b572bd4..d490510deb 100644 --- a/src/libnm-systemd-shared/src/basic/escape.h +++ b/src/libnm-systemd-shared/src/basic/escape.h @@ -45,14 +45,15 @@ char* cescape(const char *s); char* cescape_length(const char *s, size_t n); int cescape_char(char c, char *buf); -int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret); -static inline int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret) { +int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit, bool accept_nul); + +ssize_t cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret); +static inline ssize_t cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret) { return cunescape_length_with_prefix(s, length, NULL, flags, ret); } -static inline int cunescape(const char *s, UnescapeFlags flags, char **ret) { +static inline ssize_t cunescape(const char *s, UnescapeFlags flags, char **ret) { return cunescape_length(s, strlen(s), flags, ret); } -int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit, bool accept_nul); typedef enum XEscapeFlags { XESCAPE_8_BIT = 1 << 0, @@ -68,3 +69,4 @@ char* escape_non_printable_full(const char *str, size_t console_width, XEscapeFl char* shell_escape(const char *s, const char *bad); char* shell_maybe_quote(const char *s, ShellEscapeFlags flags); +char* quote_command_line(char **argv); diff --git a/src/libnm-systemd-shared/src/basic/ether-addr-util.c b/src/libnm-systemd-shared/src/basic/ether-addr-util.c index 3925810768..e660ac2c6f 100644 --- a/src/libnm-systemd-shared/src/basic/ether-addr-util.c +++ b/src/libnm-systemd-shared/src/basic/ether-addr-util.c @@ -37,6 +37,16 @@ int hw_addr_compare(const struct hw_addr_data *a, const struct hw_addr_data *b) return memcmp(a->bytes, b->bytes, a->length); } +static void hw_addr_hash_func(const struct hw_addr_data *p, struct siphash *state) { + assert(p); + assert(state); + + siphash24_compress(&p->length, sizeof(p->length), state); + siphash24_compress(p->bytes, p->length, state); +} + +DEFINE_HASH_OPS(hw_addr_hash_ops, struct hw_addr_data, hw_addr_hash_func, hw_addr_compare); + char* ether_addr_to_string(const struct ether_addr *addr, char buffer[ETHER_ADDR_TO_STRING_MAX]) { assert(addr); assert(buffer); diff --git a/src/libnm-systemd-shared/src/basic/ether-addr-util.h b/src/libnm-systemd-shared/src/basic/ether-addr-util.h index cf8afff356..794fc55bb8 100644 --- a/src/libnm-systemd-shared/src/basic/ether-addr-util.h +++ b/src/libnm-systemd-shared/src/basic/ether-addr-util.h @@ -23,7 +23,9 @@ struct hw_addr_data { #define HW_ADDR_TO_STRING_MAX (3*HW_ADDR_MAX_SIZE) char* hw_addr_to_string(const struct hw_addr_data *addr, char buffer[HW_ADDR_TO_STRING_MAX]); -/* Use only as function argument, never stand-alone! */ +/* Note: the lifetime of the compound literal is the immediately surrounding block, + * see C11 §6.5.2.5, and + * https://stackoverflow.com/questions/34880638/compound-literal-lifetime-and-if-blocks */ #define HW_ADDR_TO_STR(hw_addr) hw_addr_to_string((hw_addr), (char[HW_ADDR_TO_STRING_MAX]){}) #define HW_ADDR_NULL ((const struct hw_addr_data){}) @@ -36,6 +38,8 @@ static inline bool hw_addr_is_null(const struct hw_addr_data *addr) { return hw_addr_equal(addr, &HW_ADDR_NULL); } +extern const struct hash_ops hw_addr_hash_ops; + #define ETHER_ADDR_FORMAT_STR "%02X%02X%02X%02X%02X%02X" #define ETHER_ADDR_FORMAT_VAL(x) (x).ether_addr_octet[0], (x).ether_addr_octet[1], (x).ether_addr_octet[2], (x).ether_addr_octet[3], (x).ether_addr_octet[4], (x).ether_addr_octet[5] diff --git a/src/libnm-systemd-shared/src/basic/extract-word.c b/src/libnm-systemd-shared/src/basic/extract-word.c index 2c14b6f0cf..9f9bb0c791 100644 --- a/src/libnm-systemd-shared/src/basic/extract-word.c +++ b/src/libnm-systemd-shared/src/basic/extract-word.c @@ -27,6 +27,7 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra assert(p); assert(ret); + assert(!FLAGS_SET(flags, EXTRACT_KEEP_QUOTE | EXTRACT_UNQUOTE)); /* Bail early if called after last value or with no input */ if (!*p) @@ -50,7 +51,8 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra goto finish_force_terminate; else if (strchr(separators, c)) { if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) { - (*p)++; + if (!(flags & EXTRACT_RETAIN_SEPARATORS)) + (*p)++; goto finish_force_next; } } else { @@ -123,48 +125,58 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra return -EINVAL; } else if (c == quote) { /* found the end quote */ quote = 0; - break; + if (flags & EXTRACT_UNQUOTE) + break; } else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) { backslash = true; break; - } else { - if (!GREEDY_REALLOC(s, sz+2)) - return -ENOMEM; - - s[sz++] = c; } + + if (!GREEDY_REALLOC(s, sz+2)) + return -ENOMEM; + + s[sz++] = c; + + if (quote == 0) + break; } } else { for (;; (*p)++, c = **p) { if (c == 0) goto finish_force_terminate; - else if (IN_SET(c, '\'', '"') && (flags & EXTRACT_UNQUOTE)) { + else if (IN_SET(c, '\'', '"') && (flags & (EXTRACT_KEEP_QUOTE | EXTRACT_UNQUOTE))) { quote = c; - break; + if (flags & EXTRACT_UNQUOTE) + break; } else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) { backslash = true; break; } else if (strchr(separators, c)) { if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) { - (*p)++; + if (!(flags & EXTRACT_RETAIN_SEPARATORS)) + (*p)++; goto finish_force_next; } - /* Skip additional coalesced separators. */ - for (;; (*p)++, c = **p) { - if (c == 0) - goto finish_force_terminate; - if (!strchr(separators, c)) - break; - } + if (!(flags & EXTRACT_RETAIN_SEPARATORS)) + /* Skip additional coalesced separators. */ + for (;; (*p)++, c = **p) { + if (c == 0) + goto finish_force_terminate; + if (!strchr(separators, c)) + break; + } goto finish; - } else { - if (!GREEDY_REALLOC(s, sz+2)) - return -ENOMEM; - - s[sz++] = c; } + + if (!GREEDY_REALLOC(s, sz+2)) + return -ENOMEM; + + s[sz++] = c; + + if (quote != 0) + break; } } } diff --git a/src/libnm-systemd-shared/src/basic/extract-word.h b/src/libnm-systemd-shared/src/basic/extract-word.h index 0e9e77e93d..c82ad761ef 100644 --- a/src/libnm-systemd-shared/src/basic/extract-word.h +++ b/src/libnm-systemd-shared/src/basic/extract-word.h @@ -8,9 +8,11 @@ typedef enum ExtractFlags { EXTRACT_CUNESCAPE = 1 << 1, /* Unescape known escape sequences. */ EXTRACT_UNESCAPE_RELAX = 1 << 2, /* Allow and keep unknown escape sequences, allow and keep trailing backslash. */ EXTRACT_UNESCAPE_SEPARATORS = 1 << 3, /* Unescape separators (those specified, or whitespace by default). */ - EXTRACT_UNQUOTE = 1 << 4, /* Remove quoting with "" and ''. */ - EXTRACT_DONT_COALESCE_SEPARATORS = 1 << 5, /* Don't treat multiple adjacent separators as one */ - EXTRACT_RETAIN_ESCAPE = 1 << 6, /* Treat escape character '\' as any other character without special meaning */ + EXTRACT_KEEP_QUOTE = 1 << 4, /* Ignore separators in quoting with "" and ''. */ + EXTRACT_UNQUOTE = 1 << 5, /* Ignore separators in quoting with "" and '', and remove the quotes. */ + EXTRACT_DONT_COALESCE_SEPARATORS = 1 << 6, /* Don't treat multiple adjacent separators as one */ + EXTRACT_RETAIN_ESCAPE = 1 << 7, /* Treat escape character '\' as any other character without special meaning */ + EXTRACT_RETAIN_SEPARATORS = 1 << 8, /* Do not advance the original string pointer past the separator(s) */ /* Note that if no flags are specified, escaped escape characters will be silently stripped. */ } ExtractFlags; diff --git a/src/libnm-systemd-shared/src/basic/fd-util.c b/src/libnm-systemd-shared/src/basic/fd-util.c index 1a873601b2..6b6457dbc2 100644 --- a/src/libnm-systemd-shared/src/basic/fd-util.c +++ b/src/libnm-systemd-shared/src/basic/fd-util.c @@ -2,19 +2,20 @@ #include #include +#include +#include +#include #include #include #include #include "alloc-util.h" -#include "copy.h" #include "dirent-util.h" #include "fd-util.h" #include "fileio.h" #include "fs-util.h" #include "io-util.h" #include "macro.h" -#include "memfd-util.h" #include "missing_fcntl.h" #include "missing_syscall.h" #include "parse-util.h" @@ -207,10 +208,9 @@ static int get_max_fd(void) { return (int) (m - 1); } -int close_all_fds(const int except[], size_t n_except) { +int close_all_fds_full(int except[], size_t n_except, bool allow_alloc) { static bool have_close_range = true; /* Assume we live in the future */ _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; int r = 0; assert(n_except == 0 || except); @@ -226,129 +226,104 @@ int close_all_fds(const int except[], size_t n_except) { /* Close everything. Yay! */ if (close_range(3, -1, 0) >= 0) - return 1; + return 0; - if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno)) + if (ERRNO_IS_NOT_SUPPORTED(errno) || ERRNO_IS_PRIVILEGE(errno)) + have_close_range = false; + else return -errno; - have_close_range = false; } else { - _cleanup_free_ int *sorted_malloc = NULL; - size_t n_sorted; - int *sorted; - - assert(n_except < SIZE_MAX); - n_sorted = n_except + 1; - - if (n_sorted > 64) /* Use heap for large numbers of fds, stack otherwise */ - sorted = sorted_malloc = new(int, n_sorted); - else - sorted = newa(int, n_sorted); - - if (sorted) { - int c = 0; - - memcpy(sorted, except, n_except * sizeof(int)); + typesafe_qsort(except, n_except, cmp_int); - /* Let's add fd 2 to the list of fds, to simplify the loop below, as this - * allows us to cover the head of the array the same way as the body */ - sorted[n_sorted-1] = 2; + for (size_t i = 0; i < n_except; i++) { + int start = i == 0 ? 2 : MAX(except[i-1], 2); /* The first three fds shall always remain open */ + int end = MAX(except[i], 2); - typesafe_qsort(sorted, n_sorted, cmp_int); + assert(end >= start); - for (size_t i = 0; i < n_sorted-1; i++) { - int start, end; - - start = MAX(sorted[i], 2); /* The first three fds shall always remain open */ - end = MAX(sorted[i+1], 2); - - assert(end >= start); - - if (end - start <= 1) - continue; - - /* Close everything between the start and end fds (both of which shall stay open) */ - if (close_range(start + 1, end - 1, 0) < 0) { - if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno)) - return -errno; + if (end - start <= 1) + continue; + /* Close everything between the start and end fds (both of which shall stay open) */ + if (close_range(start + 1, end - 1, 0) < 0) { + if (ERRNO_IS_NOT_SUPPORTED(errno) || ERRNO_IS_PRIVILEGE(errno)) have_close_range = false; - break; - } - - c += end - start - 1; + else + return -errno; + goto opendir_fallback; } + } - if (have_close_range) { - /* The loop succeeded. Let's now close everything beyond the end */ + /* The loop succeeded. Let's now close everything beyond the end */ - if (sorted[n_sorted-1] >= INT_MAX) /* Dont let the addition below overflow */ - return c; + if (except[n_except-1] >= INT_MAX) /* Don't let the addition below overflow */ + return 0; - if (close_range(sorted[n_sorted-1] + 1, -1, 0) >= 0) - return c + 1; + int start = MAX(except[n_except-1], 2); - if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno)) - return -errno; + if (close_range(start + 1, -1, 0) >= 0) + return 0; - have_close_range = false; - } - } + if (ERRNO_IS_NOT_SUPPORTED(errno) || ERRNO_IS_PRIVILEGE(errno)) + have_close_range = false; + else + return -errno; } - - /* Fallback on OOM or if close_range() is not supported */ } - d = opendir("/proc/self/fd"); - if (!d) { - int fd, max_fd; + /* Fallback for when close_range() is not supported */ + opendir_fallback: + d = allow_alloc ? opendir("/proc/self/fd") : NULL; + if (d) { + struct dirent *de; - /* When /proc isn't available (for example in chroots) the fallback is brute forcing through - * the fd table */ + FOREACH_DIRENT(de, d, return -errno) { + int fd = -1, q; - max_fd = get_max_fd(); - if (max_fd < 0) - return max_fd; + if (safe_atoi(de->d_name, &fd) < 0) + /* Let's better ignore this, just in case */ + continue; - /* Refuse to do the loop over more too many elements. It's better to fail immediately than to - * spin the CPU for a long time. */ - if (max_fd > MAX_FD_LOOP_LIMIT) - return log_debug_errno(SYNTHETIC_ERRNO(EPERM), - "/proc/self/fd is inaccessible. Refusing to loop over %d potential fds.", - max_fd); + if (fd < 3) + continue; - for (fd = 3; fd >= 0; fd = fd < max_fd ? fd + 1 : -1) { - int q; + if (fd == dirfd(d)) + continue; if (fd_in_set(fd, except, n_except)) continue; q = close_nointr(fd); - if (q < 0 && q != -EBADF && r >= 0) + if (q < 0 && q != -EBADF && r >= 0) /* Valgrind has its own FD and doesn't want to have it closed */ r = q; } return r; } - FOREACH_DIRENT(de, d, return -errno) { - int fd = -1, q; + /* Fallback for when /proc isn't available (for example in chroots) or when we cannot allocate by + * brute-forcing through the file descriptor table. */ - if (safe_atoi(de->d_name, &fd) < 0) - /* Let's better ignore this, just in case */ - continue; + int max_fd = get_max_fd(); + if (max_fd < 0) + return max_fd; - if (fd < 3) - continue; + /* Refuse to do the loop over more too many elements. It's better to fail immediately than to + * spin the CPU for a long time. */ + if (max_fd > MAX_FD_LOOP_LIMIT) + return log_debug_errno(SYNTHETIC_ERRNO(EPERM), + "/proc/self/fd is inaccessible. Refusing to loop over %d potential fds.", + max_fd); - if (fd == dirfd(d)) - continue; + for (int fd = 3; fd >= 0; fd = fd < max_fd ? fd + 1 : -1) { + int q; if (fd_in_set(fd, except, n_except)) continue; q = close_nointr(fd); - if (q < 0 && q != -EBADF && r >= 0) /* Valgrind has its own FD and doesn't want to have it closed */ + if (q < 0 && q != -EBADF && r >= 0) r = q; } @@ -520,343 +495,6 @@ int move_fd(int from, int to, int cloexec) { return to; } -int acquire_data_fd(const void *data, size_t size, unsigned flags) { - - _cleanup_close_pair_ int pipefds[2] = { -1, -1 }; - char pattern[] = "/dev/shm/data-fd-XXXXXX"; - _cleanup_close_ int fd = -1; - int isz = 0, r; - ssize_t n; - off_t f; - - assert(data || size == 0); - - /* Acquire a read-only file descriptor that when read from returns the specified data. This is much more - * complex than I wish it was. But here's why: - * - * a) First we try to use memfds. They are the best option, as we can seal them nicely to make them - * read-only. Unfortunately they require kernel 3.17, and – at the time of writing – we still support 3.14. - * - * b) Then, we try classic pipes. They are the second best options, as we can close the writing side, retaining - * a nicely read-only fd in the reading side. However, they are by default quite small, and unprivileged - * clients can only bump their size to a system-wide limit, which might be quite low. - * - * c) Then, we try an O_TMPFILE file in /dev/shm (that dir is the only suitable one known to exist from - * earliest boot on). To make it read-only we open the fd a second time with O_RDONLY via - * /proc/self/. Unfortunately O_TMPFILE is not available on older kernels on tmpfs. - * - * d) Finally, we try creating a regular file in /dev/shm, which we then delete. - * - * It sucks a bit that depending on the situation we return very different objects here, but that's Linux I - * figure. */ - - if (size == 0 && ((flags & ACQUIRE_NO_DEV_NULL) == 0)) { - /* As a special case, return /dev/null if we have been called for an empty data block */ - r = open("/dev/null", O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (r < 0) - return -errno; - - return r; - } - - if ((flags & ACQUIRE_NO_MEMFD) == 0) { - fd = memfd_new("data-fd"); - if (fd < 0) - goto try_pipe; - - n = write(fd, data, size); - if (n < 0) - return -errno; - if ((size_t) n != size) - return -EIO; - - f = lseek(fd, 0, SEEK_SET); - if (f != 0) - return -errno; - - r = memfd_set_sealed(fd); - if (r < 0) - return r; - - return TAKE_FD(fd); - } - -try_pipe: - if ((flags & ACQUIRE_NO_PIPE) == 0) { - if (pipe2(pipefds, O_CLOEXEC|O_NONBLOCK) < 0) - return -errno; - - isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0); - if (isz < 0) - return -errno; - - if ((size_t) isz < size) { - isz = (int) size; - if (isz < 0 || (size_t) isz != size) - return -E2BIG; - - /* Try to bump the pipe size */ - (void) fcntl(pipefds[1], F_SETPIPE_SZ, isz); - - /* See if that worked */ - isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0); - if (isz < 0) - return -errno; - - if ((size_t) isz < size) - goto try_dev_shm; - } - - n = write(pipefds[1], data, size); - if (n < 0) - return -errno; - if ((size_t) n != size) - return -EIO; - - (void) fd_nonblock(pipefds[0], false); - - return TAKE_FD(pipefds[0]); - } - -try_dev_shm: - if ((flags & ACQUIRE_NO_TMPFILE) == 0) { - fd = open("/dev/shm", O_RDWR|O_TMPFILE|O_CLOEXEC, 0500); - if (fd < 0) - goto try_dev_shm_without_o_tmpfile; - - n = write(fd, data, size); - if (n < 0) - return -errno; - if ((size_t) n != size) - return -EIO; - - /* Let's reopen the thing, in order to get an O_RDONLY fd for the original O_RDWR one */ - return fd_reopen(fd, O_RDONLY|O_CLOEXEC); - } - -try_dev_shm_without_o_tmpfile: - if ((flags & ACQUIRE_NO_REGULAR) == 0) { - fd = mkostemp_safe(pattern); - if (fd < 0) - return fd; - - n = write(fd, data, size); - if (n < 0) { - r = -errno; - goto unlink_and_return; - } - if ((size_t) n != size) { - r = -EIO; - goto unlink_and_return; - } - - /* Let's reopen the thing, in order to get an O_RDONLY fd for the original O_RDWR one */ - r = open(pattern, O_RDONLY|O_CLOEXEC); - if (r < 0) - r = -errno; - - unlink_and_return: - (void) unlink(pattern); - return r; - } - - return -EOPNOTSUPP; -} - -/* When the data is smaller or equal to 64K, try to place the copy in a memfd/pipe */ -#define DATA_FD_MEMORY_LIMIT (64U*1024U) - -/* If memfd/pipe didn't work out, then let's use a file in /tmp up to a size of 1M. If it's large than that use /var/tmp instead. */ -#define DATA_FD_TMP_LIMIT (1024U*1024U) - -int fd_duplicate_data_fd(int fd) { - - _cleanup_close_ int copy_fd = -1, tmp_fd = -1; - _cleanup_free_ void *remains = NULL; - size_t remains_size = 0; - const char *td; - struct stat st; - int r; - - /* Creates a 'data' fd from the specified source fd, containing all the same data in a read-only fashion, but - * independent of it (i.e. the source fd can be closed and unmounted after this call succeeded). Tries to be - * somewhat smart about where to place the data. In the best case uses a memfd(). If memfd() are not supported - * uses a pipe instead. For larger data will use an unlinked file in /tmp, and for even larger data one in - * /var/tmp. */ - - if (fstat(fd, &st) < 0) - return -errno; - - /* For now, let's only accept regular files, sockets, pipes and char devices */ - if (S_ISDIR(st.st_mode)) - return -EISDIR; - if (S_ISLNK(st.st_mode)) - return -ELOOP; - if (!S_ISREG(st.st_mode) && !S_ISSOCK(st.st_mode) && !S_ISFIFO(st.st_mode) && !S_ISCHR(st.st_mode)) - return -EBADFD; - - /* If we have reason to believe the data is bounded in size, then let's use memfds or pipes as backing fd. Note - * that we use the reported regular file size only as a hint, given that there are plenty special files in - * /proc and /sys which report a zero file size but can be read from. */ - - if (!S_ISREG(st.st_mode) || st.st_size < DATA_FD_MEMORY_LIMIT) { - - /* Try a memfd first */ - copy_fd = memfd_new("data-fd"); - if (copy_fd >= 0) { - off_t f; - - r = copy_bytes(fd, copy_fd, DATA_FD_MEMORY_LIMIT, 0); - if (r < 0) - return r; - - f = lseek(copy_fd, 0, SEEK_SET); - if (f != 0) - return -errno; - - if (r == 0) { - /* Did it fit into the limit? If so, we are done. */ - r = memfd_set_sealed(copy_fd); - if (r < 0) - return r; - - return TAKE_FD(copy_fd); - } - - /* Hmm, pity, this didn't fit. Let's fall back to /tmp then, see below */ - - } else { - _cleanup_(close_pairp) int pipefds[2] = { -1, -1 }; - int isz; - - /* If memfds aren't available, use a pipe. Set O_NONBLOCK so that we will get EAGAIN rather - * then block indefinitely when we hit the pipe size limit */ - - if (pipe2(pipefds, O_CLOEXEC|O_NONBLOCK) < 0) - return -errno; - - isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0); - if (isz < 0) - return -errno; - - /* Try to enlarge the pipe size if necessary */ - if ((size_t) isz < DATA_FD_MEMORY_LIMIT) { - - (void) fcntl(pipefds[1], F_SETPIPE_SZ, DATA_FD_MEMORY_LIMIT); - - isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0); - if (isz < 0) - return -errno; - } - - if ((size_t) isz >= DATA_FD_MEMORY_LIMIT) { - - r = copy_bytes_full(fd, pipefds[1], DATA_FD_MEMORY_LIMIT, 0, &remains, &remains_size, NULL, NULL); - if (r < 0 && r != -EAGAIN) - return r; /* If we get EAGAIN it could be because of the source or because of - * the destination fd, we can't know, as sendfile() and friends won't - * tell us. Hence, treat this as reason to fall back, just to be - * sure. */ - if (r == 0) { - /* Everything fit in, yay! */ - (void) fd_nonblock(pipefds[0], false); - - return TAKE_FD(pipefds[0]); - } - - /* Things didn't fit in. But we read data into the pipe, let's remember that, so that - * when writing the new file we incorporate this first. */ - copy_fd = TAKE_FD(pipefds[0]); - } - } - } - - /* If we have reason to believe this will fit fine in /tmp, then use that as first fallback. */ - if ((!S_ISREG(st.st_mode) || st.st_size < DATA_FD_TMP_LIMIT) && - (DATA_FD_MEMORY_LIMIT + remains_size) < DATA_FD_TMP_LIMIT) { - off_t f; - - tmp_fd = open_tmpfile_unlinkable(NULL /* NULL as directory means /tmp */, O_RDWR|O_CLOEXEC); - if (tmp_fd < 0) - return tmp_fd; - - if (copy_fd >= 0) { - /* If we tried a memfd/pipe first and it ended up being too large, then copy this into the - * temporary file first. */ - - r = copy_bytes(copy_fd, tmp_fd, UINT64_MAX, 0); - if (r < 0) - return r; - - assert(r == 0); - } - - if (remains_size > 0) { - /* If there were remaining bytes (i.e. read into memory, but not written out yet) from the - * failed copy operation, let's flush them out next. */ - - r = loop_write(tmp_fd, remains, remains_size, false); - if (r < 0) - return r; - } - - r = copy_bytes(fd, tmp_fd, DATA_FD_TMP_LIMIT - DATA_FD_MEMORY_LIMIT - remains_size, COPY_REFLINK); - if (r < 0) - return r; - if (r == 0) - goto finish; /* Yay, it fit in */ - - /* It didn't fit in. Let's not forget to use what we already used */ - f = lseek(tmp_fd, 0, SEEK_SET); - if (f != 0) - return -errno; - - CLOSE_AND_REPLACE(copy_fd, tmp_fd); - - remains = mfree(remains); - remains_size = 0; - } - - /* As last fallback use /var/tmp */ - r = var_tmp_dir(&td); - if (r < 0) - return r; - - tmp_fd = open_tmpfile_unlinkable(td, O_RDWR|O_CLOEXEC); - if (tmp_fd < 0) - return tmp_fd; - - if (copy_fd >= 0) { - /* If we tried a memfd/pipe first, or a file in /tmp, and it ended up being too large, than copy this - * into the temporary file first. */ - r = copy_bytes(copy_fd, tmp_fd, UINT64_MAX, COPY_REFLINK); - if (r < 0) - return r; - - assert(r == 0); - } - - if (remains_size > 0) { - /* Then, copy in any read but not yet written bytes. */ - r = loop_write(tmp_fd, remains, remains_size, false); - if (r < 0) - return r; - } - - /* Copy in the rest */ - r = copy_bytes(fd, tmp_fd, UINT64_MAX, COPY_REFLINK); - if (r < 0) - return r; - - assert(r == 0); - -finish: - /* Now convert the O_RDWR file descriptor into an O_RDONLY one (and as side effect seek to the beginning of the - * file again */ - - return fd_reopen(tmp_fd, O_RDONLY|O_CLOEXEC); -} - int fd_move_above_stdio(int fd) { int flags, copy; PROTECT_ERRNO; @@ -1057,3 +695,20 @@ int read_nr_open(void) { /* If we fail, fall back to the hard-coded kernel limit of 1024 * 1024. */ return 1024 * 1024; } + +/* This is here because it's fd-related and is called from sd-journal code. Other btrfs-related utilities are + * in src/shared, but libsystemd must not link to libsystemd-shared, see docs/ARCHITECTURE.md. */ +int btrfs_defrag_fd(int fd) { + int r; + + assert(fd >= 0); + + r = fd_verify_regular(fd); + if (r < 0) + return r; + + if (ioctl(fd, BTRFS_IOC_DEFRAG, NULL) < 0) + return -errno; + + return 0; +} diff --git a/src/libnm-systemd-shared/src/basic/fd-util.h b/src/libnm-systemd-shared/src/basic/fd-util.h index aa8e082b38..61b6684cb3 100644 --- a/src/libnm-systemd-shared/src/basic/fd-util.h +++ b/src/libnm-systemd-shared/src/basic/fd-util.h @@ -56,7 +56,10 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(DIR*, closedir, NULL); int fd_nonblock(int fd, bool nonblock); int fd_cloexec(int fd, bool cloexec); -int close_all_fds(const int except[], size_t n_except); +int close_all_fds_full(int except[], size_t n_except, bool allow_alloc); +static inline int close_all_fds(int except[], size_t n_except) { + return close_all_fds_full(except, n_except, true); +} int same_fd(int a, int b); @@ -76,10 +79,6 @@ enum { ACQUIRE_NO_REGULAR = 1 << 4, }; -int acquire_data_fd(const void *data, size_t size, unsigned flags); - -int fd_duplicate_data_fd(int fd); - int fd_move_above_stdio(int fd); int rearrange_stdio(int original_input_fd, int original_output_fd, int original_error_fd); @@ -107,5 +106,5 @@ static inline int make_null_stdio(void) { int fd_reopen(int fd, int flags); - int read_nr_open(void); +int btrfs_defrag_fd(int fd); diff --git a/src/libnm-systemd-shared/src/basic/fileio.c b/src/libnm-systemd-shared/src/basic/fileio.c index 99a44fdea2..19fe6e214c 100644 --- a/src/libnm-systemd-shared/src/basic/fileio.c +++ b/src/libnm-systemd-shared/src/basic/fileio.c @@ -522,18 +522,17 @@ int read_full_stream_full( size_t *ret_size) { _cleanup_free_ char *buf = NULL; - size_t n, n_next, l; + size_t n, n_next = 0, l; int fd, r; assert(f); assert(ret_contents); assert(!FLAGS_SET(flags, READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_UNHEX)); + assert(size != SIZE_MAX || !FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER)); - if (offset != UINT64_MAX && offset > LONG_MAX) + if (offset != UINT64_MAX && offset > LONG_MAX) /* fseek() can only deal with "long" offsets */ return -ERANGE; - n_next = size != SIZE_MAX ? size : LINE_MAX; /* Start size */ - fd = fileno(f); if (fd >= 0) { /* If the FILE* object is backed by an fd (as opposed to memory or such, see * fmemopen()), let's optimize our buffering */ @@ -543,20 +542,20 @@ int read_full_stream_full( return -errno; if (S_ISREG(st.st_mode)) { - if (size == SIZE_MAX) { + + /* Try to start with the right file size if we shall read the file in full. Note + * that we increase the size to read here by one, so that the first read attempt + * already makes us notice the EOF. If the reported size of the file is zero, we + * avoid this logic however, since quite likely it might be a virtual file in procfs + * that all report a zero file size. */ + + if (st.st_size > 0 && + (size == SIZE_MAX || FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER))) { + uint64_t rsize = LESS_BY((uint64_t) st.st_size, offset == UINT64_MAX ? 0 : offset); - /* Safety check */ - if (rsize > READ_FULL_BYTES_MAX) - return -E2BIG; - - /* Start with the right file size. Note that we increase the size to read - * here by one, so that the first read attempt already makes us notice the - * EOF. If the reported size of the file is zero, we avoid this logic - * however, since quite likely it might be a virtual file in procfs that all - * report a zero file size. */ - if (st.st_size > 0) + if (rsize < SIZE_MAX) /* overflow check */ n_next = rsize + 1; } @@ -565,6 +564,17 @@ int read_full_stream_full( } } + /* If we don't know how much to read, figure it out now. If we shall read a part of the file, then + * allocate the requested size. If we shall load the full file start with LINE_MAX. Note that if + * READ_FULL_FILE_FAIL_WHEN_LARGER we consider the specified size a safety limit, and thus also start + * with LINE_MAX, under assumption the file is most likely much shorter. */ + if (n_next == 0) + n_next = size != SIZE_MAX && !FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER) ? size : LINE_MAX; + + /* Never read more than we need to determine that our own limit is hit */ + if (n_next > READ_FULL_BYTES_MAX) + n_next = READ_FULL_BYTES_MAX + 1; + if (offset != UINT64_MAX && fseek(f, offset, SEEK_SET) < 0) return -errno; @@ -573,6 +583,11 @@ int read_full_stream_full( char *t; size_t k; + /* If we shall fail when reading overly large data, then read exactly one byte more than the + * specified size at max, since that'll tell us if there's anymore data beyond the limit*/ + if (FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER) && n_next > size) + n_next = size + 1; + if (flags & READ_FULL_FILE_SECURE) { t = malloc(n_next + 1); if (!t) { @@ -606,14 +621,18 @@ int read_full_stream_full( if (feof(f)) break; - if (size != SIZE_MAX) { /* If we got asked to read some specific size, we already sized the buffer right, hence leave */ + if (size != SIZE_MAX && !FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER)) { /* If we got asked to read some specific size, we already sized the buffer right, hence leave */ assert(l == size); break; } assert(k > 0); /* we can't have read zero bytes because that would have been EOF */ - /* Safety check */ + if (FLAGS_SET(flags, READ_FULL_FILE_FAIL_WHEN_LARGER) && l > size) { + r = -E2BIG; + goto finalize; + } + if (n >= READ_FULL_BYTES_MAX) { r = -E2BIG; goto finalize; diff --git a/src/libnm-systemd-shared/src/basic/fileio.h b/src/libnm-systemd-shared/src/basic/fileio.h index c28b17fef5..af797cfafd 100644 --- a/src/libnm-systemd-shared/src/basic/fileio.h +++ b/src/libnm-systemd-shared/src/basic/fileio.h @@ -39,6 +39,7 @@ typedef enum { READ_FULL_FILE_UNHEX = 1 << 2, /* hex decode what we read */ READ_FULL_FILE_WARN_WORLD_READABLE = 1 << 3, /* if regular file, log at LOG_WARNING level if access mode above 0700 */ READ_FULL_FILE_CONNECT_SOCKET = 1 << 4, /* if socket inode, connect to it and read off it */ + READ_FULL_FILE_FAIL_WHEN_LARGER = 1 << 5, /* fail loading if file is larger than specified size */ } ReadFullFileFlags; int fopen_unlocked(const char *path, const char *options, FILE **ret); diff --git a/src/libnm-systemd-shared/src/basic/format-util.h b/src/libnm-systemd-shared/src/basic/format-util.h index b7e18768e3..579f231897 100644 --- a/src/libnm-systemd-shared/src/basic/format-util.h +++ b/src/libnm-systemd-shared/src/basic/format-util.h @@ -74,16 +74,17 @@ typedef enum { #define FORMAT_BYTES_MAX 16U -char *format_bytes_full(char *buf, size_t l, uint64_t t, FormatBytesFlag flag); +char *format_bytes_full(char *buf, size_t l, uint64_t t, FormatBytesFlag flag) _warn_unused_result_; +_warn_unused_result_ static inline char *format_bytes(char *buf, size_t l, uint64_t t) { return format_bytes_full(buf, l, t, FORMAT_BYTES_USE_IEC | FORMAT_BYTES_BELOW_POINT | FORMAT_BYTES_TRAILING_B); } -static inline char *format_bytes_cgroup_protection(char *buf, size_t l, uint64_t t) { - if (t == CGROUP_LIMIT_MAX) { - (void) snprintf(buf, l, "%s", "infinity"); - return buf; - } - return format_bytes(buf, l, t); -} +/* Note: the lifetime of the compound literal is the immediately surrounding block, + * see C11 §6.5.2.5, and + * https://stackoverflow.com/questions/34880638/compound-literal-lifetime-and-if-blocks */ +#define FORMAT_BYTES(t) format_bytes((char[FORMAT_BYTES_MAX]){}, FORMAT_BYTES_MAX, t) +#define FORMAT_BYTES_FULL(t, flag) format_bytes_full((char[FORMAT_BYTES_MAX]){}, FORMAT_BYTES_MAX, t, flag) + +#define FORMAT_BYTES_CGROUP_PROTECTION(t) (t == CGROUP_LIMIT_MAX ? "infinity" : FORMAT_BYTES(t)) diff --git a/src/libnm-systemd-shared/src/basic/fs-util.c b/src/libnm-systemd-shared/src/basic/fs-util.c index 5fe8fbab98..28c7247e05 100644 --- a/src/libnm-systemd-shared/src/basic/fs-util.c +++ b/src/libnm-systemd-shared/src/basic/fs-util.c @@ -8,7 +8,6 @@ #include #include "alloc-util.h" -#include "blockdev-util.h" #include "dirent-util.h" #include "fd-util.h" #include "fileio.h" @@ -581,8 +580,6 @@ int get_files_in_directory(const char *path, char ***list) { return -errno; FOREACH_DIRENT_ALL(de, d, return -errno) { - dirent_ensure_type(d, de); - if (!dirent_is_file(de)) continue; @@ -745,7 +742,8 @@ bool unsafe_transition(const struct stat *a, const struct stat *b) { } static int log_unsafe_transition(int a, int b, const char *path, unsigned flags) { - _cleanup_free_ char *n1 = NULL, *n2 = NULL; + _cleanup_free_ char *n1 = NULL, *n2 = NULL, *user_a = NULL, *user_b = NULL; + struct stat st; if (!FLAGS_SET(flags, CHASE_WARN)) return -ENOLINK; @@ -753,9 +751,14 @@ static int log_unsafe_transition(int a, int b, const char *path, unsigned flags) (void) fd_get_path(a, &n1); (void) fd_get_path(b, &n2); + if (fstat(a, &st) == 0) + user_a = uid_to_name(st.st_uid); + if (fstat(b, &st) == 0) + user_b = uid_to_name(st.st_uid); + return log_warning_errno(SYNTHETIC_ERRNO(ENOLINK), - "Detected unsafe path transition %s %s %s during canonicalization of %s.", - strna(n1), special_glyph(SPECIAL_GLYPH_ARROW), strna(n2), path); + "Detected unsafe path transition %s (owned by %s) %s %s (owned by %s) during canonicalization of %s.", + strna(n1), strna(user_a), special_glyph(SPECIAL_GLYPH_ARROW), strna(n2), strna(user_b), path); } static int log_autofs_mount_point(int fd, const char *path, unsigned flags) { @@ -1375,18 +1378,39 @@ int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags) { } int fsync_directory_of_file(int fd) { - _cleanup_free_ char *path = NULL; _cleanup_close_ int dfd = -1; struct stat st; int r; assert(fd >= 0); - /* We only reasonably can do this for regular files and directories, hence check for that */ + /* We only reasonably can do this for regular files and directories, or for O_PATH fds, hence check + * for the inode type first */ if (fstat(fd, &st) < 0) return -errno; - if (S_ISREG(st.st_mode)) { + if (S_ISDIR(st.st_mode)) { + dfd = openat(fd, "..", O_RDONLY|O_DIRECTORY|O_CLOEXEC, 0); + if (dfd < 0) + return -errno; + + } else if (!S_ISREG(st.st_mode)) { /* Regular files are OK regardless if O_PATH or not, for all other + * types check O_PATH flag */ + int flags; + + flags = fcntl(fd, F_GETFL); + if (flags < 0) + return -errno; + + if (!FLAGS_SET(flags, O_PATH)) /* If O_PATH this refers to the inode in the fs, in which case + * we can sensibly do what is requested. Otherwise this refers + * to a socket, fifo or device node, where the concept of a + * containing directory doesn't make too much sense. */ + return -ENOTTY; + } + + if (dfd < 0) { + _cleanup_free_ char *path = NULL; r = fd_get_path(fd, &path); if (r < 0) { @@ -1409,13 +1433,7 @@ int fsync_directory_of_file(int fd) { dfd = open_parent(path, O_CLOEXEC|O_NOFOLLOW, 0); if (dfd < 0) return dfd; - - } else if (S_ISDIR(st.st_mode)) { - dfd = openat(fd, "..", O_RDONLY|O_DIRECTORY|O_CLOEXEC, 0); - if (dfd < 0) - return -errno; - } else - return -ENOTTY; + } if (fsync(dfd) < 0) return -errno; @@ -1466,12 +1484,56 @@ int fsync_path_at(int at_fd, const char *path) { return 0; } +int fsync_parent_at(int at_fd, const char *path) { + _cleanup_close_ int opened_fd = -1; + + if (isempty(path)) { + if (at_fd != AT_FDCWD) + return fsync_directory_of_file(at_fd); + + opened_fd = open("..", O_RDONLY|O_DIRECTORY|O_CLOEXEC); + if (opened_fd < 0) + return -errno; + + if (fsync(opened_fd) < 0) + return -errno; + + return 0; + } + + opened_fd = openat(at_fd, path, O_PATH|O_CLOEXEC|O_NOFOLLOW); + if (opened_fd < 0) + return -errno; + + return fsync_directory_of_file(opened_fd); +} + +int fsync_path_and_parent_at(int at_fd, const char *path) { + _cleanup_close_ int opened_fd = -1; + + if (isempty(path)) { + if (at_fd != AT_FDCWD) + return fsync_full(at_fd); + + opened_fd = open(".", O_RDONLY|O_DIRECTORY|O_CLOEXEC); + } else + opened_fd = openat(at_fd, path, O_RDONLY|O_NOFOLLOW|O_NONBLOCK|O_CLOEXEC); + if (opened_fd < 0) + return -errno; + + return fsync_full(opened_fd); +} + int syncfs_path(int atfd, const char *path) { _cleanup_close_ int fd = -1; - assert(path); + if (isempty(path)) { + if (atfd != AT_FDCWD) + return syncfs(atfd) < 0 ? -errno : 0; - fd = openat(atfd, path, O_CLOEXEC|O_RDONLY|O_NONBLOCK); + fd = open(".", O_RDONLY|O_DIRECTORY|O_CLOEXEC); + } else + fd = openat(atfd, path, O_RDONLY|O_CLOEXEC|O_NONBLOCK); if (fd < 0) return -errno; @@ -1504,91 +1566,6 @@ int open_parent(const char *path, int flags, mode_t mode) { return fd; } -static int blockdev_is_encrypted(const char *sysfs_path, unsigned depth_left) { - _cleanup_free_ char *p = NULL, *uuids = NULL; - _cleanup_closedir_ DIR *d = NULL; - int r, found_encrypted = false; - - assert(sysfs_path); - - if (depth_left == 0) - return -EINVAL; - - p = path_join(sysfs_path, "dm/uuid"); - if (!p) - return -ENOMEM; - - r = read_one_line_file(p, &uuids); - if (r != -ENOENT) { - if (r < 0) - return r; - - /* The DM device's uuid attribute is prefixed with "CRYPT-" if this is a dm-crypt device. */ - if (startswith(uuids, "CRYPT-")) - return true; - } - - /* Not a dm-crypt device itself. But maybe it is on top of one? Follow the links in the "slaves/" - * subdir. */ - - p = mfree(p); - p = path_join(sysfs_path, "slaves"); - if (!p) - return -ENOMEM; - - d = opendir(p); - if (!d) { - if (errno == ENOENT) /* Doesn't have underlying devices */ - return false; - - return -errno; - } - - for (;;) { - _cleanup_free_ char *q = NULL; - struct dirent *de; - - errno = 0; - de = readdir_no_dot(d); - if (!de) { - if (errno != 0) - return -errno; - - break; /* No more underlying devices */ - } - - q = path_join(p, de->d_name); - if (!q) - return -ENOMEM; - - r = blockdev_is_encrypted(q, depth_left - 1); - if (r < 0) - return r; - if (r == 0) /* we found one that is not encrypted? then propagate that immediately */ - return false; - - found_encrypted = true; - } - - return found_encrypted; -} - -int path_is_encrypted(const char *path) { - char p[SYS_BLOCK_PATH_MAX(NULL)]; - dev_t devt; - int r; - - r = get_block_device(path, &devt); - if (r < 0) - return r; - if (r == 0) /* doesn't have a block device */ - return false; - - xsprintf_sys_block_path(p, NULL, devt); - - return blockdev_is_encrypted(p, 10 /* safety net: maximum recursion depth */); -} - int conservative_renameat( int olddirfd, const char *oldpath, int newdirfd, const char *newpath) { diff --git a/src/libnm-systemd-shared/src/basic/fs-util.h b/src/libnm-systemd-shared/src/basic/fs-util.h index 85bdea64df..d612cbe404 100644 --- a/src/libnm-systemd-shared/src/basic/fs-util.h +++ b/src/libnm-systemd-shared/src/basic/fs-util.h @@ -140,13 +140,13 @@ int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags); int fsync_directory_of_file(int fd); int fsync_full(int fd); int fsync_path_at(int at_fd, const char *path); +int fsync_parent_at(int at_fd, const char *path); +int fsync_path_and_parent_at(int at_fd, const char *path); int syncfs_path(int atfd, const char *path); int open_parent(const char *path, int flags, mode_t mode); -int path_is_encrypted(const char *path); - int conservative_renameat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath); static inline int conservative_rename(const char *oldpath, const char *newpath) { return conservative_renameat(AT_FDCWD, oldpath, AT_FDCWD, newpath); diff --git a/src/libnm-systemd-shared/src/basic/hashmap.c b/src/libnm-systemd-shared/src/basic/hashmap.c index 34fed69d99..0decbb04e1 100644 --- a/src/libnm-systemd-shared/src/basic/hashmap.c +++ b/src/libnm-systemd-shared/src/basic/hashmap.c @@ -112,7 +112,7 @@ assert_cc(IDX_FIRST == _IDX_SWAP_END); assert_cc(IDX_FIRST == _IDX_ITERATOR_FIRST); /* Storage space for the "swap" buckets. - * All entry types can fit into a ordered_hashmap_entry. */ + * All entry types can fit into an ordered_hashmap_entry. */ struct swap_entries { struct ordered_hashmap_entry e[_IDX_SWAP_END - _IDX_SWAP_BEGIN]; }; @@ -1761,6 +1761,9 @@ char** _hashmap_get_strv(HashmapBase *h) { Iterator i; unsigned idx, n; + if (!h) + return new0(char*, 1); + sv = new(char*, n_entries(h)+1); if (!sv) return NULL; diff --git a/src/libnm-systemd-shared/src/basic/hashmap.h b/src/libnm-systemd-shared/src/basic/hashmap.h index f7ade2e774..eafc08f658 100644 --- a/src/libnm-systemd-shared/src/basic/hashmap.h +++ b/src/libnm-systemd-shared/src/basic/hashmap.h @@ -224,7 +224,7 @@ static inline int ordered_hashmap_remove_and_replace(OrderedHashmap *h, const vo return hashmap_remove_and_replace(PLAIN_HASHMAP(h), old_key, new_key, value); } -/* Since merging data from a OrderedHashmap into a Hashmap or vice-versa +/* Since merging data from an OrderedHashmap into a Hashmap or vice-versa * should just work, allow this by having looser type-checking here. */ int _hashmap_merge(Hashmap *h, Hashmap *other); #define hashmap_merge(h, other) _hashmap_merge(PLAIN_HASHMAP(h), PLAIN_HASHMAP(other)) diff --git a/src/libnm-systemd-shared/src/basic/hexdecoct.c b/src/libnm-systemd-shared/src/basic/hexdecoct.c index da1add7c76..8c83a0e71a 100644 --- a/src/libnm-systemd-shared/src/basic/hexdecoct.c +++ b/src/libnm-systemd-shared/src/basic/hexdecoct.c @@ -565,38 +565,79 @@ int unbase64char(char c) { return -EINVAL; } -ssize_t base64mem(const void *p, size_t l, char **out) { - char *r, *z; +static void maybe_line_break(char **x, char *start, size_t line_break) { + size_t n; + + assert(x); + assert(*x); + assert(start); + assert(*x >= start); + + if (line_break == SIZE_MAX) + return; + + n = *x - start; + + if (n % (line_break + 1) == line_break) + *((*x)++) = '\n'; +} + +ssize_t base64mem_full( + const void *p, + size_t l, + size_t line_break, + char **out) { + const uint8_t *x; + char *r, *z; + size_t m; assert(p || l == 0); assert(out); + assert(line_break > 0); /* three input bytes makes four output bytes, padding is added so we must round up */ - z = r = malloc(4 * (l + 2) / 3 + 1); + m = 4 * (l + 2) / 3 + 1; + + if (line_break != SIZE_MAX) + m += m / line_break; + + z = r = malloc(m); if (!r) return -ENOMEM; for (x = p; x < (const uint8_t*) p + (l / 3) * 3; x += 3) { /* x[0] == XXXXXXXX; x[1] == YYYYYYYY; x[2] == ZZZZZZZZ */ + maybe_line_break(&z, r, line_break); *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */ + maybe_line_break(&z, r, line_break); *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */ + maybe_line_break(&z, r, line_break); *(z++) = base64char((x[1] & 15) << 2 | x[2] >> 6); /* 00YYYYZZ */ + maybe_line_break(&z, r, line_break); *(z++) = base64char(x[2] & 63); /* 00ZZZZZZ */ } switch (l % 3) { case 2: + maybe_line_break(&z, r, line_break); *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */ + maybe_line_break(&z, r, line_break); *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */ + maybe_line_break(&z, r, line_break); *(z++) = base64char((x[1] & 15) << 2); /* 00YYYY00 */ + maybe_line_break(&z, r, line_break); *(z++) = '='; break; case 1: + maybe_line_break(&z, r, line_break); *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */ + maybe_line_break(&z, r, line_break); *(z++) = base64char((x[0] & 3) << 4); /* 00XX0000 */ + maybe_line_break(&z, r, line_break); *(z++) = '='; + maybe_line_break(&z, r, line_break); *(z++) = '='; break; @@ -604,6 +645,7 @@ ssize_t base64mem(const void *p, size_t l, char **out) { *z = 0; *out = r; + assert(z >= r); /* Let static analyzers know that the answer is non-negative. */ return z - r; } diff --git a/src/libnm-systemd-shared/src/basic/hexdecoct.h b/src/libnm-systemd-shared/src/basic/hexdecoct.h index 4ace5b7a99..5218f78665 100644 --- a/src/libnm-systemd-shared/src/basic/hexdecoct.h +++ b/src/libnm-systemd-shared/src/basic/hexdecoct.h @@ -33,7 +33,11 @@ int unbase64char(char c) _const_; char *base32hexmem(const void *p, size_t l, bool padding); int unbase32hexmem(const char *p, size_t l, bool padding, void **mem, size_t *len); -ssize_t base64mem(const void *p, size_t l, char **out); +ssize_t base64mem_full(const void *p, size_t l, size_t line_break, char **ret); +static inline ssize_t base64mem(const void *p, size_t l, char **ret) { + return base64mem_full(p, l, SIZE_MAX, ret); +} + int base64_append(char **prefix, int plen, const void *p, size_t l, int margin, int width); diff --git a/src/libnm-systemd-shared/src/basic/macro.h b/src/libnm-systemd-shared/src/basic/macro.h index 072fed4378..a8476184c2 100644 --- a/src/libnm-systemd-shared/src/basic/macro.h +++ b/src/libnm-systemd-shared/src/basic/macro.h @@ -30,6 +30,7 @@ #define _weakref_(x) __attribute__((__weakref__(#x))) #define _alignas_(x) __attribute__((__aligned__(__alignof(x)))) #define _alignptr_ __attribute__((__aligned__(sizeof(void*)))) +#define _warn_unused_result_ __attribute__((__warn_unused_result__)) #if __GNUC__ >= 7 #define _fallthrough_ __attribute__((__fallthrough__)) #else @@ -213,7 +214,7 @@ static inline size_t GREEDY_ALLOC_ROUND_UP(size_t l) { * Contrary to strlen(), this is a constant expression. * @x: a string literal. */ -#define STRLEN(x) (sizeof(""x"") - 1) +#define STRLEN(x) ((unsigned) sizeof(""x"") - 1) /* * container_of - cast a member of a structure out to the containing structure @@ -341,10 +342,10 @@ static inline int __coverity_check_and_return__(int condition) { * negative '-' prefix (hence works correctly on signed * types). Includes space for the trailing NUL. */ #define DECIMAL_STR_MAX(type) \ - (2+(sizeof(type) <= 1 ? 3 : \ - sizeof(type) <= 2 ? 5 : \ - sizeof(type) <= 4 ? 10 : \ - sizeof(type) <= 8 ? 20 : sizeof(int[-2*(sizeof(type) > 8)]))) + (2U+(sizeof(type) <= 1 ? 3U : \ + sizeof(type) <= 2 ? 5U : \ + sizeof(type) <= 4 ? 10U : \ + sizeof(type) <= 8 ? 20U : (unsigned) sizeof(int[-2*(sizeof(type) > 8)]))) #define DECIMAL_STR_WIDTH(x) \ ({ \ diff --git a/src/libnm-systemd-shared/src/basic/parse-util.c b/src/libnm-systemd-shared/src/basic/parse-util.c index b79c885dfd..70062eba2a 100644 --- a/src/libnm-systemd-shared/src/basic/parse-util.c +++ b/src/libnm-systemd-shared/src/basic/parse-util.c @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -731,7 +730,7 @@ int parse_oom_score_adjust(const char *s, int *ret) { if (r < 0) return r; - if (v < OOM_SCORE_ADJ_MIN || v > OOM_SCORE_ADJ_MAX) + if (!oom_score_adjust_is_valid(v)) return -ERANGE; *ret = v; diff --git a/src/libnm-systemd-shared/src/basic/path-util.c b/src/libnm-systemd-shared/src/basic/path-util.c index eb547e7467..4aebb6541e 100644 --- a/src/libnm-systemd-shared/src/basic/path-util.c +++ b/src/libnm-systemd-shared/src/basic/path-util.c @@ -466,8 +466,10 @@ char *path_startswith_full(const char *path, const char *prefix, bool accept_dot int path_compare(const char *a, const char *b) { int r; - assert(a); - assert(b); + /* Order NULL before non-NULL */ + r = CMP(!!a, !!b); + if (r != 0) + return r; /* A relative path and an absolute path must not compare as equal. * Which one is sorted before the other does not really matter. @@ -514,10 +516,6 @@ int path_compare(const char *a, const char *b) { } } -bool path_equal(const char *a, const char *b) { - return path_compare(a, b) == 0; -} - bool path_equal_or_files_same(const char *a, const char *b, int flags) { return path_equal(a, b) || files_same(a, b, flags) > 0; } diff --git a/src/libnm-systemd-shared/src/basic/path-util.h b/src/libnm-systemd-shared/src/basic/path-util.h index c6d4668794..26e7362d1f 100644 --- a/src/libnm-systemd-shared/src/basic/path-util.h +++ b/src/libnm-systemd-shared/src/basic/path-util.h @@ -62,7 +62,11 @@ static inline char* path_startswith(const char *path, const char *prefix) { return path_startswith_full(path, prefix, true); } int path_compare(const char *a, const char *b) _pure_; -bool path_equal(const char *a, const char *b) _pure_; + +static inline bool path_equal(const char *a, const char *b) { + return path_compare(a, b) == 0; +} + bool path_equal_or_files_same(const char *a, const char *b, int flags); /* Compares only the last portion of the input paths, ie: the filenames */ bool path_equal_filename(const char *a, const char *b); diff --git a/src/libnm-systemd-shared/src/basic/process-util.c b/src/libnm-systemd-shared/src/basic/process-util.c index 20b2441468..ecb14306f8 100644 --- a/src/libnm-systemd-shared/src/basic/process-util.c +++ b/src/libnm-systemd-shared/src/basic/process-util.c @@ -643,20 +643,23 @@ int get_process_environ(pid_t pid, char **env) { return 0; } -int get_process_ppid(pid_t pid, pid_t *_ppid) { - int r; +int get_process_ppid(pid_t pid, pid_t *ret) { _cleanup_free_ char *line = NULL; long unsigned ppid; const char *p; + int r; assert(pid >= 0); - assert(_ppid); if (pid == 0 || pid == getpid_cached()) { - *_ppid = getppid(); + if (ret) + *ret = getppid(); return 0; } + if (pid == 1) /* PID 1 has no parent, shortcut this case */ + return -EADDRNOTAVAIL; + p = procfs_file_alloca(pid, "stat"); r = read_one_line_file(p, &line); if (r == -ENOENT) @@ -664,9 +667,8 @@ int get_process_ppid(pid_t pid, pid_t *_ppid) { if (r < 0) return r; - /* Let's skip the pid and comm fields. The latter is enclosed - * in () but does not escape any () in its value, so let's - * skip over it manually */ + /* Let's skip the pid and comm fields. The latter is enclosed in () but does not escape any () in its + * value, so let's skip over it manually */ p = strrchr(line, ')'); if (!p) @@ -680,10 +682,17 @@ int get_process_ppid(pid_t pid, pid_t *_ppid) { &ppid) != 1) return -EIO; - if ((long unsigned) (pid_t) ppid != ppid) + /* If ppid is zero the process has no parent. Which might be the case for PID 1 but also for + * processes originating in other namespaces that are inserted into a pidns. Return a recognizable + * error in this case. */ + if (ppid == 0) + return -EADDRNOTAVAIL; + + if ((pid_t) ppid < 0 || (long unsigned) (pid_t) ppid != ppid) return -ERANGE; - *_ppid = (pid_t) ppid; + if (ret) + *ret = (pid_t) ppid; return 0; } @@ -1028,30 +1037,6 @@ bool is_main_thread(void) { return cached > 0; } -_noreturn_ void freeze(void) { - - log_close(); - - /* Make sure nobody waits for us on a socket anymore */ - (void) close_all_fds(NULL, 0); - - sync(); - - /* Let's not freeze right away, but keep reaping zombies. */ - for (;;) { - int r; - siginfo_t si = {}; - - r = waitid(P_ALL, 0, &si, WEXITED); - if (r < 0 && errno != EINTR) - break; - } - - /* waitid() failed with an unexpected error, things are really borked. Freeze now! */ - for (;;) - pause(); -} - bool oom_score_adjust_is_valid(int oa) { return oa >= OOM_SCORE_ADJ_MIN && oa <= OOM_SCORE_ADJ_MAX; } @@ -1262,7 +1247,7 @@ static void restore_sigsetp(sigset_t **ssp) { int safe_fork_full( const char *name, - const int except_fds[], + int except_fds[], size_t n_except_fds, ForkFlags flags, pid_t *ret_pid) { @@ -1457,7 +1442,7 @@ int safe_fork_full( int namespace_fork( const char *outer_name, const char *inner_name, - const int except_fds[], + int except_fds[], size_t n_except_fds, ForkFlags flags, int pidns_fd, @@ -1473,7 +1458,8 @@ int namespace_fork( * process. This ensures that we are fully a member of the destination namespace, with pidns an all, so that * /proc/self/fd works correctly. */ - r = safe_fork_full(outer_name, except_fds, n_except_fds, (flags|FORK_DEATHSIG) & ~(FORK_REOPEN_LOG|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE), ret_pid); + r = safe_fork_full(outer_name, except_fds, n_except_fds, + (flags|FORK_DEATHSIG) & ~(FORK_REOPEN_LOG|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE), ret_pid); if (r < 0) return r; if (r == 0) { @@ -1508,86 +1494,10 @@ int namespace_fork( return 1; } -int fork_agent(const char *name, const int except[], size_t n_except, pid_t *ret_pid, const char *path, ...) { - bool stdout_is_tty, stderr_is_tty; - size_t n, i; - va_list ap; - char **l; - int r; - - assert(path); - - /* Spawns a temporary TTY agent, making sure it goes away when we go away */ - - r = safe_fork_full(name, - except, - n_except, - FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_CLOSE_ALL_FDS|FORK_REOPEN_LOG, - ret_pid); - if (r < 0) - return r; - if (r > 0) - return 0; - - /* In the child: */ - - stdout_is_tty = isatty(STDOUT_FILENO); - stderr_is_tty = isatty(STDERR_FILENO); - - if (!stdout_is_tty || !stderr_is_tty) { - int fd; - - /* Detach from stdout/stderr. and reopen - * /dev/tty for them. This is important to - * ensure that when systemctl is started via - * popen() or a similar call that expects to - * read EOF we actually do generate EOF and - * not delay this indefinitely by because we - * keep an unused copy of stdin around. */ - fd = open("/dev/tty", O_WRONLY); - if (fd < 0) { - log_error_errno(errno, "Failed to open /dev/tty: %m"); - _exit(EXIT_FAILURE); - } - - if (!stdout_is_tty && dup2(fd, STDOUT_FILENO) < 0) { - log_error_errno(errno, "Failed to dup2 /dev/tty: %m"); - _exit(EXIT_FAILURE); - } - - if (!stderr_is_tty && dup2(fd, STDERR_FILENO) < 0) { - log_error_errno(errno, "Failed to dup2 /dev/tty: %m"); - _exit(EXIT_FAILURE); - } - - safe_close_above_stdio(fd); - } - - (void) rlimit_nofile_safe(); - - /* Count arguments */ - va_start(ap, path); - for (n = 0; va_arg(ap, char*); n++) - ; - va_end(ap); - - /* Allocate strv */ - l = newa(char*, n + 1); - - /* Fill in arguments */ - va_start(ap, path); - for (i = 0; i <= n; i++) - l[i] = va_arg(ap, char*); - va_end(ap); - - execv(path, l); - _exit(EXIT_FAILURE); -} - int set_oom_score_adjust(int value) { char t[DECIMAL_STR_MAX(int)]; - sprintf(t, "%i", value); + xsprintf(t, "%i", value); return write_string_file("/proc/self/oom_score_adj", t, WRITE_STRING_FILE_VERIFY_ON_FAILURE|WRITE_STRING_FILE_DISABLE_BUFFER); diff --git a/src/libnm-systemd-shared/src/basic/process-util.h b/src/libnm-systemd-shared/src/basic/process-util.h index 0e064de85e..c953649502 100644 --- a/src/libnm-systemd-shared/src/basic/process-util.h +++ b/src/libnm-systemd-shared/src/basic/process-util.h @@ -82,8 +82,6 @@ int pid_from_same_root_fs(pid_t pid); bool is_main_thread(void); -_noreturn_ void freeze(void); - bool oom_score_adjust_is_valid(int oa); #ifndef PERSONALITY_INVALID @@ -168,15 +166,13 @@ typedef enum ForkFlags { FORK_NEW_USERNS = 1 << 13, /* Run child in its own user namespace */ } ForkFlags; -int safe_fork_full(const char *name, const int except_fds[], size_t n_except_fds, ForkFlags flags, pid_t *ret_pid); +int safe_fork_full(const char *name, int except_fds[], size_t n_except_fds, ForkFlags flags, pid_t *ret_pid); static inline int safe_fork(const char *name, ForkFlags flags, pid_t *ret_pid) { return safe_fork_full(name, NULL, 0, flags, ret_pid); } -int namespace_fork(const char *outer_name, const char *inner_name, const int except_fds[], size_t n_except_fds, ForkFlags flags, int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd, pid_t *ret_pid); - -int fork_agent(const char *name, const int except[], size_t n_except, pid_t *pid, const char *path, ...) _sentinel_; +int namespace_fork(const char *outer_name, const char *inner_name, int except_fds[], size_t n_except_fds, ForkFlags flags, int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd, pid_t *ret_pid); int set_oom_score_adjust(int value); diff --git a/src/libnm-systemd-shared/src/basic/signal-util.c b/src/libnm-systemd-shared/src/basic/signal-util.c index 131fd3ba00..b06b5ce774 100644 --- a/src/libnm-systemd-shared/src/basic/signal-util.c +++ b/src/libnm-systemd-shared/src/basic/signal-util.c @@ -253,3 +253,38 @@ int signal_is_blocked(int sig) { return r; } + +int pop_pending_signal_internal(int sig, ...) { + sigset_t ss; + va_list ap; + int r; + + if (sig < 0) /* Empty list? */ + return -EINVAL; + + if (sigemptyset(&ss) < 0) + return -errno; + + /* Add first signal (if the signal is zero, we'll silently skip it, to make it easiert to build + * parameter lists where some element are sometimes off, similar to how sigset_add_many_ap() handles + * this.) */ + if (sig > 0 && sigaddset(&ss, sig) < 0) + return -errno; + + /* Add all other signals */ + va_start(ap, sig); + r = sigset_add_many_ap(&ss, ap); + va_end(ap); + if (r < 0) + return r; + + r = sigtimedwait(&ss, NULL, &(struct timespec) { 0, 0 }); + if (r < 0) { + if (errno == EAGAIN) + return 0; + + return -errno; + } + + return r; /* Returns the signal popped */ +} diff --git a/src/libnm-systemd-shared/src/basic/signal-util.h b/src/libnm-systemd-shared/src/basic/signal-util.h index 37271d7a68..36372c19bd 100644 --- a/src/libnm-systemd-shared/src/basic/signal-util.h +++ b/src/libnm-systemd-shared/src/basic/signal-util.h @@ -62,3 +62,6 @@ static inline const char* signal_to_string_with_check(int n) { } int signal_is_blocked(int sig); + +int pop_pending_signal_internal(int sig, ...); +#define pop_pending_signal(...) pop_pending_signal_internal(__VA_ARGS__, -1) diff --git a/src/libnm-systemd-shared/src/basic/socket-util.c b/src/libnm-systemd-shared/src/basic/socket-util.c index f6e751a09f..1c353e86b0 100644 --- a/src/libnm-systemd-shared/src/basic/socket-util.c +++ b/src/libnm-systemd-shared/src/basic/socket-util.c @@ -748,6 +748,22 @@ static const char* const ip_tos_table[] = { DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ip_tos, int, 0xff); +bool ifname_valid_char(char a) { + if ((unsigned char) a >= 127U) + return false; + + if ((unsigned char) a <= 32U) + return false; + + if (IN_SET(a, + ':', /* colons are used by the legacy "alias" interface logic */ + '/', /* slashes cannot work, since we need to use network interfaces in sysfs paths, and in paths slashes are separators */ + '%')) /* %d is used in the kernel's weird foo%d format string naming feature which we really really don't want to ever run into by accident */ + return false; + + return true; +} + bool ifname_valid_full(const char *p, IfnameValidFlags flags) { bool numeric = true; @@ -781,16 +797,7 @@ bool ifname_valid_full(const char *p, IfnameValidFlags flags) { return false; for (const char *t = p; *t; t++) { - if ((unsigned char) *t >= 127U) - return false; - - if ((unsigned char) *t <= 32U) - return false; - - if (IN_SET(*t, - ':', /* colons are used by the legacy "alias" interface logic */ - '/', /* slashes cannot work, since we need to use network interfaces in sysfs paths, and in paths slashes are separators */ - '%')) /* %d is used in the kernel's weird foo%d format string naming feature which we really really don't want to ever run into by accident */ + if (!ifname_valid_char(*t)) return false; numeric = numeric && (*t >= '0' && *t <= '9'); diff --git a/src/libnm-systemd-shared/src/basic/socket-util.h b/src/libnm-systemd-shared/src/basic/socket-util.h index e0b959f5da..f92e425fd6 100644 --- a/src/libnm-systemd-shared/src/basic/socket-util.h +++ b/src/libnm-systemd-shared/src/basic/socket-util.h @@ -139,6 +139,7 @@ typedef enum { IFNAME_VALID_NUMERIC = 1 << 1, _IFNAME_VALID_ALL = IFNAME_VALID_ALTERNATIVE | IFNAME_VALID_NUMERIC, } IfnameValidFlags; +bool ifname_valid_char(char a); bool ifname_valid_full(const char *p, IfnameValidFlags flags); static inline bool ifname_valid(const char *p) { return ifname_valid_full(p, 0); diff --git a/src/libnm-systemd-shared/src/basic/time-util.c b/src/libnm-systemd-shared/src/basic/time-util.c index 26a6762b17..c3b175a192 100644 --- a/src/libnm-systemd-shared/src/basic/time-util.c +++ b/src/libnm-systemd-shared/src/basic/time-util.c @@ -429,19 +429,37 @@ char *format_timestamp_relative(char *buf, size_t l, usec_t t) { s = "left"; } - if (d >= USEC_PER_YEAR) - snprintf(buf, l, USEC_FMT " years " USEC_FMT " months %s", - d / USEC_PER_YEAR, - (d % USEC_PER_YEAR) / USEC_PER_MONTH, s); - else if (d >= USEC_PER_MONTH) - snprintf(buf, l, USEC_FMT " months " USEC_FMT " days %s", - d / USEC_PER_MONTH, - (d % USEC_PER_MONTH) / USEC_PER_DAY, s); - else if (d >= USEC_PER_WEEK) - snprintf(buf, l, USEC_FMT " weeks " USEC_FMT " days %s", - d / USEC_PER_WEEK, - (d % USEC_PER_WEEK) / USEC_PER_DAY, s); - else if (d >= 2*USEC_PER_DAY) + if (d >= USEC_PER_YEAR) { + usec_t years = d / USEC_PER_YEAR; + usec_t months = (d % USEC_PER_YEAR) / USEC_PER_MONTH; + + snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s %s", + years, + years == 1 ? "year" : "years", + months, + months == 1 ? "month" : "months", + s); + } else if (d >= USEC_PER_MONTH) { + usec_t months = d / USEC_PER_MONTH; + usec_t days = (d % USEC_PER_MONTH) / USEC_PER_DAY; + + snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s %s", + months, + months == 1 ? "month" : "months", + days, + days == 1 ? "day" : "days", + s); + } else if (d >= USEC_PER_WEEK) { + usec_t weeks = d / USEC_PER_WEEK; + usec_t days = (d % USEC_PER_WEEK) / USEC_PER_DAY; + + snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s %s", + weeks, + weeks == 1 ? "week" : "weeks", + days, + days == 1 ? "day" : "days", + s); + } else if (d >= 2*USEC_PER_DAY) snprintf(buf, l, USEC_FMT " days %s", d / USEC_PER_DAY, s); else if (d >= 25*USEC_PER_HOUR) snprintf(buf, l, "1 day " USEC_FMT "h %s", @@ -535,14 +553,12 @@ char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) { /* Let's see if we should shows this in dot notation */ if (t < USEC_PER_MINUTE && b > 0) { - usec_t cc; - signed char j; + signed char j = 0; - j = 0; - for (cc = table[i].usec; cc > 1; cc /= 10) + for (usec_t cc = table[i].usec; cc > 1; cc /= 10) j++; - for (cc = accuracy; cc > 1; cc /= 10) { + for (usec_t cc = accuracy; cc > 1; cc /= 10) { b /= 10; j--; } @@ -1244,74 +1260,126 @@ int parse_nsec(const char *t, nsec_t *nsec) { return 0; } -int get_timezones(char ***ret) { +static int get_timezones_from_zone1970_tab(char ***ret) { _cleanup_fclose_ FILE *f = NULL; _cleanup_strv_free_ char **zones = NULL; - size_t n_zones = 0; int r; assert(ret); - zones = strv_new("UTC"); - if (!zones) - return -ENOMEM; + f = fopen("/usr/share/zoneinfo/zone1970.tab", "re"); + if (!f) + return -errno; - n_zones = 1; + for (;;) { + _cleanup_free_ char *line = NULL, *cc = NULL, *co = NULL, *tz = NULL; - f = fopen("/usr/share/zoneinfo/zone1970.tab", "re"); - if (f) { - for (;;) { - _cleanup_free_ char *line = NULL, *w = NULL; - char *p; - size_t k; + r = read_line(f, LONG_LINE_MAX, &line); + if (r < 0) + return r; + if (r == 0) + break; - r = read_line(f, LONG_LINE_MAX, &line); - if (r < 0) - return r; - if (r == 0) - break; + const char *p = line; + + /* Line format is: + * 'country codes' 'coordinates' 'timezone' 'comments' */ + r = extract_many_words(&p, NULL, 0, &cc, &co, &tz, NULL); + if (r < 0) + continue; - p = strstrip(line); + /* Lines that start with # are comments. */ + if (*cc == '#') + continue; + + r = strv_extend(&zones, tz); + if (r < 0) + return r; + } - if (isempty(p) || *p == '#') - continue; + *ret = TAKE_PTR(zones); + return 0; +} - /* Skip over country code */ - p += strcspn(p, WHITESPACE); - p += strspn(p, WHITESPACE); +static int get_timezones_from_tzdata_zi(char ***ret) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_strv_free_ char **zones = NULL; + int r; - /* Skip over coordinates */ - p += strcspn(p, WHITESPACE); - p += strspn(p, WHITESPACE); + f = fopen("/usr/share/zoneinfo/tzdata.zi", "re"); + if (!f) + return -errno; - /* Found timezone name */ - k = strcspn(p, WHITESPACE); - if (k <= 0) - continue; + for (;;) { + _cleanup_free_ char *line = NULL, *type = NULL, *f1 = NULL, *f2 = NULL; - w = strndup(p, k); - if (!w) - return -ENOMEM; + r = read_line(f, LONG_LINE_MAX, &line); + if (r < 0) + return r; + if (r == 0) + break; - if (!GREEDY_REALLOC(zones, n_zones + 2)) - return -ENOMEM; + const char *p = line; - zones[n_zones++] = TAKE_PTR(w); - zones[n_zones] = NULL; - } + /* The only lines we care about are Zone and Link lines. + * Zone line format is: + * 'Zone' 'timezone' ... + * Link line format is: + * 'Link' 'target' 'alias' + * See 'man zic' for more detail. */ + r = extract_many_words(&p, NULL, 0, &type, &f1, &f2, NULL); + if (r < 0) + continue; - strv_sort(zones); - strv_uniq(zones); + char *tz; + if (IN_SET(*type, 'Z', 'z')) + /* Zone lines have timezone in field 1. */ + tz = f1; + else if (IN_SET(*type, 'L', 'l')) + /* Link lines have timezone in field 2. */ + tz = f2; + else + /* Not a line we care about. */ + continue; - } else if (errno != ENOENT) - return -errno; + r = strv_extend(&zones, tz); + if (r < 0) + return r; + } *ret = TAKE_PTR(zones); + return 0; +} + +int get_timezones(char ***ret) { + _cleanup_strv_free_ char **zones = NULL; + int r; + + assert(ret); + + r = get_timezones_from_tzdata_zi(&zones); + if (r == -ENOENT) { + log_debug_errno(r, "Could not get timezone data from tzdata.zi, using zone1970.tab: %m"); + r = get_timezones_from_zone1970_tab(&zones); + if (r == -ENOENT) + log_debug_errno(r, "Could not get timezone data from zone1970.tab, using UTC: %m"); + } + if (r < 0 && r != -ENOENT) + return r; + /* Always include UTC */ + r = strv_extend(&zones, "UTC"); + if (r < 0) + return -ENOMEM; + + strv_sort(zones); + strv_uniq(zones); + + *ret = TAKE_PTR(zones); return 0; } -bool timezone_is_valid(const char *name, int log_level) { +int verify_timezone(const char *name, int log_level) { bool slash = false; const char *p, *t; _cleanup_close_ int fd = -1; @@ -1319,26 +1387,26 @@ bool timezone_is_valid(const char *name, int log_level) { int r; if (isempty(name)) - return false; + return -EINVAL; /* Always accept "UTC" as valid timezone, since it's the fallback, even if user has no timezones installed. */ if (streq(name, "UTC")) - return true; + return 0; if (name[0] == '/') - return false; + return -EINVAL; for (p = name; *p; p++) { if (!(*p >= '0' && *p <= '9') && !(*p >= 'a' && *p <= 'z') && !(*p >= 'A' && *p <= 'Z') && !IN_SET(*p, '-', '_', '+', '/')) - return false; + return -EINVAL; if (*p == '/') { if (slash) - return false; + return -EINVAL; slash = true; } else @@ -1346,38 +1414,31 @@ bool timezone_is_valid(const char *name, int log_level) { } if (slash) - return false; + return -EINVAL; if (p - name >= PATH_MAX) - return false; + return -ENAMETOOLONG; t = strjoina("/usr/share/zoneinfo/", name); fd = open(t, O_RDONLY|O_CLOEXEC); - if (fd < 0) { - log_full_errno(log_level, errno, "Failed to open timezone file '%s': %m", t); - return false; - } + if (fd < 0) + return log_full_errno(log_level, errno, "Failed to open timezone file '%s': %m", t); r = fd_verify_regular(fd); - if (r < 0) { - log_full_errno(log_level, r, "Timezone file '%s' is not a regular file: %m", t); - return false; - } + if (r < 0) + return log_full_errno(log_level, r, "Timezone file '%s' is not a regular file: %m", t); r = loop_read_exact(fd, buf, 4, false); - if (r < 0) { - log_full_errno(log_level, r, "Failed to read from timezone file '%s': %m", t); - return false; - } + if (r < 0) + return log_full_errno(log_level, r, "Failed to read from timezone file '%s': %m", t); /* Magic from tzfile(5) */ - if (memcmp(buf, "TZif", 4) != 0) { - log_full(log_level, "Timezone file '%s' has wrong magic bytes", t); - return false; - } + if (memcmp(buf, "TZif", 4) != 0) + return log_full_errno(log_level, SYNTHETIC_ERRNO(EBADMSG), + "Timezone file '%s' has wrong magic bytes", t); - return true; + return 0; } bool clock_boottime_supported(void) { diff --git a/src/libnm-systemd-shared/src/basic/time-util.h b/src/libnm-systemd-shared/src/basic/time-util.h index cfde189818..895af88299 100644 --- a/src/libnm-systemd-shared/src/basic/time-util.h +++ b/src/libnm-systemd-shared/src/basic/time-util.h @@ -111,20 +111,31 @@ usec_t triple_timestamp_by_clock(triple_timestamp *ts, clockid_t clock); usec_t timespec_load(const struct timespec *ts) _pure_; nsec_t timespec_load_nsec(const struct timespec *ts) _pure_; -struct timespec *timespec_store(struct timespec *ts, usec_t u); -struct timespec *timespec_store_nsec(struct timespec *ts, nsec_t n); +struct timespec* timespec_store(struct timespec *ts, usec_t u); +struct timespec* timespec_store_nsec(struct timespec *ts, nsec_t n); usec_t timeval_load(const struct timeval *tv) _pure_; -struct timeval *timeval_store(struct timeval *tv, usec_t u); +struct timeval* timeval_store(struct timeval *tv, usec_t u); -char *format_timestamp_style(char *buf, size_t l, usec_t t, TimestampStyle style); -char *format_timestamp_relative(char *buf, size_t l, usec_t t); -char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy); +char* format_timestamp_style(char *buf, size_t l, usec_t t, TimestampStyle style) _warn_unused_result_; +char* format_timestamp_relative(char *buf, size_t l, usec_t t) _warn_unused_result_; +char* format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) _warn_unused_result_; -static inline char *format_timestamp(char *buf, size_t l, usec_t t) { +_warn_unused_result_ +static inline char* format_timestamp(char *buf, size_t l, usec_t t) { return format_timestamp_style(buf, l, t, TIMESTAMP_PRETTY); } +/* Note: the lifetime of the compound literal is the immediately surrounding block, + * see C11 §6.5.2.5, and + * https://stackoverflow.com/questions/34880638/compound-literal-lifetime-and-if-blocks */ +#define FORMAT_TIMESTAMP(t) format_timestamp((char[FORMAT_TIMESTAMP_MAX]){}, FORMAT_TIMESTAMP_MAX, t) +#define FORMAT_TIMESTAMP_RELATIVE(t) \ + format_timestamp_relative((char[FORMAT_TIMESTAMP_RELATIVE_MAX]){}, FORMAT_TIMESTAMP_RELATIVE_MAX, t) +#define FORMAT_TIMESPAN(t, accuracy) format_timespan((char[FORMAT_TIMESPAN_MAX]){}, FORMAT_TIMESPAN_MAX, t, accuracy) +#define FORMAT_TIMESTAMP_STYLE(t, style) \ + format_timestamp_style((char[FORMAT_TIMESTAMP_MAX]){}, FORMAT_TIMESTAMP_MAX, t, style) + int parse_timestamp(const char *t, usec_t *usec); int parse_sec(const char *t, usec_t *usec); @@ -134,7 +145,10 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit); int parse_nsec(const char *t, nsec_t *nsec); int get_timezones(char ***l); -bool timezone_is_valid(const char *name, int log_level); +int verify_timezone(const char *name, int log_level); +static inline bool timezone_is_valid(const char *name, int log_level) { + return verify_timezone(name, log_level) >= 0; +} bool clock_boottime_supported(void); bool clock_supported(clockid_t clock); @@ -153,9 +167,8 @@ usec_t jiffies_to_usec(uint32_t jiffies); bool in_utc_timezone(void); static inline usec_t usec_add(usec_t a, usec_t b) { - - /* Adds two time values, and makes sure USEC_INFINITY as input results as USEC_INFINITY in output, and doesn't - * overflow. */ + /* Adds two time values, and makes sure USEC_INFINITY as input results as USEC_INFINITY in output, + * and doesn't overflow. */ if (a > USEC_INFINITY - b) /* overflow check */ return USEC_INFINITY; @@ -164,7 +177,6 @@ static inline usec_t usec_add(usec_t a, usec_t b) { } static inline usec_t usec_sub_unsigned(usec_t timestamp, usec_t delta) { - if (timestamp == USEC_INFINITY) /* Make sure infinity doesn't degrade */ return USEC_INFINITY; if (timestamp < delta) @@ -181,14 +193,14 @@ static inline usec_t usec_sub_signed(usec_t timestamp, int64_t delta) { } #if SIZEOF_TIME_T == 8 -/* The last second we can format is 31. Dec 9999, 1s before midnight, because otherwise we'd enter 5 digit year - * territory. However, since we want to stay away from this in all timezones we take one day off. */ -#define USEC_TIMESTAMP_FORMATTABLE_MAX ((usec_t) 253402214399000000) + /* The last second we can format is 31. Dec 9999, 1s before midnight, because otherwise we'd enter 5 digit + * year territory. However, since we want to stay away from this in all timezones we take one day off. */ +# define USEC_TIMESTAMP_FORMATTABLE_MAX ((usec_t) 253402214399000000) #elif SIZEOF_TIME_T == 4 /* With a 32bit time_t we can't go beyond 2038... */ -#define USEC_TIMESTAMP_FORMATTABLE_MAX ((usec_t) 2147483647000000) +# define USEC_TIMESTAMP_FORMATTABLE_MAX ((usec_t) 2147483647000000) #else -#error "Yuck, time_t is neither 4 nor 8 bytes wide?" +# error "Yuck, time_t is neither 4 nor 8 bytes wide?" #endif int time_change_fd(void); -- cgit v1.2.3