Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'ynl-ethtool-netlink-fix-nla_len-overflow-for-large-string-sets'

Hangbin Liu says:

====================
ynl/ethtool/netlink: fix nla_len overflow for large string sets

This series addresses a silent data corruption issue triggered when ynl
retrieves string sets from NICs with a large number of statistics entries
(e.g. mlx5_core with thousands of ETH_SS_STATS strings).

The root cause is that struct nlattr.nla_len is a __u16 (max 65535
bytes). When a NIC exports enough statistics strings, the
ETHTOOL_A_STRINGSET_STRINGS nest built by strset_fill_set() exceeds
this limit. nla_nest_end() silently truncates the length on assignment,
producing a corrupted netlink message.

Patch 1 moves ethtool.py to selftest.

Patch 2 improves the ethtool tool: rename the doit/dumpit helpers
to do_set/do_get and convert do_get to use ynl.do() with an
explicit device header instead of a full dump with client-side filtering.

Patch 3 adds a --dbg-small-recv option to the YNL ethtool tool,
matching the same option already present in cli.py, to help debug netlink
message size issues

Patch 4 adds a new helper nla_nest_end_safe() to check whether the nla_len
is overflow and return -EMSGSIZE early if so.

Patch 5 uses the new helper in ethtool to make sure the ethtool doesn't
reply a corrupted netlink message.
====================

Link: https://patch.msgid.link/20260408-b4-ynl_ethtool-v2-0-7623a5e8f70b@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

