On 21/09/2024 12:08, 胡义臻 wrote:
I'm renmingshuai's successor, a beginner with dnsmasq, and this e-mail is follow-up to

https://lists.thekelleys.org.uk/pipermail/dnsmasq-discuss/2024q3/017664.html 
<https://lists.thekelleys.org.uk/pipermail/dnsmasq-discuss/2024q3/017664.html>

Question 1: Why does the dhcp_reply function add the stack variable netid to daemon->dhcp_conf->netid->list, which is a global variable?

dhcp_reply

if (config)

{

struct dhcp_netid_list *list;

for (list = config->netid; list; list = list->next)

{

list->list->next = netid;

netid = list->list;

}

}

}

When we find config, 'list->list->next = netid;' add the stack variable netid to the end of the linked list daemon->dhcp_conf->netid->list. According to my understanding, the code is intended to add the first node of config->netid->list to the head of netid. The purpose of 'list->list->next = netid;' is to cache netid so that after 'netid = list->list;' the new netid->next is the original netid. Do I understand that right?

If I understand this correctly, the issue is that we add the stack variable (for example, netid = &iface_id;, the &iface_id is a stack address) to daemon->dhcp_conf->netid->list, and when dnsmasq receives the SIGHUP signal, 'clear_dynamic_conf' will free the memory of daemon->dhcp_conf->netid. In this case, daemon->dhcp_conf->netid in stack space is freed as a pointer. If the pointer is not an address returned by malloc, a bad-free error may occur, resulting in a segmentation fault.

The coredump is as follows:

[New LWP 2925451]

[Thread debugging using libthread_db enabled]

Using host libthread_db library "/usr/lib64/libthread_db.so.1".

Core was generated by `dnsmasq --no-hosts --no-resolv --pid-file=/var/lib/neutron/dhcp/d7435322-bf42-4'.

Program terminated with signal SIGSEGV, Segmentation fault.

#0 0x0000151e5168b0be in free () from /usr/lib64/libc.so.6

(gdb) bt

#0 0x0000151e5168b0be in free () from /usr/lib64/libc.so.6

#1 0x00005651025a08f0 in dhcp_netid_free (nid=0xc08b255501ae7f00) at option.c:1022

#2 dhcp_netid_list_free (netid=0x0) at option.c:1053

#3 dhcp_config_free (config=0x565104124bc0) at option.c:1071

#4 0x00005651025aa011 in clear_dynamic_conf () at option.c:5204

#5 reread_dhcp () at option.c:5245

#6 0x00005651025b2c53 in clear_cache_and_reload (now=1722067132) at dnsmasq.c:1699

#7 0x00005651025960ac in async_event (now=1722067132, pipe=15) at dnsmasq.c:1449

#8 main (argc=<optimized out>, argv=<optimized out>) at dnsmasq.c:1192

(gdb)

Question 2: Why does the dhcp_reply function use two netid linked lists: netid and tagif_netid? Can't we just use one?

Through code review, my understanding is as follows: netid stores various tags. tagif_netid matches the content of the --tag-if option with the tag saved in netid. If the matching is successful, the tag in 'set:' is added and returned to tagif_netid. But why divide it into two linked lists? Can't I directly return it to netid?

The reason I'm asking this question is, I want to put all the similar

list->list->next = netid;

netid = list->list;

change to

netid = dhcp_netid_create(list->list->net, netid);

The purpose of this is to avoid adding the stack variable netid to any global variable. Then call

dhcp_netid_free(netid);

before the function return to free all the memory that has been malloced.(I plan to change return to goto to avoid manually freeing memory at each return)

If both the netid and tagif_netid exist, the free logic is complex.

If question one is indeed a problem, is the above modification reasonable? Is there a better modification?

Question 3: Are there any common test cases for dnsmasq?

I found this test case on github, but it covered so little that I wanted to find a comprehensive use case to ensure that my changes were correct and did not introduce problems.

https://github.com/InfrastructureServices/dnsmasq-tests <https://github.com/InfrastructureServices/dnsmasq-tests>




This code is not an example to be followed. Understanding what's going on needs a bit of history.

Various dhcp configuration options can take zero or more "filter" tags and zero or more "set" tags, which are added to the set of active tags and used to filter dhcp configurations later in processing.

For instance you can filter a dhcp-host based in the network interface, and set a tag that controls which options are sent by dhcp-option

dhcp-host=tag:eth0,set:specialdns,00:11:22:33:44:55

dhcp-option=tag:specialdns,3,3.4.5.6

will send a special DNS server to MAC address 00:11:22:33:44:55 when it's connecting via eth0.

This used to be more limited: the number of "set" tags was limited to zero or one.

The "filter" tags are represented by a linked list of struct netid and the "set% tag was a single struct netid.

At run time, the dhcp code builds a linked list of all the active tags using the ->next filed of the "set".

At some point, the ability to have more than one "set" tag was added. Since the ->next filed of the netid structures was already in use as workspace, the single struct netid was replaced by a linked list of struct netid_list, each of which pointed to one struct netid.

None of this configuration could be altered at runtime, so none of this ever had to be freed.

At some point the ability to re-read configuration was added, so now old configs needed to be freed. The code to do this has a bug. dhcp_netid_list_free() follows the linked list and frees each struct netid_list and the struct netid it points to, but it frees the struct netid by calling dhcp_netid_free(), which is meant to free the immutable "filter" tags, and follows the ->next pointers. in the "set" tags the ->next pointers are dhcp processing workspace. Outside of dhcp_reply() they are not valid. Hence the crashes.

The fix is to change dhcp_netid_list_free() to

static void dhcp_netid_list_free(struct dhcp_netid_list *netid)
{
  while (netid)
    {
      struct dhcp_netid_list *tmplist = netid;
      netid = netid->next;
      /* Note: don't use dhcp_netid_free() here, since that
         frees a list linked on netid->next. Where a netid_list
         is used that's because the the ->next pointers in the
         netids are being used to temporarily construct
         a list of valid tags. */
      free(tmplist->list->net);
      free(tmplist->list);
      free(tmplist);
    }
}

I'll commit that change as soon as I've sent this.


Cheers,

Simon.





_______________________________________________
Dnsmasq-discuss mailing list
Dnsmasq-discuss@lists.thekelleys.org.uk
https://lists.thekelleys.org.uk/cgi-bin/mailman/listinfo/dnsmasq-discuss

Reply via email to