carp: retire ioctl(2) API

All supported stable branches use netlink(4) API to configure carp(4).
The deleted code also has kernel stack leak vulnerability, that requires
extra effort to fix.

Reviewed by:		pouria, kp
Differential Revision:	https://reviews.freebsd.org/D55804
This commit is contained in:
Gleb Smirnoff
2026-03-12 09:30:46 -07:00
parent 7e68af7ce2
commit 72472e52e3
6 changed files with 133 additions and 352 deletions
+7 -2
View File
@@ -156,8 +156,13 @@ setcarp_callback(if_ctx *ctx, void *arg __unused)
if (carpr_vrrp_adv_inter != 0)
carpr.carpr_vrrp_adv_inter = carpr_vrrp_adv_inter;
if (ifconfig_carp_set_info(lifh, ctx->ifname, &carpr))
err(1, "SIOCSVH");
if (ifconfig_carp_set_info(lifh, ctx->ifname, &carpr)) {
if (ifconfig_err_errtype(lifh) == OTHER)
err(1, "%s: %s", __func__,
strerror(ifconfig_err_errno(lifh)));
else
err(1, "%s: %d", __func__, ifconfig_err_errtype(lifh));
}
}
static void
+3 -9
View File
@@ -24,7 +24,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.Dd April 12, 2024
.Dd March 11, 2026
.Dt CARP 4
.Os
.Sh NAME
@@ -71,10 +71,7 @@ and
.Cm advskew
are put inside CARP advertisements.
These values can be configured using
.Xr ifconfig 8 ,
or through the
.Dv SIOCSVH
.Xr ioctl 2 .
.Xr ifconfig 8 .
.Pp
CARP defaults to using multicast messages, but can be configured to unicast
announcements to peers using the
@@ -88,10 +85,7 @@ and
Note that TTL verification is disabled if the peer address is not a multicast
address.
These values can be configured using
.Xr ifconfig 8 ,
or through the
.Dv SIOCSPEER
.Xr ioctl 2 .
.Xr ifconfig 8 .
.Pp
.Xr carp 4
can be configured to use either the non-standard CARP protocol, or VRRPv3 (RFC 5798).
-10
View File
@@ -248,7 +248,6 @@ int (*carp_master_p)(struct ifaddr *);
int (*carp_forus_p)(struct ifnet *ifp, u_char *dhost);
int (*carp_output_p)(struct ifnet *ifp, struct mbuf *m,
const struct sockaddr *sa);
int (*carp_ioctl_p)(struct ifreq *, u_long, struct thread *);
int (*carp_attach_p)(struct ifaddr *, int);
void (*carp_detach_p)(struct ifaddr *, bool);
#endif
@@ -2923,15 +2922,6 @@ ifioctl(struct socket *so, u_long cmd, caddr_t data, struct thread *td)
error = if_getgroupmembers(req);
goto out_noref;
}
#if defined(INET) || defined(INET6)
case SIOCSVH:
case SIOCGVH:
if (carp_ioctl_p == NULL)
error = EPROTONOSUPPORT;
else
error = (*carp_ioctl_p)(ifr, cmd, td);
goto out_noref;
#endif
}
ifp = ifunit_ref(ifr->ifr_name);
+122 -315
View File
@@ -160,23 +160,6 @@ struct carp_if {
#define CIF_PROMISC 0x00000001
};
/* Kernel equivalent of struct carpreq, but with more fields for new features.
* */
struct carpkreq {
int carpr_count;
int carpr_vhid;
int carpr_state;
int carpr_advskew;
int carpr_advbase;
unsigned char carpr_key[CARP_KEY_LEN];
/* Everything above this is identical to carpreq */
struct in_addr carpr_addr;
struct in6_addr carpr_addr6;
carp_version_t carpr_version;
uint8_t carpr_vrrp_priority;
uint16_t carpr_vrrp_adv_inter;
};
/*
* Brief design of carp(4).
*
@@ -2256,216 +2239,6 @@ carp_free_if(struct carp_if *cif)
free(cif, M_CARP);
}
static bool
carp_carprcp(void *arg, struct carp_softc *sc, int priv)
{
struct carpreq *carpr = arg;
CARP_LOCK(sc);
carpr->carpr_state = sc->sc_state;
carpr->carpr_vhid = sc->sc_vhid;
switch (sc->sc_version) {
case CARP_VERSION_CARP:
carpr->carpr_advbase = sc->sc_advbase;
carpr->carpr_advskew = sc->sc_advskew;
if (priv)
bcopy(sc->sc_key, carpr->carpr_key,
sizeof(carpr->carpr_key));
else
bzero(carpr->carpr_key, sizeof(carpr->carpr_key));
break;
case CARP_VERSION_VRRPv3:
break;
}
CARP_UNLOCK(sc);
return (true);
}
static int
carp_ioctl_set(if_t ifp, struct carpkreq *carpr)
{
struct epoch_tracker et;
struct carp_softc *sc = NULL;
int error = 0;
if (carpr->carpr_vhid <= 0 || carpr->carpr_vhid > CARP_MAXVHID)
return (EINVAL);
switch (carpr->carpr_version) {
case CARP_VERSION_CARP:
if (carpr->carpr_advbase != 0 && (carpr->carpr_advbase > 255 ||
carpr->carpr_advbase < CARP_DFLTINTV))
return (EINVAL);
if (carpr->carpr_advskew < 0 || carpr->carpr_advskew >= 255)
return (EINVAL);
break;
case CARP_VERSION_VRRPv3:
/* XXXGL: shouldn't we check anything? */
break;
default:
return (EINVAL);
}
if (ifp->if_carp) {
IFNET_FOREACH_CARP(ifp, sc)
if (sc->sc_vhid == carpr->carpr_vhid)
break;
}
if (sc == NULL)
sc = carp_alloc(ifp, carpr->carpr_version, carpr->carpr_vhid);
else if (sc->sc_version != carpr->carpr_version)
return (EINVAL);
CARP_LOCK(sc);
switch (sc->sc_version) {
case CARP_VERSION_CARP:
if (carpr->carpr_advbase != 0)
sc->sc_advbase = carpr->carpr_advbase;
sc->sc_advskew = carpr->carpr_advskew;
if (carpr->carpr_addr.s_addr != INADDR_ANY)
sc->sc_carpaddr = carpr->carpr_addr;
if (!IN6_IS_ADDR_UNSPECIFIED(&carpr->carpr_addr6)) {
memcpy(&sc->sc_carpaddr6, &carpr->carpr_addr6,
sizeof(sc->sc_carpaddr6));
}
if (carpr->carpr_key[0] != '\0') {
bcopy(carpr->carpr_key, sc->sc_key, sizeof(sc->sc_key));
carp_hmac_prepare(sc);
}
break;
case CARP_VERSION_VRRPv3:
if (carpr->carpr_vrrp_priority != 0)
sc->sc_vrrp_prio = carpr->carpr_vrrp_priority;
if (carpr->carpr_vrrp_adv_inter)
sc->sc_vrrp_adv_inter = carpr->carpr_vrrp_adv_inter;
break;
}
if (sc->sc_state != INIT &&
carpr->carpr_state != sc->sc_state) {
switch (carpr->carpr_state) {
case BACKUP:
callout_stop(&sc->sc_ad_tmo);
carp_set_state(sc, BACKUP,
"user requested via ifconfig");
carp_setrun(sc, 0);
carp_delroute(sc);
break;
case MASTER:
NET_EPOCH_ENTER(et);
carp_master_down_locked(sc,
"user requested via ifconfig");
NET_EPOCH_EXIT(et);
break;
default:
break;
}
}
CARP_UNLOCK(sc);
return (error);
}
static int
carp_ioctl_get(if_t ifp, struct ucred *cred, struct carpreq *carpr,
bool (*outfn)(void *, struct carp_softc *, int), void *arg)
{
int priveleged;
struct carp_softc *sc;
if (carpr->carpr_vhid < 0 || carpr->carpr_vhid > CARP_MAXVHID)
return (EINVAL);
if (carpr->carpr_count < 1)
return (EMSGSIZE);
if (ifp->if_carp == NULL)
return (ENOENT);
priveleged = (priv_check_cred(cred, PRIV_NETINET_CARP) == 0);
if (carpr->carpr_vhid != 0) {
IFNET_FOREACH_CARP(ifp, sc)
if (sc->sc_vhid == carpr->carpr_vhid)
break;
if (sc == NULL)
return (ENOENT);
if (! outfn(arg, sc, priveleged))
return (ENOMEM);
carpr->carpr_count = 1;
} else {
int count;
count = 0;
IFNET_FOREACH_CARP(ifp, sc)
count++;
if (count > carpr->carpr_count)
return (EMSGSIZE);
IFNET_FOREACH_CARP(ifp, sc) {
if (! outfn(arg, sc, priveleged))
return (ENOMEM);
carpr->carpr_count = count;
}
}
return (0);
}
int
carp_ioctl(struct ifreq *ifr, u_long cmd, struct thread *td)
{
struct carpreq carpr;
struct carpkreq carprk = {
.carpr_version = CARP_VERSION_CARP,
};
struct ifnet *ifp;
int error = 0;
if ((error = copyin(ifr_data_get_ptr(ifr), &carpr, sizeof carpr)))
return (error);
ifp = ifunit_ref(ifr->ifr_name);
if ((error = carp_is_supported_if(ifp)) != 0)
goto out;
if ((ifp->if_flags & IFF_MULTICAST) == 0) {
error = EADDRNOTAVAIL;
goto out;
}
sx_xlock(&carp_sx);
switch (cmd) {
case SIOCSVH:
if ((error = priv_check(td, PRIV_NETINET_CARP)))
break;
memcpy(&carprk, &carpr, sizeof(carpr));
error = carp_ioctl_set(ifp, &carprk);
break;
case SIOCGVH:
error = carp_ioctl_get(ifp, td->td_ucred, &carpr,
carp_carprcp, &carpr);
if (error == 0) {
error = copyout(&carpr,
(char *)ifr_data_get_ptr(ifr),
carpr.carpr_count * sizeof(carpr));
}
break;
default:
error = EINVAL;
}
sx_xunlock(&carp_sx);
out:
if (ifp != NULL)
if_rele(ifp);
return (error);
}
static int
carp_get_vhid(struct ifaddr *ifa)
{
@@ -2757,67 +2530,6 @@ nlattr_get_carp_key(struct nlattr *nla, struct nl_pstate *npt, const void *arg,
return (0);
}
struct carp_nl_send_args {
struct nlmsghdr *hdr;
struct nl_pstate *npt;
};
static bool
carp_nl_send(void *arg, struct carp_softc *sc, int priv)
{
struct carp_nl_send_args *nlsa = arg;
struct nlmsghdr *hdr = nlsa->hdr;
struct nl_pstate *npt = nlsa->npt;
struct nl_writer *nw = npt->nw;
struct genlmsghdr *ghdr_new;
if (!nlmsg_reply(nw, hdr, sizeof(struct genlmsghdr))) {
nlmsg_abort(nw);
return (false);
}
ghdr_new = nlmsg_reserve_object(nw, struct genlmsghdr);
if (ghdr_new == NULL) {
nlmsg_abort(nw);
return (false);
}
ghdr_new->cmd = CARP_NL_CMD_GET;
ghdr_new->version = 0;
ghdr_new->reserved = 0;
CARP_LOCK(sc);
nlattr_add_u32(nw, CARP_NL_VHID, sc->sc_vhid);
nlattr_add_u32(nw, CARP_NL_STATE, sc->sc_state);
nlattr_add_u8(nw, CARP_NL_VERSION, sc->sc_version);
switch (sc->sc_version) {
case CARP_VERSION_CARP:
nlattr_add_s32(nw, CARP_NL_ADVBASE, sc->sc_advbase);
nlattr_add_s32(nw, CARP_NL_ADVSKEW, sc->sc_advskew);
nlattr_add_in_addr(nw, CARP_NL_ADDR, &sc->sc_carpaddr);
nlattr_add_in6_addr(nw, CARP_NL_ADDR6, &sc->sc_carpaddr6);
if (priv)
nlattr_add(nw, CARP_NL_KEY, sizeof(sc->sc_key),
sc->sc_key);
break;
case CARP_VERSION_VRRPv3:
nlattr_add_u8(nw, CARP_NL_VRRP_PRIORITY, sc->sc_vrrp_prio);
nlattr_add_u16(nw, CARP_NL_VRRP_ADV_INTER,
sc->sc_vrrp_adv_inter);
break;
}
CARP_UNLOCK(sc);
if (! nlmsg_end(nw)) {
nlmsg_abort(nw);
return (false);
}
return (true);
}
struct nl_carp_parsed {
unsigned int ifindex;
char *ifname;
@@ -2856,16 +2568,20 @@ static int
carp_nl_get(struct nlmsghdr *hdr, struct nl_pstate *npt)
{
struct nl_carp_parsed attrs = { };
struct carp_nl_send_args args;
struct carpreq carpr = { };
struct epoch_tracker et;
struct nl_writer *nw = npt->nw;
struct carp_softc *sc;
if_t ifp = NULL;
int error;
bool privileged;
error = nl_parse_nlmsg(hdr, &carp_parser, npt, &attrs);
if (error != 0)
return (error);
if (attrs.vhid < 0 || attrs.vhid > CARP_MAXVHID)
return (EINVAL);
NET_EPOCH_ENTER(et);
if (attrs.ifname != NULL)
ifp = ifunit_ref(attrs.ifname);
@@ -2876,19 +2592,72 @@ carp_nl_get(struct nlmsghdr *hdr, struct nl_pstate *npt)
if ((error = carp_is_supported_if(ifp)) != 0)
goto out;
hdr->nlmsg_flags |= NLM_F_MULTI;
args.hdr = hdr;
args.npt = npt;
if (ifp->if_carp == NULL) {
error = ENOENT;
goto out;
}
carpr.carpr_vhid = attrs.vhid;
carpr.carpr_count = CARP_MAXVHID;
hdr->nlmsg_flags |= NLM_F_MULTI;
privileged = (priv_check_cred(nlp_get_cred(npt->nlp),
PRIV_NETINET_CARP) == 0);
sx_xlock(&carp_sx);
error = carp_ioctl_get(ifp, nlp_get_cred(npt->nlp), &carpr,
carp_nl_send, &args);
IFNET_FOREACH_CARP(ifp, sc) {
struct genlmsghdr *ghdr_new;
if (attrs.vhid != 0 && attrs.vhid != sc->sc_vhid)
continue;
if (!nlmsg_reply(nw, hdr, sizeof(struct genlmsghdr))) {
nlmsg_abort(nw);
error = ENOMEM;
break;
}
ghdr_new = nlmsg_reserve_object(nw, struct genlmsghdr);
if (ghdr_new == NULL) {
nlmsg_abort(nw);
error = ENOMEM;
break;
}
ghdr_new->cmd = CARP_NL_CMD_GET;
ghdr_new->version = 0;
ghdr_new->reserved = 0;
CARP_LOCK(sc);
nlattr_add_u32(nw, CARP_NL_VHID, sc->sc_vhid);
nlattr_add_u32(nw, CARP_NL_STATE, sc->sc_state);
nlattr_add_u8(nw, CARP_NL_VERSION, sc->sc_version);
switch (sc->sc_version) {
case CARP_VERSION_CARP:
nlattr_add_s32(nw, CARP_NL_ADVBASE, sc->sc_advbase);
nlattr_add_s32(nw, CARP_NL_ADVSKEW, sc->sc_advskew);
nlattr_add_in_addr(nw, CARP_NL_ADDR, &sc->sc_carpaddr);
nlattr_add_in6_addr(nw, CARP_NL_ADDR6,
&sc->sc_carpaddr6);
if (privileged)
nlattr_add(nw, CARP_NL_KEY, sizeof(sc->sc_key),
sc->sc_key);
break;
case CARP_VERSION_VRRPv3:
nlattr_add_u8(nw, CARP_NL_VRRP_PRIORITY,
sc->sc_vrrp_prio);
nlattr_add_u16(nw, CARP_NL_VRRP_ADV_INTER,
sc->sc_vrrp_adv_inter);
break;
}
CARP_UNLOCK(sc);
if (! nlmsg_end(nw)) {
nlmsg_abort(nw);
error = ENOMEM;
break;
}
}
sx_xunlock(&carp_sx);
if (! nlmsg_end_dump(npt->nw, error, hdr))
if (! nlmsg_end_dump(nw, error, hdr))
error = ENOMEM;
out:
@@ -2902,8 +2671,8 @@ static int
carp_nl_set(struct nlmsghdr *hdr, struct nl_pstate *npt)
{
struct nl_carp_parsed attrs = { };
struct carpkreq carpr;
struct epoch_tracker et;
struct carp_softc *sc;
if_t ifp = NULL;
int error;
@@ -2949,26 +2718,66 @@ carp_nl_set(struct nlmsghdr *hdr, struct nl_pstate *npt)
goto out;
}
carpr.carpr_count = 1;
carpr.carpr_vhid = attrs.vhid;
carpr.carpr_state = attrs.state;
carpr.carpr_version = attrs.version;
switch (attrs.version) {
sx_xlock(&carp_sx);
if (ifp->if_carp) {
IFNET_FOREACH_CARP(ifp, sc)
if (sc->sc_vhid == attrs.vhid)
break;
} else
sc = NULL;
if (sc == NULL)
sc = carp_alloc(ifp, attrs.version, attrs.vhid);
else if (sc->sc_version != attrs.version) {
sx_xunlock(&carp_sx);
error = EINVAL;
goto out;
}
CARP_LOCK(sc);
switch (sc->sc_version) {
case CARP_VERSION_CARP:
carpr.carpr_advbase = attrs.advbase;
carpr.carpr_advskew = attrs.advskew;
carpr.carpr_addr = attrs.addr;
carpr.carpr_addr6 = attrs.addr6;
memcpy(&carpr.carpr_key, &attrs.key, sizeof(attrs.key));
if (attrs.advbase != 0)
sc->sc_advbase = attrs.advbase;
sc->sc_advskew = attrs.advskew;
if (attrs.addr.s_addr != INADDR_ANY)
sc->sc_carpaddr = attrs.addr;
if (!IN6_IS_ADDR_UNSPECIFIED(&attrs.addr6)) {
memcpy(&sc->sc_carpaddr6, &attrs.addr6,
sizeof(sc->sc_carpaddr6));
}
if (attrs.key[0] != '\0') {
bcopy(attrs.key, sc->sc_key, sizeof(sc->sc_key));
carp_hmac_prepare(sc);
}
break;
case CARP_VERSION_VRRPv3:
carpr.carpr_vrrp_priority = attrs.vrrp_prio;
carpr.carpr_vrrp_adv_inter = attrs.vrrp_adv_inter;
if (attrs.vrrp_prio != 0)
sc->sc_vrrp_prio = attrs.vrrp_prio;
if (attrs.vrrp_adv_inter)
sc->sc_vrrp_adv_inter = attrs.vrrp_adv_inter;
break;
}
sx_xlock(&carp_sx);
error = carp_ioctl_set(ifp, &carpr);
if (sc->sc_state != INIT && sc->sc_state != attrs.state) {
switch (attrs.state) {
case BACKUP:
callout_stop(&sc->sc_ad_tmo);
carp_set_state(sc, BACKUP,
"user requested via ifconfig");
carp_setrun(sc, 0);
carp_delroute(sc);
break;
case MASTER:
NET_EPOCH_ENTER(et);
carp_master_down_locked(sc,
"user requested via ifconfig");
NET_EPOCH_EXIT(et);
break;
default:
break;
}
}
CARP_UNLOCK(sc);
sx_xunlock(&carp_sx);
out:
@@ -3035,7 +2844,6 @@ carp_mod_cleanup(void)
carp_iamatch6_p = NULL;
carp_macmatch6_p = NULL;
#endif
carp_ioctl_p = NULL;
carp_attach_p = NULL;
carp_detach_p = NULL;
carp_get_vhid_p = NULL;
@@ -3070,7 +2878,6 @@ carp_mod_load(void)
carp_forus_p = carp_forus;
carp_output_p = carp_output;
carp_linkstate_p = carp_linkstate;
carp_ioctl_p = carp_ioctl;
carp_attach_p = carp_attach;
carp_detach_p = carp_detach;
carp_demote_adj_p = carp_demote_adj;
-15
View File
@@ -160,23 +160,10 @@ struct carpstats {
uint64_t carps_preempt; /* if enabled, preemptions */
};
/*
* Configuration structure for SIOCSVH SIOCGVH
*/
struct carpreq {
int carpr_count;
int carpr_vhid;
#define CARP_MAXVHID 255
int carpr_state;
#define CARP_STATES "INIT", "BACKUP", "MASTER"
#define CARP_MAXSTATE 2
int carpr_advskew;
#define CARP_MAXSKEW 240
int carpr_advbase;
unsigned char carpr_key[CARP_KEY_LEN];
};
#define SIOCSVH _IOWR('i', 245, struct ifreq)
#define SIOCGVH _IOWR('i', 246, struct ifreq)
typedef enum carp_version {
CARP_VERSION_CARP = 2,
@@ -184,7 +171,6 @@ typedef enum carp_version {
} carp_version_t;
#ifdef _KERNEL
int carp_ioctl(struct ifreq *, u_long, struct thread *);
int carp_attach(struct ifaddr *, int);
void carp_detach(struct ifaddr *, bool);
void carp_carpdev_state(struct ifnet *);
@@ -198,7 +184,6 @@ int carp_forus(struct ifnet *, u_char *);
/* These are external networking stack hooks for CARP */
/* net/if.c */
extern int (*carp_ioctl_p)(struct ifreq *, u_long, struct thread *);
extern int (*carp_attach_p)(struct ifaddr *, int);
extern void (*carp_detach_p)(struct ifaddr *, bool);
extern void (*carp_linkstate_p)(struct ifnet *);
+1 -1
View File
@@ -74,7 +74,7 @@
* cannot include sys/param.h and should only be updated here.
*/
#undef __FreeBSD_version
#define __FreeBSD_version 1600012
#define __FreeBSD_version 1600013
/*
* __FreeBSD_kernel__ indicates that this system uses the kernel of FreeBSD,