AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/07/01 07:29 flow patch-triage
0m Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch only modifies user-space Python tooling for Netlink (YNL) in tools/net/ynl/ and does not change any kernel code.",
  "WorthFuzzing": false
}

1/1 2026/07/01 07:29 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit e7a3780e43d6f76f45093b9cd790caf690114a8d\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Wed Jul 1 07:29:27 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/tools/net/ynl/pyynl/__init__.py b/tools/net/ynl/pyynl/__init__.py\nindex e69de29bb2d1d6..d8f59c132ab7ee 100644\n--- a/tools/net/ynl/pyynl/__init__.py\n+++ b/tools/net/ynl/pyynl/__init__.py\n@@ -0,0 +1,9 @@\n+# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause\n+\n+\"\"\" Python YNL (YAML Netlink) library. \"\"\"\n+\n+# Re-export the public library API so it can be imported straight from the\n+# package, e.g. `from pyynl import YnlFamily`.\n+# pylint: disable=wildcard-import,unused-wildcard-import\n+from .lib import *\n+from .lib import __all__\ndiff --git a/tools/net/ynl/pyynl/cli.py b/tools/net/ynl/pyynl/cli.py\nindex 8275a806cf73d6..b6a6ce12b4a7f8 100755\n--- a/tools/net/ynl/pyynl/cli.py\n+++ b/tools/net/ynl/pyynl/cli.py\n@@ -17,9 +17,7 @@ import textwrap\n # pylint: disable=no-name-in-module,wrong-import-position\n sys.path.append(pathlib.Path(__file__).resolve().parent.as_posix())\n from lib import YnlFamily, Netlink, NlError, SpecFamily, SpecException, YnlException\n-\n-SYS_SCHEMA_DIR='/usr/share/ynl'\n-RELATIVE_SCHEMA_DIR='../../../../Documentation/netlink'\n+from lib import list_families\n \n # pylint: disable=too-few-public-methods,too-many-locals\n class Colors:\n@@ -48,30 +46,6 @@ def term_width():\n     \"\"\" Get terminal width in columns (80 if stdout is not a terminal) \"\"\"\n     return shutil.get_terminal_size().columns\n \n-def schema_dir():\n-    \"\"\"\n-    Return the effective schema directory, preferring in-tree before\n-    system schema directory.\n-    \"\"\"\n-    script_dir = os.path.dirname(os.path.abspath(__file__))\n-    schema_dir_ = os.path.abspath(f\"{script_dir}/{RELATIVE_SCHEMA_DIR}\")\n-    if not os.path.isdir(schema_dir_):\n-        schema_dir_ = SYS_SCHEMA_DIR\n-    if not os.path.isdir(schema_dir_):\n-        raise YnlException(f\"Schema directory {schema_dir_} does not exist\")\n-    return schema_dir_\n-\n-def spec_dir():\n-    \"\"\"\n-    Return the effective spec directory, relative to the effective\n-    schema directory.\n-    \"\"\"\n-    spec_dir_ = schema_dir() + '/specs'\n-    if not os.path.isdir(spec_dir_):\n-        raise YnlException(f\"Spec directory {spec_dir_} does not exist\")\n-    return spec_dir_\n-\n-\n class YnlEncoder(json.JSONEncoder):\n     \"\"\"A custom encoder for emitting JSON with ynl-specific instance types\"\"\"\n     def default(self, o):\n@@ -272,9 +246,8 @@ def main():\n             pprint.pprint(msg, width=term_width(), compact=True)\n \n     if args.list_families:\n-        for filename in sorted(os.listdir(spec_dir())):\n-            if filename.endswith('.yaml'):\n-                print(filename.removesuffix('.yaml'))\n+        for family in list_families():\n+            print(family)\n         return\n \n     if args.no_schema:\n@@ -284,28 +257,23 @@ def main():\n     if args.json_text:\n         attrs = json.loads(args.json_text)\n \n-    if args.family:\n-        spec = f\"{spec_dir()}/{args.family}.yaml\"\n-    else:\n-        spec = args.spec\n-    if not os.path.isfile(spec):\n-        raise YnlException(f\"Spec file {spec} does not exist\")\n+    if args.spec and not os.path.isfile(args.spec):\n+        raise YnlException(f\"Spec file {args.spec} does not exist\")\n \n+    # Spec/YnlFamily will raise if both or neither spec and family are given\n     if args.validate:\n+        # Force validation even for installed specs (schema=True), unless the\n+        # user explicitly picked a schema or opted out with --no-schema.\n+        schema = True if args.schema is None else args.schema\n         try:\n-            SpecFamily(spec, args.schema)\n+            SpecFamily(args.spec, schema_path=schema, family=args.family)\n         except SpecException as error:\n             print(error)\n             sys.exit(1)\n         return\n \n-    if args.family: # set behaviour when using installed specs\n-        if args.schema is None and spec.startswith(SYS_SCHEMA_DIR):\n-            args.schema = '' # disable schema validation when installed\n-        if args.process_unknown is None:\n-            args.process_unknown = True\n-\n-    ynl = YnlFamily(spec, args.schema, args.process_unknown,\n+    ynl = YnlFamily(args.spec, schema=args.schema, family=args.family,\n+                    process_unknown=args.process_unknown,\n                     recv_size=args.dbg_small_recv)\n     if args.dbg_small_recv:\n         ynl.set_recv_dbg(True)\ndiff --git a/tools/net/ynl/pyynl/lib/__init__.py b/tools/net/ynl/pyynl/lib/__init__.py\nindex be741985ae4ee7..aa4263c8cba9ef 100644\n--- a/tools/net/ynl/pyynl/lib/__init__.py\n+++ b/tools/net/ynl/pyynl/lib/__init__.py\n@@ -5,12 +5,13 @@\n from .nlspec import SpecAttr, SpecAttrSet, SpecEnumEntry, SpecEnumSet, \\\n     SpecFamily, SpecOperation, SpecSubMessage, SpecSubMessageFormat, \\\n     SpecException\n+from .specdir import list_families\n from .ynl import YnlFamily, Netlink, NlError, NlPolicy, YnlException\n \n from .doc_generator import YnlDocGenerator\n \n __all__ = [\"SpecAttr\", \"SpecAttrSet\", \"SpecEnumEntry\", \"SpecEnumSet\",\n            \"SpecFamily\", \"SpecOperation\", \"SpecSubMessage\", \"SpecSubMessageFormat\",\n-           \"SpecException\",\n+           \"SpecException\", \"list_families\",\n            \"YnlFamily\", \"Netlink\", \"NlError\", \"NlPolicy\", \"YnlException\",\n            \"YnlDocGenerator\"]\ndiff --git a/tools/net/ynl/pyynl/lib/nlspec.py b/tools/net/ynl/pyynl/lib/nlspec.py\nindex 0469a0e270d059..b4ec59814ab1bc 100644\n--- a/tools/net/ynl/pyynl/lib/nlspec.py\n+++ b/tools/net/ynl/pyynl/lib/nlspec.py\n@@ -12,6 +12,8 @@ import importlib\n import os\n import yaml as pyyaml\n \n+from .specdir import find_spec, SYS_SCHEMA_DIR\n+\n \n class SpecException(Exception):\n     \"\"\"Netlink spec exception.\n@@ -444,7 +446,23 @@ class SpecFamily(SpecElement):\n     except AttributeError:\n         _yaml_loader = pyyaml.SafeLoader\n \n-    def __init__(self, spec_path, schema_path=None, exclude_ops=None):\n+    def __init__(self, spec_path=None, schema_path=None, exclude_ops=None,\n+                 family=None):\n+        # schema_path selects how the spec is validated:\n+        #   None  -- no preference: validate against the default schema,\n+        #            but trust (skip) installed specs selected by family=\n+        #   True  -- always validate against the default schema\n+        #   path  -- validate against this schema\n+        #   ''    -- do not validate\n+        if (spec_path is None) == (family is None):\n+            raise ValueError(\"Specify exactly one of spec path or family name\")\n+        if family is not None:\n+            spec_path = find_spec(family)\n+            # Installed specs are assumed correct, so skip schema validation\n+            # to save cycles unless the caller asked to validate.\n+            if schema_path is None and spec_path.startswith(SYS_SCHEMA_DIR):\n+                schema_path = ''\n+\n         with open(spec_path, \"r\", encoding='utf-8') as stream:\n             prefix = '# SPDX-License-Identifier: '\n             first = stream.readline().strip()\n@@ -465,7 +483,7 @@ class SpecFamily(SpecElement):\n         self.proto = self.yaml.get('protocol', 'genetlink')\n         self.msg_id_model = self.yaml['operations'].get('enum-model', 'unified')\n \n-        if schema_path is None:\n+        if schema_path is None or schema_path is True:\n             schema_path = os.path.dirname(os.path.dirname(spec_path)) + f'/{self.proto}.yaml'\n         if schema_path:\n             with open(schema_path, \"r\", encoding='utf-8') as stream:\ndiff --git a/tools/net/ynl/pyynl/lib/specdir.py b/tools/net/ynl/pyynl/lib/specdir.py\nnew file mode 100644\nindex 00000000000000..fcea9b9fb7b043\n--- /dev/null\n+++ b/tools/net/ynl/pyynl/lib/specdir.py\n@@ -0,0 +1,51 @@\n+# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause\n+\n+\"\"\"\n+Locating YNL spec and schema files on disk.\n+\n+Resolves the directory holding the YAML specs (preferring an in-tree copy\n+over the installed system path) and maps family names to spec files.\n+\"\"\"\n+\n+import os\n+\n+SYS_SCHEMA_DIR='/usr/share/ynl'\n+RELATIVE_SCHEMA_DIR='../../../../../Documentation/netlink'\n+\n+\n+def schema_dir():\n+    \"\"\"\n+    Return the effective schema directory, preferring in-tree before\n+    system schema directory.\n+    \"\"\"\n+    script_dir = os.path.dirname(os.path.abspath(__file__))\n+    schema_dir_ = os.path.abspath(f\"{script_dir}/{RELATIVE_SCHEMA_DIR}\")\n+    if not os.path.isdir(schema_dir_):\n+        schema_dir_ = SYS_SCHEMA_DIR\n+    if not os.path.isdir(schema_dir_):\n+        raise FileNotFoundError(f\"Schema directory {schema_dir_} does not exist\")\n+    return schema_dir_\n+\n+def spec_dir():\n+    \"\"\"\n+    Return the effective spec directory, relative to the effective\n+    schema directory.\n+    \"\"\"\n+    spec_dir_ = schema_dir() + '/specs'\n+    if not os.path.isdir(spec_dir_):\n+        raise FileNotFoundError(f\"Spec directory {spec_dir_} does not exist\")\n+    return spec_dir_\n+\n+\n+def find_spec(family):\n+    \"\"\" Return the path to the YAML spec file for a family by name. \"\"\"\n+    spec = f\"{spec_dir()}/{family}.yaml\"\n+    if not os.path.isfile(spec):\n+        raise FileNotFoundError(f\"Spec for family '{family}' not found at {spec}\")\n+    return spec\n+\n+\n+def list_families():\n+    \"\"\" Return the sorted names of all families with an installed spec. \"\"\"\n+    return sorted(f.removesuffix('.yaml')\n+                  for f in os.listdir(spec_dir()) if f.endswith('.yaml'))\ndiff --git a/tools/net/ynl/pyynl/lib/ynl.py b/tools/net/ynl/pyynl/lib/ynl.py\nindex 092d132edec12f..8682bf588e1fa0 100644\n--- a/tools/net/ynl/pyynl/lib/ynl.py\n+++ b/tools/net/ynl/pyynl/lib/ynl.py\n@@ -661,6 +661,14 @@ class YnlFamily(SpecFamily):\n     \"\"\"\n     YNL family -- a Netlink interface built from a YAML spec.\n \n+    The spec can be selected either by file path (def_path=) or, when it\n+    ships in a well-known location, by family name (family=\"xyz\"); exactly\n+    one of the two must be given. For example:\n+\n+      from pyynl import YnlFamily\n+\n+      ynl = YnlFamily(family=\"netdev\")\n+\n     Primary use of the class is to execute Netlink commands:\n \n       ynl.\u003cop_name\u003e(attrs, ...)\n@@ -691,11 +699,16 @@ class YnlFamily(SpecFamily):\n \n       ynl.get_policy(op_name, mode)      -- query kernel policy for an op\n     \"\"\"\n-    def __init__(self, def_path, schema=None, process_unknown=False,\n-                 recv_size=0):\n-        super().__init__(def_path, schema)\n+    def __init__(self, def_path=None, schema=None, process_unknown=None,\n+                 recv_size=0, family=None):\n+        super().__init__(def_path, schema, family=family)\n \n         self.include_raw = False\n+        # Specs from /usr (selected by family=) have a higher chance of being\n+        # stale, default to ignoring unknown attrs. In-tree users, and users\n+        # who bundle the spec need to make a conscious decision.\n+        if process_unknown is None:\n+            process_unknown = family is not None\n         self.process_unknown = process_unknown\n \n         try:\ndiff --git a/tools/net/ynl/tests/ethtool.py b/tools/net/ynl/tests/ethtool.py\nindex db3b62c652e77f..0ee0c8e87686be 100755\n--- a/tools/net/ynl/tests/ethtool.py\n+++ b/tools/net/ynl/tests/ethtool.py\n@@ -11,12 +11,10 @@ import pathlib\n import pprint\n import sys\n import re\n-import os\n \n # pylint: disable=no-name-in-module,wrong-import-position\n sys.path.append(pathlib.Path(__file__).resolve().parent.parent.joinpath('pyynl').as_posix())\n # pylint: disable=import-error\n-from cli import schema_dir, spec_dir\n from lib import YnlFamily\n \n \n@@ -173,10 +171,7 @@ def main():\n \n     args = parser.parse_args()\n \n-    spec = os.path.join(spec_dir(), 'ethtool.yaml')\n-    schema = os.path.join(schema_dir(), 'genetlink-legacy.yaml')\n-\n-    ynl = YnlFamily(spec, schema, recv_size=args.dbg_small_recv)\n+    ynl = YnlFamily(family='ethtool', recv_size=args.dbg_small_recv)\n     if args.dbg_small_recv:\n         ynl.set_recv_dbg(True)\n \n"
}

