lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-Id: <20230615151336.77589-1-donald.hunter@gmail.com>
Date: Thu, 15 Jun 2023 16:13:36 +0100
From: Donald Hunter <donald.hunter@...il.com>
To: netdev@...r.kernel.org,
	Jakub Kicinski <kuba@...nel.org>,
	"David S. Miller" <davem@...emloft.net>,
	Eric Dumazet <edumazet@...gle.com>,
	Paolo Abeni <pabeni@...hat.com>
Cc: donald.hunter@...hat.com,
	Donald Hunter <donald.hunter@...il.com>
Subject: [RFC net-next v1] tools: ynl: Add an strace rendering mode to ynl-gen

Add --mode strace to ynl-gen-c.py to generate source files for strace
that teach it to understand how to decode genetlink messages defined
in the spec. I successfully used this to add openvswitch message
decoding to strace as I described in:

https://donaldh.wtf/2023/06/teaching-strace-new-tricks/

It successfully generated ovs_datapath and ovs_vport but ovs_flow
needed manual fixes to fix code ordering and forward declarations.

Limitations:

- Uses a crude mechanism to try and emit functions in the right order
  which fails for ovs_flow
- Outputs all strace sources to stdout or a single file
- Does not use the right semantic strace decoders for e.g. IP or MAC
  addresses because there is no schema information to say what the
  domain type is.

This seems like a useful tool to have as part of the ynl suite since
it lowers the cost of getting good strace support for new netlink
families. But I realise that the generated format is dependent on an
out of tree project. If there is interest in having this in-tree then
I can clean it up and address some of the limitations before
submission.

Signed-off-by: Donald Hunter <donald.hunter@...il.com>
---
 tools/net/ynl/ynl-gen-c.py | 286 +++++++++++++++++++++++++++++++++++++
 1 file changed, 286 insertions(+)

diff --git a/tools/net/ynl/ynl-gen-c.py b/tools/net/ynl/ynl-gen-c.py
index 71c5e79e877f..efd87d8463ed 100755
--- a/tools/net/ynl/ynl-gen-c.py
+++ b/tools/net/ynl/ynl-gen-c.py
@@ -2268,6 +2268,288 @@ def render_user_family(family, cw, prototype):
     cw.block_end(line=';')
 
 