+68 -40
+19
include/net/netlink.h
··· 2265 2265 } 2266 2266 2267 2267 /** 2268 + * nla_nest_end_safe - Validate and finalize nesting of attributes 2269 + * @skb: socket buffer the attributes are stored in 2270 + * @start: container attribute 2271 + * 2272 + * Corrects the container attribute header to include all appended 2273 + * attributes. 2274 + * 2275 + * Returns: the total data length of the skb, or -EMSGSIZE if the 2276 + * nested attribute length exceeds U16_MAX. 2277 + */ 2278 + static inline int nla_nest_end_safe(struct sk_buff *skb, struct nlattr *start) 2279 + { 2280 + if (skb_tail_pointer(skb) - (unsigned char *)start > U16_MAX) 2281 + return -EMSGSIZE; 2282 + 2283 + return nla_nest_end(skb, start); 2284 + } 2285 + 2286 + /** 2268 2287 * nla_nest_cancel - Cancel nesting of attributes 2269 2288 * @skb: socket buffer the message is stored in 2270 2289 * @start: container attribute
+2 -1
net/ethtool/strset.c
··· 443 443 if (strset_fill_string(skb, set_info, i) < 0) 444 444 goto nla_put_failure; 445 445 } 446 - nla_nest_end(skb, strings_attr); 446 + if (nla_nest_end_safe(skb, strings_attr) < 0) 447 + goto nla_put_failure; 447 448 } 448 449 449 450 nla_nest_end(skb, stringset_attr);
+42 -37
tools/net/ynl/pyynl/ethtool.py tools/net/ynl/tests/ethtool.py
··· 14 14 import os 15 15 16 16 # pylint: disable=no-name-in-module,wrong-import-position 17 - sys.path.append(pathlib.Path(__file__).resolve().parent.as_posix()) 17 + sys.path.append(pathlib.Path(__file__).resolve().parent.parent.joinpath('pyynl').as_posix()) 18 18 # pylint: disable=import-error 19 19 from cli import schema_dir, spec_dir 20 20 from lib import YnlFamily ··· 84 84 speed = [ k for k, v in value.items() if v and speed_re.match(k) ] 85 85 print(f'{name}: {" ".join(speed)}') 86 86 87 - def doit(ynl, args, op_name): 87 + def do_set(ynl, args, op_name): 88 88 """ 89 - Prepare request header, parse arguments and doit. 89 + Prepare request header, parse arguments and do a set operation. 90 90 """ 91 91 req = { 92 92 'header': { ··· 97 97 args_to_req(ynl, op_name, args.args, req) 98 98 ynl.do(op_name, req) 99 99 100 - def dumpit(ynl, args, op_name, extra=None): 100 + def do_get(ynl, args, op_name, extra=None): 101 101 """ 102 - Prepare request header, parse arguments and dumpit (filtering out the 103 - devices we're not interested in). 102 + Prepare request header and get info for a specific device using doit. 104 103 """ 105 104 extra = extra or {} 106 - reply = ynl.dump(op_name, { 'header': {} } | extra) 105 + req = {'header': {'dev-name': args.device}} 106 + req['header'].update(extra.pop('header', {})) 107 + req.update(extra) 108 + 109 + reply = ynl.do(op_name, req) 107 110 if not reply: 108 111 return {} 109 112 110 - for msg in reply: 111 - if msg['header']['dev-name'] == args.device: 112 - if args.json: 113 - pprint.PrettyPrinter().pprint(msg) 114 - sys.exit(0) 115 - msg.pop('header', None) 116 - return msg 117 - 118 - print(f"Not supported for device {args.device}") 119 - sys.exit(1) 113 + if args.json: 114 + pprint.PrettyPrinter().pprint(reply) 115 + sys.exit(0) 116 + reply.pop('header', None) 117 + return reply 120 118 121 119 def bits_to_dict(attr): 122 120 """ ··· 166 168 parser.add_argument('device', metavar='device', type=str) 167 169 parser.add_argument('args', metavar='args', type=str, nargs='*') 168 170 171 + dbg_group = parser.add_argument_group('Debug options') 172 + dbg_group.add_argument('--dbg-small-recv', default=0, const=4000, 173 + action='store', nargs='?', type=int, metavar='INT', 174 + help="Length of buffers used for recv()") 175 + 169 176 args = parser.parse_args() 170 177 171 178 spec = os.path.join(spec_dir(), 'ethtool.yaml') 172 179 schema = os.path.join(schema_dir(), 'genetlink-legacy.yaml') 173 180 174 - ynl = YnlFamily(spec, schema) 181 + ynl = YnlFamily(spec, schema, recv_size=args.dbg_small_recv) 182 + if args.dbg_small_recv: 183 + ynl.set_recv_dbg(True) 175 184 176 185 if args.set_priv_flags: 177 186 # TODO: parse the bitmask ··· 186 181 return 187 182 188 183 if args.set_eee: 189 - doit(ynl, args, 'eee-set') 184 + do_set(ynl, args, 'eee-set') 190 185 return 191 186 192 187 if args.set_pause: 193 - doit(ynl, args, 'pause-set') 188 + do_set(ynl, args, 'pause-set') 194 189 return 195 190 196 191 if args.set_coalesce: 197 - doit(ynl, args, 'coalesce-set') 192 + do_set(ynl, args, 'coalesce-set') 198 193 return 199 194 200 195 if args.set_features: ··· 203 198 return 204 199 205 200 if args.set_channels: 206 - doit(ynl, args, 'channels-set') 201 + do_set(ynl, args, 'channels-set') 207 202 return 208 203 209 204 if args.set_ring: 210 - doit(ynl, args, 'rings-set') 205 + do_set(ynl, args, 'rings-set') 211 206 return 212 207 213 208 if args.show_priv_flags: 214 - flags = bits_to_dict(dumpit(ynl, args, 'privflags-get')['flags']) 209 + flags = bits_to_dict(do_get(ynl, args, 'privflags-get')['flags']) 215 210 print_field(flags) 216 211 return 217 212 218 213 if args.show_eee: 219 - eee = dumpit(ynl, args, 'eee-get') 214 + eee = do_get(ynl, args, 'eee-get') 220 215 ours = bits_to_dict(eee['modes-ours']) 221 216 peer = bits_to_dict(eee['modes-peer']) 222 217 ··· 237 232 return 238 233 239 234 if args.show_pause: 240 - print_field(dumpit(ynl, args, 'pause-get'), 235 + print_field(do_get(ynl, args, 'pause-get'), 241 236 ('autoneg', 'Autonegotiate', 'bool'), 242 237 ('rx', 'RX', 'bool'), 243 238 ('tx', 'TX', 'bool')) 244 239 return 245 240 246 241 if args.show_coalesce: 247 - print_field(dumpit(ynl, args, 'coalesce-get')) 242 + print_field(do_get(ynl, args, 'coalesce-get')) 248 243 return 249 244 250 245 if args.show_features: 251 - reply = dumpit(ynl, args, 'features-get') 246 + reply = do_get(ynl, args, 'features-get') 252 247 available = bits_to_dict(reply['hw']) 253 248 requested = bits_to_dict(reply['wanted']).keys() 254 249 active = bits_to_dict(reply['active']).keys() ··· 275 270 return 276 271 277 272 if args.show_channels: 278 - reply = dumpit(ynl, args, 'channels-get') 273 + reply = do_get(ynl, args, 'channels-get') 279 274 print(f'Channel parameters for {args.device}:') 280 275 281 276 print('Pre-set maximums:') ··· 295 290 return 296 291 297 292 if args.show_ring: 298 - reply = dumpit(ynl, args, 'channels-get') 293 + reply = do_get(ynl, args, 'channels-get') 299 294 300 295 print(f'Ring parameters for {args.device}:') 301 296 ··· 324 319 print('NIC statistics:') 325 320 326 321 # TODO: pass id? 327 - strset = dumpit(ynl, args, 'strset-get') 322 + strset = do_get(ynl, args, 'strset-get') 328 323 pprint.PrettyPrinter().pprint(strset) 329 324 330 325 req = { ··· 343 338 }, 344 339 } 345 340 346 - rsp = dumpit(ynl, args, 'stats-get', req) 341 + rsp = do_get(ynl, args, 'stats-get', req) 347 342 pprint.PrettyPrinter().pprint(rsp) 348 343 return 349 344 ··· 354 349 }, 355 350 } 356 351 357 - tsinfo = dumpit(ynl, args, 'tsinfo-get', req) 352 + tsinfo = do_get(ynl, args, 'tsinfo-get', req) 358 353 359 354 print(f'Time stamping parameters for {args.device}:') 360 355 ··· 382 377 return 383 378 384 379 print(f'Settings for {args.device}:') 385 - linkmodes = dumpit(ynl, args, 'linkmodes-get') 380 + linkmodes = do_get(ynl, args, 'linkmodes-get') 386 381 ours = bits_to_dict(linkmodes['ours']) 387 382 388 383 supported_ports = ('TP', 'AUI', 'BNC', 'MII', 'FIBRE', 'Backplane') ··· 430 425 5: 'Directly Attached Copper', 431 426 0xef: 'None', 432 427 } 433 - linkinfo = dumpit(ynl, args, 'linkinfo-get') 428 + linkinfo = do_get(ynl, args, 'linkinfo-get') 434 429 print(f'Port: {ports.get(linkinfo["port"], "Other")}') 435 430 436 431 print_field(linkinfo, ('phyaddr', 'PHYAD')) ··· 452 447 mdix = mdix_ctrl.get(linkinfo['tp-mdix'], 'Unknown (auto)') 453 448 print(f'MDI-X: {mdix}') 454 449 455 - debug = dumpit(ynl, args, 'debug-get') 450 + debug = do_get(ynl, args, 'debug-get') 456 451 msgmask = bits_to_dict(debug.get("msgmask", [])).keys() 457 452 print(f'Current message level: {" ".join(msgmask)}') 458 453 459 - linkstate = dumpit(ynl, args, 'linkstate-get') 454 + linkstate = do_get(ynl, args, 'linkstate-get') 460 455 detected_states = { 461 456 0: 'no', 462 457 1: 'yes',
+4 -1
tools/net/ynl/tests/Makefile
··· 36 36 rt-route \ 37 37 # end of TEST_GEN_FILES 38 38 39 - TEST_FILES := ynl_nsim_lib.sh 39 + TEST_FILES := \ 40 + ethtool.py \ 41 + ynl_nsim_lib.sh \ 42 + # end of TEST_FILES 40 43 41 44 CFLAGS_netdev:=$(CFLAGS_netdev) $(CFLAGS_rt-link) 42 45 CFLAGS_ovs:=$(CFLAGS_ovs_datapath)
+1 -1
tools/net/ynl/tests/test_ynl_ethtool.sh
··· 8 8 source "$KSELFTEST_KTAP_HELPERS" 9 9 10 10 # Default ynl-ethtool path for direct execution, can be overridden by make install 11 - ynl_ethtool="../pyynl/ethtool.py" 11 + ynl_ethtool="./ethtool.py" 12 12 13 13 readonly NSIM_ID="1337" 14 14 readonly NSIM_DEV_NAME="nsim${NSIM_ID}"