2/1 2026/07/01 07:29 agent patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch only modifies user-space Python tooling for Netlink (YNL) in tools/net/ynl/ and does not change any kernel code.",
  "WorthFuzzing": false
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Do not list too generic configs (we already have them enabled). Only list those that
   specifically cover the modified code. List them in the EnableConfigs output array,
   and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit e7a3780e43d6f76f45093b9cd790caf690114a8d
Author: syz-cluster <triage@syzkaller.com>
Date:   Wed Jul 1 07:29:27 2026 +0000

    syz-cluster: applied patch under review

diff --git a/tools/net/ynl/pyynl/__init__.py b/tools/net/ynl/pyynl/__init__.py
index e69de29bb2d1d6..d8f59c132ab7ee 100644
--- a/tools/net/ynl/pyynl/__init__.py
+++ b/tools/net/ynl/pyynl/__init__.py
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+
+""" Python YNL (YAML Netlink) library. """
+
+# Re-export the public library API so it can be imported straight from the
+# package, e.g. `from pyynl import YnlFamily`.
+# pylint: disable=wildcard-import,unused-wildcard-import
+from .lib import *
+from .lib import __all__
diff --git a/tools/net/ynl/pyynl/cli.py b/tools/net/ynl/pyynl/cli.py
index 8275a806cf73d6..b6a6ce12b4a7f8 100755
--- a/tools/net/ynl/pyynl/cli.py
+++ b/tools/net/ynl/pyynl/cli.py
@@ -17,9 +17,7 @@ import textwrap
 # pylint: disable=no-name-in-module,wrong-import-position
 sys.path.append(pathlib.Path(__file__).resolve().parent.as_posix())
 from lib import YnlFamily, Netlink, NlError, SpecFamily, SpecException, YnlException
