* [PATCH net-next 0/2] tools: ynl: update NULL pointer handling in generated code @ 2026-08-03 20:16 Thaison Phan 2026-08-03 20:16 ` [PATCH net-next 1/2] tools: ynl: check for null ptr on dump free Thaison Phan 2026-08-03 20:16 ` [PATCH net-next 2/2] tools: ynl: check alloc fails in generated code Thaison Phan 0 siblings, 2 replies; 8+ messages in thread From: Thaison Phan @ 2026-08-03 20:16 UTC (permalink / raw) To: Jakub Kicinski, Donald Hunter, David S . Miller , Eric Dumazet, Paolo Abeni Cc: Simon Horman, Fengyuan Gong, Stan Iliev, Asbjørn Sloth Tønnesen, Matthieu Baerts (NGI0), Thaison Phan, netdev, linux-kernel This series fixes potential NULL pointer dereferences in YNL-generated C code during dump list freeing and memory allocation parsing/setters. Testing: - Regenerated all c headers/sources under tools/net/ynl/generated/ - Built libynl, ynltool, and test binaries without errors or warnings - Ran make target run_tests in tools/net/ynl/tests successfully - Ran tools/testing/selftests/drivers/net/psp.py in QEMU x86_64 successfully Thaison Phan (2): tools: ynl: check for null ptr on dump free tools: ynl: check malloc/calloc fails in generated code tools/net/ynl/pyynl/ynl_gen_c.py | 55 ++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 17 deletions(-) -- 2.55.0.571.g244d577d93-goog ^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH net-next 1/2] tools: ynl: check for null ptr on dump free 2026-08-03 20:16 [PATCH net-next 0/2] tools: ynl: update NULL pointer handling in generated code Thaison Phan @ 2026-08-03 20:16 ` Thaison Phan 2026-08-04 21:25 ` Jakub Kicinski 2026-08-03 20:16 ` [PATCH net-next 2/2] tools: ynl: check alloc fails in generated code Thaison Phan 1 sibling, 1 reply; 8+ messages in thread From: Thaison Phan @ 2026-08-03 20:16 UTC (permalink / raw) To: Jakub Kicinski, Donald Hunter, David S . Miller , Eric Dumazet, Paolo Abeni Cc: Simon Horman, Fengyuan Gong, Stan Iliev, Asbjørn Sloth Tønnesen, Matthieu Baerts (NGI0), Thaison Phan, netdev, linux-kernel Static analysis detected code paths where freeing a dump list after early errors when creating the corresponding dump list like in ynl_exec_dump() can result in a null pointer dereference since the first node in the ynl_dump_state would still be zero initialized. To prevent this potential problem updated the ynl c generation script to check for a NULL pointer before continuing to free the nodes in a dump list. Signed-off-by: Thaison Phan <thaisonphan@google.com> --- tools/net/ynl/pyynl/ynl_gen_c.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/net/ynl/pyynl/ynl_gen_c.py b/tools/net/ynl/pyynl/ynl_gen_c.py index cdc3646f2642..2fd68c738075 100755 --- a/tools/net/ynl/pyynl/ynl_gen_c.py +++ b/tools/net/ynl/pyynl/ynl_gen_c.py @@ -2747,7 +2747,7 @@ def print_dump_type_free(ri): ri.cw.block_start() ri.cw.p(f"{sub_type} *next = rsp;") ri.cw.nl() - ri.cw.block_start(line='while ((void *)next != YNL_LIST_END)') + ri.cw.block_start(line='while (next && (void *)next != YNL_LIST_END)') _free_type_members_iter(ri, ri.struct['reply']) ri.cw.p('rsp = next;') ri.cw.p('next = rsp->next;') -- 2.55.0.571.g244d577d93-goog ^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH net-next 1/2] tools: ynl: check for null ptr on dump free 2026-08-03 20:16 ` [PATCH net-next 1/2] tools: ynl: check for null ptr on dump free Thaison Phan @ 2026-08-04 21:25 ` Jakub Kicinski 2026-08-05 13:14 ` Thaison Phan 0 siblings, 1 reply; 8+ messages in thread From: Jakub Kicinski @ 2026-08-04 21:25 UTC (permalink / raw) To: Thaison Phan Cc: Donald Hunter, David S . Miller , Eric Dumazet, Paolo Abeni, Simon Horman, Fengyuan Gong, Stan Iliev, Asbjørn Sloth Tønnesen, Matthieu Baerts (NGI0), netdev, linux-kernel On Mon, 3 Aug 2026 20:16:51 +0000 Thaison Phan wrote: > Static analysis What's the tool? I guess we can do this but I'd factor it out to a separate check at the start of the function. Otherwise it reads as if there could be a NULL on the list itself. FWIW NULL is not an empty list, it's a dump error. So the static tool / LLM is definitely wrong. But I guess free(NULL) is supposed to be a noop in general so whatever. ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH net-next 1/2] tools: ynl: check for null ptr on dump free 2026-08-04 21:25 ` Jakub Kicinski @ 2026-08-05 13:14 ` Thaison Phan 2026-08-06 0:07 ` Jakub Kicinski 0 siblings, 1 reply; 8+ messages in thread From: Thaison Phan @ 2026-08-05 13:14 UTC (permalink / raw) To: kuba Cc: ast, davem, donald.hunter, edumazet, gfengyuan, horms, linux-kernel, matttbe, netdev, pabeni, stani, thaisonphan Thanks for looking through this. > What's the tool? Sashiko (LLM) reported the issue. The path it reported was based on code output by print_dump() where since the dump state is zero initialized struct ynl_dump_state yds = {}, yds.first would be NULL. It then pointed out that in the call to ynl_exec_dump() that there could be a failure in ynl_msg_end() or send() that would result in `yds.first` staying as NULL, and the code going to the free_list label where the dump list free would get passed NULL. I may be misunderstanding the code, but it seemed legitimate. > I guess we can do this but I'd factor it out to a separate check at the > start of the function. Otherwise it reads as if there could be a NULL on > the list itself. Factoring out the NULL to be a separate check makes more sense to me and better shows the intent of the check. I'll update that in v2. Thanks, Thaison ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH net-next 1/2] tools: ynl: check for null ptr on dump free 2026-08-05 13:14 ` Thaison Phan @ 2026-08-06 0:07 ` Jakub Kicinski 0 siblings, 0 replies; 8+ messages in thread From: Jakub Kicinski @ 2026-08-06 0:07 UTC (permalink / raw) To: Thaison Phan Cc: ast, davem, donald.hunter, edumazet, gfengyuan, horms, linux-kernel, matttbe, netdev, pabeni, stani On Wed, 5 Aug 2026 13:14:54 +0000 Thaison Phan wrote: > Thanks for looking through this. > > > What's the tool? > > Sashiko (LLM) reported the issue. The path it reported was based on code > output by print_dump() where since the dump state is zero initialized > struct ynl_dump_state yds = {}, yds.first would be NULL. It then pointed > out that in the call to ynl_exec_dump() that there could be a failure in > ynl_msg_end() or send() that would result in `yds.first` staying as NULL, > and the code going to the free_list label where the dump list free would > get passed NULL. I may be misunderstanding the code, but it seemed > legitimate. I see it now, you're right. Plumbing the END markers in the list construction would be a PITA. > > I guess we can do this but I'd factor it out to a separate check at the > > start of the function. Otherwise it reads as if there could be a NULL on > > the list itself. > > Factoring out the NULL to be a separate check makes more sense to me and > better shows the intent of the check. I'll update that in v2. > > Thanks, > Thaison ^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH net-next 2/2] tools: ynl: check alloc fails in generated code 2026-08-03 20:16 [PATCH net-next 0/2] tools: ynl: update NULL pointer handling in generated code Thaison Phan 2026-08-03 20:16 ` [PATCH net-next 1/2] tools: ynl: check for null ptr on dump free Thaison Phan @ 2026-08-03 20:16 ` Thaison Phan 2026-08-04 21:33 ` Jakub Kicinski 1 sibling, 1 reply; 8+ messages in thread From: Thaison Phan @ 2026-08-03 20:16 UTC (permalink / raw) To: Jakub Kicinski, Donald Hunter, David S . Miller , Eric Dumazet, Paolo Abeni Cc: Simon Horman, Fengyuan Gong, Stan Iliev, Asbjørn Sloth Tønnesen, Matthieu Baerts (NGI0), Thaison Phan, netdev, linux-kernel Generated YNL code does not check the return value of malloc() and calloc() before passing the resulting pointer to memcpy(). This could lead to a NULL pointer dereference on memory allocation failure. Updated the C code generator to check for allocation failures and to return an error code when applicable, or to return with no error code for a user to check for a NULL in the field that allocation was attempted for. Signed-off-by: Thaison Phan <thaisonphan@google.com> --- tools/net/ynl/pyynl/ynl_gen_c.py | 53 ++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/tools/net/ynl/pyynl/ynl_gen_c.py b/tools/net/ynl/pyynl/ynl_gen_c.py index 2fd68c738075..a323acdc42ba 100755 --- a/tools/net/ynl/pyynl/ynl_gen_c.py +++ b/tools/net/ynl/pyynl/ynl_gen_c.py @@ -526,16 +526,20 @@ class TypeString(Type): def _attr_get(self, ri, var): len_mem = var + '->_len.' + self.c_name - return [f"{len_mem} = len;", - f"{var}->{self.c_name} = malloc(len + 1);", + return [f"{var}->{self.c_name} = malloc(len + 1);", + f"if (!{var}->{self.c_name})", + "return YNL_PARSE_CB_ERROR;", + f"{len_mem} = len;", f"memcpy({var}->{self.c_name}, ynl_attr_get_str(attr), len);", f"{var}->{self.c_name}[len] = 0;"], \ ['len = strnlen(ynl_attr_get_str(attr), ynl_attr_data_len(attr));'], \ ['unsigned int len;'] def _setter_lines(self, ri, member, presence): - return [f"{presence} = strlen({self.c_name});", - f"{member} = malloc({presence} + 1);", + return [f"{member} = malloc(strlen({self.c_name}) + 1);", + f"if (!{member})", + "return;", + f"{presence} = strlen({self.c_name});", f'memcpy({member}, {self.c_name}, {presence});', f'{member}[{presence}] = 0;'] @@ -582,15 +586,19 @@ class TypeBinary(Type): def _attr_get(self, ri, var): len_mem = var + '->_len.' + self.c_name - return [f"{len_mem} = len;", - f"{var}->{self.c_name} = malloc(len);", + return [f"{var}->{self.c_name} = malloc(len);", + f"if (!{var}->{self.c_name})", + "return YNL_PARSE_CB_ERROR;", + f"{len_mem} = len;", f"memcpy({var}->{self.c_name}, ynl_attr_data(attr), len);"], \ ['len = ynl_attr_data_len(attr);'], \ ['unsigned int len;'] def _setter_lines(self, ri, member, presence): - return [f"{presence} = len;", - f"{member} = malloc({presence});", + return [f"{member} = malloc(len);", + f"if (!{member})", + "return;", + f"{presence} = len;", f'memcpy({member}, {self.c_name}, {presence});'] @@ -601,11 +609,13 @@ class TypeBinaryStruct(TypeBinary): def _attr_get(self, ri, var): struct_sz = 'sizeof(struct ' + c_lower(self.get("struct")) + ')' len_mem = var + '->_' + self.presence_type() + '.' + self.c_name - return [f"{len_mem} = len;", - f"if (len < {struct_sz})", + return [f"if (len < {struct_sz})", f"{var}->{self.c_name} = calloc(1, {struct_sz});", "else", f"{var}->{self.c_name} = malloc(len);", + f"if (!{var}->{self.c_name})", + "return YNL_PARSE_CB_ERROR;", + f"{len_mem} = len;", f"memcpy({var}->{self.c_name}, ynl_attr_data(attr), len);"], \ ['len = ynl_attr_data_len(attr);'], \ ['unsigned int len;'] @@ -631,18 +641,21 @@ class TypeBinaryScalarArray(TypeBinary): def _attr_get(self, ri, var): len_mem = var + '->_count.' + self.c_name - return [f"{len_mem} = len / sizeof(__{self.get('sub-type')});", - f"len = {len_mem} * sizeof(__{self.get('sub-type')});", + return [f"len = (len / sizeof(__{self.get('sub-type')})) * sizeof(__{self.get('sub-type')});", f"{var}->{self.c_name} = malloc(len);", + f"if (!{var}->{self.c_name})", + "return YNL_PARSE_CB_ERROR;", + f"{len_mem} = len / sizeof(__{self.get('sub-type')});", f"memcpy({var}->{self.c_name}, ynl_attr_data(attr), len);"], \ ['len = ynl_attr_data_len(attr);'], \ ['unsigned int len;'] def _setter_lines(self, ri, member, presence): - return [f"{presence} = count;", - f"count *= sizeof(__{self.get('sub-type')});", - f"{member} = malloc(count);", - f'memcpy({member}, {self.c_name}, count);'] + return [f"{member} = malloc(count * sizeof(__{self.get('sub-type')}));", + f"if (!{member})", + "return;", + f"{presence} = count;", + f'memcpy({member}, {self.c_name}, count * sizeof(__{self.get("sub-type")}));'] class TypeBitfield32(Type): @@ -2227,6 +2240,8 @@ def _multi_parse(ri, struct, init_lines, local_vars): ri.cw.block_start(line=f"if (n_{aspec.c_name})") ri.cw.p(f"dst->{aspec.c_name} = calloc(n_{aspec.c_name}, sizeof(*dst->{aspec.c_name}));") + ri.cw.p(f"if (!dst->{aspec.c_name})") + ri.cw.p("return YNL_PARSE_CB_ERROR;") ri.cw.p(f"dst->_count.{aspec.c_name} = n_{aspec.c_name};") ri.cw.p('i = 0;') if 'nested-attributes' in aspec: @@ -2252,6 +2267,8 @@ def _multi_parse(ri, struct, init_lines, local_vars): aspec = struct[arg] ri.cw.block_start(line=f"if (n_{aspec.c_name})") ri.cw.p(f"dst->{aspec.c_name} = calloc(n_{aspec.c_name}, sizeof(*dst->{aspec.c_name}));") + ri.cw.p(f"if (!dst->{aspec.c_name})") + ri.cw.p("return YNL_PARSE_CB_ERROR;") ri.cw.p(f"dst->_count.{aspec.c_name} = n_{aspec.c_name};") ri.cw.p('i = 0;') if 'nested-attributes' in aspec: @@ -2275,6 +2292,8 @@ def _multi_parse(ri, struct, init_lines, local_vars): ri.cw.nl() ri.cw.p('len = strnlen(ynl_attr_get_str(attr), ynl_attr_data_len(attr));') ri.cw.p(f'dst->{aspec.c_name}[i] = malloc(sizeof(struct ynl_string) + len + 1);') + ri.cw.p(f"if (!dst->{aspec.c_name}[i])") + ri.cw.p("return YNL_PARSE_CB_ERROR;") ri.cw.p(f"dst->{aspec.c_name}[i]->len = len;") ri.cw.p(f"memcpy(dst->{aspec.c_name}[i]->str, ynl_attr_get_str(attr), len);") ri.cw.p(f"dst->{aspec.c_name}[i]->str[len] = 0;") @@ -2434,6 +2453,8 @@ def print_req(ri): if 'reply' in ri.op[ri.op_mode]: ri.cw.p('rsp = calloc(1, sizeof(*rsp));') + ri.cw.p('if (!rsp)') + ri.cw.p(f'return {ret_err};') ri.cw.p('yrs.yarg.data = rsp;') ri.cw.p(f"yrs.cb = {op_prefix(ri, 'reply')}_parse;") if ri.op.value is not None: -- 2.55.0.571.g244d577d93-goog ^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH net-next 2/2] tools: ynl: check alloc fails in generated code 2026-08-03 20:16 ` [PATCH net-next 2/2] tools: ynl: check alloc fails in generated code Thaison Phan @ 2026-08-04 21:33 ` Jakub Kicinski 2026-08-05 13:42 ` Thaison Phan 0 siblings, 1 reply; 8+ messages in thread From: Jakub Kicinski @ 2026-08-04 21:33 UTC (permalink / raw) To: Thaison Phan Cc: Donald Hunter, David S . Miller , Eric Dumazet, Paolo Abeni, Simon Horman, Fengyuan Gong, Stan Iliev, Asbjørn Sloth Tønnesen, Matthieu Baerts (NGI0), netdev, linux-kernel On Mon, 3 Aug 2026 20:16:52 +0000 Thaison Phan wrote: > def _setter_lines(self, ri, member, presence): > - return [f"{presence} = len;", > - f"{member} = malloc({presence});", > + return [f"{member} = malloc(len);", > + f"if (!{member})", > + "return;", > + f"{presence} = len;", > f'memcpy({member}, {self.c_name}, {presence});'] Silently eating errors in setters does not seem great, better to crash and make it clear than things are not working than have a silently misconfigured system? AI also points out that setters are preceded by a free (in case we're replacing existing attr, multiple sets on a single attr are allowed) So if alloc fails we'll end up with NULL ptr and len from previous allocation. Maybe tackle the setters in a separate patch, and keep patch 2 scoped to the easier case? -- pw-bot: cr ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH net-next 2/2] tools: ynl: check alloc fails in generated code 2026-08-04 21:33 ` Jakub Kicinski @ 2026-08-05 13:42 ` Thaison Phan 0 siblings, 0 replies; 8+ messages in thread From: Thaison Phan @ 2026-08-05 13:42 UTC (permalink / raw) To: kuba Cc: ast, davem, donald.hunter, edumazet, gfengyuan, horms, linux-kernel, matttbe, netdev, pabeni, stani, thaisonphan > Silently eating errors in setters does not seem great, better to crash > and make it clear than things are not working than have a silently > misconfigured system? I was thinking that the user could check for NULL in the struct field that the setter was allocating for to see if something went wrong, but that is probably not ideal either since a user might assume with the void return there can be no errors, and I didn't realize the field is not guaranteed to be NULL when it is passed in. > AI also points out that setters are preceded by a free (in case we're > replacing existing attr, multiple sets on a single attr are allowed) So > if alloc fails we'll end up with NULL ptr and len from previous > allocation. That makes sense, so we could have an unreported free() on the key someone using the setter might think is still allocated. We could maybe delay the free() until after the malloc() works, and maybe we could return an error code in setters that allocate to let users know something went wrong. > Maybe tackle the setters in a separate patch, and keep patch 2 scoped > to the easier case? That sounds like a good idea to me. I'll make v2 only tackle the getters and look into a different way to handle the setters. Thanks, Thaison ^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-08-06 0:07 UTC | newest] Thread overview: 8+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-08-03 20:16 [PATCH net-next 0/2] tools: ynl: update NULL pointer handling in generated code Thaison Phan 2026-08-03 20:16 ` [PATCH net-next 1/2] tools: ynl: check for null ptr on dump free Thaison Phan 2026-08-04 21:25 ` Jakub Kicinski 2026-08-05 13:14 ` Thaison Phan 2026-08-06 0:07 ` Jakub Kicinski 2026-08-03 20:16 ` [PATCH net-next 2/2] tools: ynl: check alloc fails in generated code Thaison Phan 2026-08-04 21:33 ` Jakub Kicinski 2026-08-05 13:42 ` Thaison Phan
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).