+def render_strace(family, cw):
+
+    xlat_headers = []
+
+    # xlat for definitions
+
+    defines = []
+    for const in family['definitions']:
+        if const['type'] != 'const':
+            cw.writes_defines(defines)
+            defines = []
+            cw.nl()
+
+        if const['type'] == 'enum' or const['type'] == 'flags':
+            enum = family.consts[const['name']]
+
+            xlat_name = f"{family.name}_{c_lower(const['name'])}"
+            xlat_headers.append(xlat_name)
+
+            cw.p(f"// For src/xlat/{xlat_name}.in")
+            cw.p('#unconditional')
+            if const['type'] == 'enum':
+                cw.p('#value_indexed')
+
+            name_pfx = const.get('name-prefix', f"{family.name}-{const['name']}-")
+            for entry in enum.entries.values():
+                cw.p(entry.c_name)
+
+            cw.nl()
+        elif const['type'] == 'const':
+            defines.append([c_upper(family.get('c-define-name',
+                                               f"{family.name}-{const['name']}")),
+                            const['value']])
+
+    if defines:
+        cw.writes_defines(defines)
+        cw.nl()
+
+    # xlat for attrs
+
+    for _, attr_set in family.attr_sets.items():
+        if attr_set.subset_of:
+            continue
+
+        xlat_name = c_lower(attr_set.yaml['enum-name'])
+        xlat_headers.append(xlat_name)
+
+        cw.p(f"// For src/xlat/{xlat_name}.in")
+        cw.p('#unconditional')
+        cw.p('#value_indexed')
+
+        for _, attr in attr_set.items():
+            cw.p(attr.enum_name)
+        cw.nl()
+
+    # xlat for commands
+
+    separate_ntf = 'async-prefix' in family['operations']
+
+    xlat_name = f"{family.name}_cmds"
+    xlat_headers.append(xlat_name)
+
+    cw.p(f"// For src/xlat/{xlat_name}.in")
+    cw.p('#unconditional')
+    cw.p('#value_indexed')
+
+    for op in family.msgs.values():
+        if separate_ntf and ('notify' in op or 'event' in op):
+            continue
+
+        cw.p(op.enum_name)
+    cw.nl()
+
+    if separate_ntf:
+        uapi_enum_start(family, cw, family['operations'], enum_name='async-enum')
+        for op in family.msgs.values():
+            if separate_ntf and not ('notify' in op or 'event' in op):
+                continue
+
+            suffix = ','
+            if 'value' in op:
+                suffix = f" = {op['value']},"
+            cw.p(op.enum_name + suffix)
+        cw.block_end(line=';')
+        cw.nl()
+
+    cw.nl()
+    if defines:
+        cw.writes_defines(defines)
+        cw.nl()
+
+    # Bind into netlink_generic.(c|h)
+
+    cw.p('// Add to src/netlink_generic.h')
+    cw.p(f"extern DECL_NETLINK_GENERIC_DECODER(decode_{family.name}_msg);")
+    cw.nl()
+
+    cw.p('// Add to src/netlink_generic.c in genl_decoders[]')
+    cw.p(f"""\t{{ "{family.name}", decode_{family.name}_msg }},""")
+    cw.nl()
+
+    # strace Makefile
+
+    cw.p('// Add to src/Makefile.am in libstrace_a_SOURCES')
+    cw.p(f"\t{family.name}.c \\")
+    cw.nl()
+
+    # Start of C source file
+
+    cw.p(f"// For src/{family.name}.c")
+    cw.nl()
+
+    cw.p('#include "defs.h"')
+    cw.p('#include "netlink.h"')
+    cw.p('#include "nlattr.h"')
+    cw.p('#include <linux/genetlink.h>')
+    cw.p(f"#include <{family['uapi-header']}>")
+    cw.p('#include "netlink_generic.h"')
+    for h in xlat_headers:
+        cw.p(f"#include \"xlat/{h}.h\"")
+    cw.nl()
+
+    # C code for flags, enum and struct decoders
+
+    for defn in family['definitions']:
+        if defn['type'] in [ 'flags', 'enum' ]:
+            prefix = defn.get('name-prefix', f"{family.name}-{defn['name']}-")
+
+            cw.p('static bool')
+            cw.p(f"decode_{c_lower(defn['name'])}(struct tcb *const tcp,")
+            cw.p("\t\tconst kernel_ulong_t addr,")
+            cw.p("\t\tconst unsigned int len,")
+            cw.p("\t\tconst void *const opaque_data)")
+            cw.block_start()
+            cw.block_start("static const struct decode_nla_xlat_opts opts =")
+            cw.p(f"""{family.name}_{c_lower(defn['name'])}, "{c_upper(prefix)}???", .size = 4""")
+            cw.block_end(';')
+            decoder = 'xval' if defn['type'] == 'enum' else 'flags'
+            cw.p(f"return decode_nla_{decoder}(tcp, addr, len, &opts);")
+            cw.block_end()
+
+        elif defn['type'] == 'struct':
+            struct_name = c_lower(defn['enum-name'] if 'enum-name' in defn else defn['name'])
+            var_name = c_lower(defn['name'])
+
+            cw.p('static bool')
+            cw.p(f"decode_{struct_name}(struct tcb *const tcp,")
+            cw.p("\t\tconst kernel_ulong_t addr,")
+            cw.p("\t\tconst unsigned int len,")
+            cw.p("\t\tconst void *const opaque_data)")
+            cw.block_start()
+
+            cw.p(f"struct {struct_name} {var_name};")
+            cw.p(f"umove_or_printaddr(tcp, addr, &{var_name});")
+            cw.nl()
+
+            for m in defn['members']:
+                if m['name'].startswith('pad'):
+                    continue
+                cw.p(f"PRINT_FIELD_U({var_name}, {c_lower(m['name'])});")
+                cw.p('tprint_struct_next();')
+
+            cw.p('return true;')
+            cw.block_end()
+
+        cw.nl()
+
+    # C code for attibute set decoders
+
+    for _, attr_set in family.attr_sets.items():
+        if attr_set.subset_of:
+            continue
+
+        # Emit nested attr decoders before referencing them
+
+        for _, attr in attr_set.items():
+            if type(attr) in [ TypeNest, TypeArrayNest ]:
+                decoder = f"decode_{c_lower(attr.enum_name)}"
+                nested_set = family.attr_sets[attr['nested-attributes']]
+                nested_attrs = f"{c_lower(nested_set.yaml['enum-name'])}"
+                name_prefix = nested_set.yaml.get('name-prefix',
+                                                  f"{family.name}-{nested_set.name}-")
+                attr_prefix = f"{c_upper(name_prefix)}"
+                decoder_array = f"{c_lower(nested_set.name)}_attr_decoders"
+                array_nest = "_item" if type(attr) == TypeArrayNest else ""
+
+                cw.p('static bool')
+                cw.p(f"{decoder}{array_nest}(struct tcb *const tcp,")
+                cw.p("\tconst kernel_ulong_t addr,")
+                cw.p("\tconst unsigned int len,")
+                cw.p("\tconst void *const opaque_data)")
+                cw.block_start()
+                cw.p(f"decode_nlattr(tcp, addr, len, {nested_attrs},")
+                cw.p(f"\t\"{attr_prefix}???\",")
+                cw.p(f"\tARRSZ_PAIR({decoder_array}),")
+                cw.p("\tNULL);")
+                cw.p('return true;')
+                cw.block_end()
+                cw.nl()
+
+            if type(attr) == TypeArrayNest:
+                cw.p('static bool')
+                cw.p(f"{decoder}(struct tcb *const tcp,")
+                cw.p("\tconst kernel_ulong_t addr,")
+                cw.p("\tconst unsigned int len,")
+                cw.p("\tconst void *const opaque_data)")
+                cw.block_start()
+                cw.p(f"nla_decoder_t decoder = &{decoder}_item;")
+                cw.p('decode_nlattr(tcp, addr, len, NULL, NULL, &decoder, 0, NULL);')
+                cw.p('return true;')
+                cw.block_end()
+                cw.nl()
+
+        # Then emit the decoders array
+
+        cw.block_start(f"static const nla_decoder_t {c_lower(attr_set.name)}_attr_decoders[] =")
+        for _, attr in attr_set.items():
+            if type(attr) in [ TypeUnused, TypeFlag ]:
+                decoder = 'NULL'
+            elif type(attr) == TypeString:
+                decoder = 'decode_nla_str'
+            elif type(attr) == TypeBinary:
+                decoder = 'NULL'
+                if 'struct' in attr.yaml:
+                    defn = family.consts[attr.yaml['struct']]
+                    enum_name = c_lower(defn.get('enum-name', defn.name))
+                    decoder = f"decode_{enum_name}"
+            elif type(attr) == TypeNest:
+                decoder = f"decode_{c_lower(attr.enum_name)}"
+            elif type(attr) == TypeScalar and 'enum' in attr:
+                decoder = f"decode_{c_lower(attr['enum'])}"
+            else:
+                decoder = f"decode_nla_{attr.type}"
+
+            cw.p(f"[{attr.enum_name}] = {decoder},")
+        cw.block_end(';')
+        cw.nl()
+
+    # C code for top-level decoder
+
+    for op in family.msgs.values():
+        cmd_prefix = c_upper(family.yaml['operations']['name-prefix'])
+        attr_set_name = op.yaml['attribute-set']
+        attr_set = family.attr_sets[attr_set_name]
+        name_prefix = c_upper(attr_set.yaml.get('name-prefix', attr_set_name))
+        enum_name = c_lower(attr_set.yaml['enum-name'])
+
+        cw.block_start(f"DECL_NETLINK_GENERIC_DECODER(decode_{family.name}_msg)")
+
+        if op.fixed_header:
+            defn = family.consts[op.fixed_header]
+            header_name = c_lower(defn['name'])
+            cw.p(f"struct {header_name} header;")
+            cw.p(f"size_t offset = sizeof(struct {header_name});")
+            cw.nl()
+
+        cw.p('tprint_struct_begin();')
+        cw.p(f"""PRINT_FIELD_XVAL(*genl, cmd, {family.name}_cmds, "{cmd_prefix}???");""");
+        cw.p('tprint_struct_next();')
+        cw.p('PRINT_FIELD_U(*genl, version);')
+        cw.p('tprint_struct_next();')
+
+        if op.fixed_header:
+            cw.p('if (umove_or_printaddr(tcp, addr, &header))')
+            cw.p('return;')
+            for m in defn.members:
+                cw.p(f"PRINT_FIELD_U(header, {c_lower(m.name)});")
+                cw.p('tprint_struct_next();')
+            cw.nl()
+            cw.p(f"decode_nlattr(tcp, addr + offset, len - offset,");
+        else:
+            cw.p(f"decode_nlattr(tcp, addr, len,");
+
+        cw.p(f"\t{enum_name},");
+        cw.p(f"\t\"{name_prefix}???\",");
+        cw.p(f"\tARRSZ_PAIR({c_lower(attr_set_name)}_attr_decoders),");
+        cw.p("\tNULL);")
+        cw.p('tprint_struct_end();')
+        cw.block_end()
+        break
+
+
 def find_kernel_root(full_path):
     sub_path = ''
     while True:
@@ -2335,6 +2617,10 @@ def main():
         render_uapi(parsed, cw)
         return
 
+    if args.mode == 'strace':
+        render_strace(parsed, cw)
+        return
+
     hdr_prot = f"_LINUX_{parsed.name.upper()}_GEN_H"
     if args.header:
         cw.p('#ifndef ' + hdr_prot)
-- 
2.39.0


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