-
-SYS_SCHEMA_DIR='/usr/share/ynl'
-RELATIVE_SCHEMA_DIR='../../../../Documentation/netlink'
+from lib import list_families
 
 # pylint: disable=too-few-public-methods,too-many-locals
 class Colors:
@@ -48,30 +46,6 @@ def term_width():
     """ Get terminal width in columns (80 if stdout is not a terminal) """
     return shutil.get_terminal_size().columns
 
-def schema_dir():
-    """
-    Return the effective schema directory, preferring in-tree before
-    system schema directory.
-    """
-    script_dir = os.path.dirname(os.path.abspath(__file__))
-    schema_dir_ = os.path.abspath(f"{script_dir}/{RELATIVE_SCHEMA_DIR}")
-    if not os.path.isdir(schema_dir_):
-        schema_dir_ = SYS_SCHEMA_DIR
-    if not os.path.isdir(schema_dir_):
-        raise YnlException(f"Schema directory {schema_dir_} does not exist")
-    return schema_dir_
-
-def spec_dir():
-    """
-    Return the effective spec directory, relative to the effective
-    schema directory.
-    """
-    spec_dir_ = schema_dir() + '/specs'
-    if not os.path.isdir(spec_dir_):
-        raise YnlException(f"Spec directory {spec_dir_} does not exist")
-    return spec_dir_
-
-
 class YnlEncoder(json.JSONEncoder):
     """A custom encoder for emitting JSON with ynl-specific instance types"""
     def default(self, o):
