netlink: fix compatibility with older netlink applications.

Some apps try to provide only the non-zero part of the required message
 header instead of the full one. It happens when fetching routes or
 interface addresses, where the first header byte is the family.
This behavior is "illegal" under the "strict" Netlink socket option,
 however there are many applications out there doing things in the
 "old" way.

Support this usecase by copying the provided bytes into the temporary
 zero-filled header and running the parser on this header instead.

Reported by:	Goran Mekić <meka@tilda.center>
This commit is contained in:
Alexander V. Chernikov 2023-01-16 12:14:35 +00:00
parent a8633361ab
commit 228c632ab3
2 changed files with 33 additions and 3 deletions

View File

@ -213,9 +213,19 @@ nl_parse_header(void *hdr, int len, const struct nlhdr_parser *parser,
int error;
if (__predict_false(len < parser->nl_hdr_off)) {
nlmsg_report_err_msg(npt, "header too short: expected %d, got %d",
parser->nl_hdr_off, len);
return (EINVAL);
if (npt->strict) {
nlmsg_report_err_msg(npt, "header too short: expected %d, got %d",
parser->nl_hdr_off, len);
return (EINVAL);
}
/* Compat with older applications: pretend there's a full header */
void *tmp_hdr = npt_alloc(npt, parser->nl_hdr_off);
if (tmp_hdr == NULL)
return (EINVAL);
memcpy(tmp_hdr, hdr, len);
hdr = tmp_hdr;
len = parser->nl_hdr_off;
}
if (npt->strict && parser->sp != NULL && !parser->sp(hdr, npt))

View File

@ -8,6 +8,7 @@
from atf_python.sys.net.netlink import NlConst
from atf_python.sys.net.netlink import NlHelper
from atf_python.sys.net.netlink import NlmBaseFlags
from atf_python.sys.net.netlink import Nlmsghdr
from atf_python.sys.net.netlink import NlMsgType
from atf_python.sys.net.netlink import NlRtMsgType
from atf_python.sys.net.netlink import Nlsock
@ -68,6 +69,25 @@ def test_46_filter_iface(self):
assert len([r for r in ret if r[0] == ifname and r[1] == socket.AF_INET6]) == 2
assert len(ret) == 3
def test_46_filter_family_compat(self):
"""Tests that family filtering works with the stripped header"""
hdr = Nlmsghdr(
nlmsg_len=17,
nlmsg_type=NlRtMsgType.RTM_GETADDR.value,
nlmsg_flags=NlmBaseFlags.NLM_F_ACK.value | NlmBaseFlags.NLM_F_REQUEST.value,
nlmsg_seq=self.helper.get_seq()
)
data = bytes(hdr) + struct.pack("@B", socket.AF_INET)
self.nlsock.write_data(data)
ret = []
for rx_msg in self.read_msg_list(hdr.nlmsg_seq, NlRtMsgType.RTM_NEWADDR):
ifname = socket.if_indextoname(rx_msg.base_hdr.ifa_index)
family = rx_msg.base_hdr.ifa_family
ret.append((ifname, family, rx_msg))
assert len(ret) == 2
def filter_iface_family(self, family, num_items):
"""Tests that listing outputs IPv4 for the specific interface"""
epair_ifname = self.vnet.iface_alias_map["if1"].name