@@ -272,9 +246,8 @@ def main():
             pprint.pprint(msg, width=term_width(), compact=True)
 
     if args.list_families:
-        for filename in sorted(os.listdir(spec_dir())):
-            if filename.endswith('.yaml'):
-                print(filename.removesuffix('.yaml'))
+        for family in list_families():
+            print(family)
         return
 
     if args.no_schema:
@@ -284,28 +257,23 @@ def main():
     if args.json_text:
         attrs = json.loads(args.json_text)
 
-    if args.family:
-        spec = f"{spec_dir()}/{args.family}.yaml"
-    else:
-        spec = args.spec
-    if not os.path.isfile(spec):
-        raise YnlException(f"Spec file {spec} does not exist")
+    if args.spec and not os.path.isfile(args.spec):
+        raise YnlException(f"Spec file {args.spec} does not exist")
 
+    # Spec/YnlFamily will raise if both or neither spec and family are given
     if args.validate:
+        # Force validation even for installed specs (schema=True), unless the
+        # user explicitly picked a schema or opted out with --no-schema.
+        schema = True if args.schema is None else args.schema
         try:
-            SpecFamily(spec, args.schema)
+            SpecFamily(args.spec, schema_path=schema, family=args.family)
         except SpecException as error:
             print(error)
             sys.exit(1)
         return
 
-    if args.family: # set behaviour when using installed specs
-        if args.schema is None and spec.startswith(SYS_SCHEMA_DIR):
-            args.schema = '' # disable schema validation when installed
-        if args.process_unknown is None:
-            args.process_unknown = True
-
-    ynl = YnlFamily(spec, args.schema, args.process_unknown,
+    ynl = YnlFamily(args.spec, schema=args.schema, family=args.family,
+                    process_unknown=args.process_unknown,
                     recv_size=args.dbg_small_recv)
     if args.dbg_small_recv:
         ynl.set_recv_dbg(True)
diff --git a/tools/net/ynl/pyynl/lib/__init__.py b/tools/net/ynl/pyynl/lib/__init__.py
index be741985ae4ee7..aa4263c8cba9ef 100644
--- a/tools/net/ynl/pyynl/lib/__init__.py
+++ b/tools/net/ynl/pyynl/lib/__init__.py
@@ -5,12 +5,13 @@
 from .nlspec import SpecAttr, SpecAttrSet, SpecEnumEntry, SpecEnumSet, \
     SpecFamily, SpecOperation, SpecSubMessage, SpecSubMessageFormat, \
     SpecException
+from .specdir import list_families
 from .ynl import YnlFamily, Netlink, NlError, NlPolicy, YnlException
 
 from .doc_generator import YnlDocGenerator
 
 __all__ = ["SpecAttr", "SpecAttrSet", "SpecEnumEntry", "SpecEnumSet",
            "SpecFamily", "SpecOperation", "SpecSubMessage", "SpecSubMessageFormat",
-           "SpecException",
+           "SpecException", "list_families",
            "YnlFamily", "Netlink", "NlError", "NlPolicy", "YnlException",
            "YnlDocGenerator"]
diff --git a/tools/net/ynl/pyynl/lib/nlspec.py b/tools/net/ynl/pyynl/lib/nlspec.py
index 0469a0e270d059..b4ec59814ab1bc 100644
--- a/tools/net/ynl/pyynl/lib/nlspec.py
+++ b/tools/net/ynl/pyynl/lib/nlspec.py
@@ -12,6 +12,8 @@ import importlib
 import os
 import yaml as pyyaml
 
+from .specdir import find_spec, SYS_SCHEMA_DIR
+
 
 class SpecException(Exception):
     """Netlink spec exception.
@@ -444,7 +446,23 @@ class SpecFamily(SpecElement):
     except AttributeError:
         _yaml_loader = pyyaml.SafeLoader
 
-    def __init__(self, spec_path, schema_path=None, exclude_ops=None):
+    def __init__(self, spec_path=None, schema_path=None, exclude_ops=None,
+                 family=None):
+        # schema_path selects how the spec is validated:
+        #   None  -- no preference: validate against the default schema,
+        #            but trust (skip) installed specs selected by family=
+        #   True  -- always validate against the default schema
+        #   path  -- validate against this schema
+        #   ''    -- do not validate
+        if (spec_path is None) == (family is None):
+            raise ValueError("Specify exactly one of spec path or family name")
+        if family is not None:
+            spec_path = find_spec(family)
+            # Installed specs are assumed correct, so skip schema validation
+            # to save cycles unless the caller asked to validate.
+            if schema_path is None and spec_path.startswith(SYS_SCHEMA_DIR):
+                schema_path = ''
+
         with open(spec_path, "r", encoding='utf-8') as stream:
             prefix = '# SPDX-License-Identifier: '
             first = stream.readline().strip()
@@ -465,7 +483,7 @@ class SpecFamily(SpecElement):
         self.proto = self.yaml.get('protocol', 'genetlink')
         self.msg_id_model = self.yaml['operations'].get('enum-model', 'unified')
 
-        if schema_path is None:
+        if schema_path is None or schema_path is True:
             schema_path = os.path.dirname(os.path.dirname(spec_path)) + f'/{self.proto}.yaml'
         if schema_path:
             with open(schema_path, "r", encoding='utf-8') as stream:
diff --git a/tools/net/ynl/pyynl/lib/specdir.py b/tools/net/ynl/pyynl/lib/specdir.py
new file mode 100644
index 00000000000000..fcea9b9fb7b043
--- /dev/null
+++ b/tools/net/ynl/pyynl/lib/specdir.py
@@ -0,0 +1,51 @@
+# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+
+"""
+Locating YNL spec and schema files on disk.
+
+Resolves the directory holding the YAML specs (preferring an in-tree copy
+over the installed system path) and maps family names to spec files.
+"""
+
+import os
+
+SYS_SCHEMA_DIR='/usr/share/ynl'
+RELATIVE_SCHEMA_DIR='../../../../../Documentation/netlink'
+
+
+def schema_dir():
+    """
+    Return the effective schema directory, preferring in-tree before
+    system schema directory.
+    """
+    script_dir = os.path.dirname(os.path.abspath(__file__))
+    schema_dir_ = os.path.abspath(f"{script_dir}/{RELATIVE_SCHEMA_DIR}")
+    if not os.path.isdir(schema_dir_):
+        schema_dir_ = SYS_SCHEMA_DIR
+    if not os.path.isdir(schema_dir_):
+        raise FileNotFoundError(f"Schema directory {schema_dir_} does not exist")
+    return schema_dir_
+
+def spec_dir():
+    """
+    Return the effective spec directory, relative to the effective
+    schema directory.
+    """
+    spec_dir_ = schema_dir() + '/specs'
+    if not os.path.isdir(spec_dir_):
+        raise FileNotFoundError(f"Spec directory {spec_dir_} does not exist")
+    return spec_dir_
+
+
+def find_spec(family):
+    """ Return the path to the YAML spec file for a family by name. """
+    spec = f"{spec_dir()}/{family}.yaml"
+    if not os.path.isfile(spec):
+        raise FileNotFoundError(f"Spec for family '{family}' not found at {spec}")
+    return spec
+
+
+def list_families():
+    """ Return the sorted names of all families with an installed spec. """
+    return sorted(f.removesuffix('.yaml')
+                  for f in os.listdir(spec_dir()) if f.endswith('.yaml'))
diff --git a/tools/net/ynl/pyynl/lib/ynl.py b/tools/net/ynl/pyynl/lib/ynl.py
index 092d132edec12f..8682bf588e1fa0 100644
--- a/tools/net/ynl/pyynl/lib/ynl.py
+++ b/tools/net/ynl/pyynl/lib/ynl.py
@@ -661,6 +661,14 @@ class YnlFamily(SpecFamily):
     """
     YNL family -- a Netlink interface built from a YAML spec.
 
+    The spec can be selected either by file path (def_path=) or, when it
+    ships in a well-known location, by family name (family="xyz"); exactly
+    one of the two must be given. For example:
+
+      from pyynl import YnlFamily
+
+      ynl = YnlFamily(family="netdev")
+
     Primary use of the class is to execute Netlink commands:
 
       ynl.<op_name>(attrs, ...)
@@ -691,11 +699,16 @@ class YnlFamily(SpecFamily):
 
       ynl.get_policy(op_name, mode)      -- query kernel policy for an op
     """
-    def __init__(self, def_path, schema=None, process_unknown=False,
-                 recv_size=0):
-        super().__init__(def_path, schema)
+    def __init__(self, def_path=None, schema=None, process_unknown=None,
+                 recv_size=0, family=None):
+        super().__init__(def_path, schema, family=family)
 
         self.include_raw = False
+        # Specs from /usr (selected by family=) have a higher chance of being
+        # stale, default to ignoring unknown attrs. In-tree users, and users
+        # who bundle the spec need to make a conscious decision.
+        if process_unknown is None:
+            process_unknown = family is not None
         self.process_unknown = process_unknown
 
         try:
diff --git a/tools/net/ynl/tests/ethtool.py b/tools/net/ynl/tests/ethtool.py
index db3b62c652e77f..0ee0c8e87686be 100755
--- a/tools/net/ynl/tests/ethtool.py
+++ b/tools/net/ynl/tests/ethtool.py
@@ -11,12 +11,10 @@ import pathlib
 import pprint
 import sys
 import re
-import os
 
 # pylint: disable=no-name-in-module,wrong-import-position
 sys.path.append(pathlib.Path(__file__).resolve().parent.parent.joinpath('pyynl').as_posix())
 # pylint: disable=import-error
-from cli import schema_dir, spec_dir
 from lib import YnlFamily
 
 
@@ -173,10 +171,7 @@ def main():
 
     args = parser.parse_args()
 
-    spec = os.path.join(spec_dir(), 'ethtool.yaml')
-    schema = os.path.join(schema_dir(), 'genetlink-legacy.yaml')
-
-    ynl = YnlFamily(spec, schema, recv_size=args.dbg_small_recv)
+    ynl = YnlFamily(family='ethtool', recv_size=args.dbg_small_recv)
     if args.dbg_small_recv:
         ynl.set_recv_dbg(True)
 

3/2 2026/07/01 07:29 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 5610
							output: 63
							thoughts: 339
						

Thoughts:
**WorthFuzzing Assessment: Negative**

Okay, let's break this down. This patch is touching Python code within `tools/net/ynl/pyynl/`. I recognize this as the user-space YAML Netlink library (YNL), which makes it clear this is a tooling layer for interacting with Netlink. Good, good. It's confined to `tools/`, which means this is **not** modifying any core kernel code, drivers, or architectures. The code itself is python scripts, specifically within a tool directory. 

Let me double check the files that have been modified: `__init__.py`, `cli.py`, the `lib` directory's `__init__.py`, `nlspec.py`, `specdir.py`, `ynl.py`, and finally a test file, `ethtool.py`. All are within that specified directory structure. No kernel headers, no kernel modules, nothing that directly impacts kernel execution.

Therefore, because this is all within the user-space `tools/` directory and it is only modifications to Python scripts, there are no changes here that would necessitate fuzzing the kernel. It's a non-issue from a functional standpoint.

So, WorthFuzzing is definitively `false`. No reason to even look at the focus symbols or enable configurations. The reasoning is clear: pure user-space tooling.



4/2 2026/07/01 07:29 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [],
  "Reasoning": "The patch only modifies user-space Python tooling for Netlink (YNL) in tools/net/ynl/ and does not change any kernel code.",
  "WorthFuzzing": false
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch only modifies user-space Python tooling for Netlink (YNL) in tools/net/ynl/ and does not change any kernel code.",
  "WorthFuzzing": false
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)