falcosecurity / libs

libsinsp, libscap, the kernel module driver, and the eBPF driver sources
https://falcosecurity.github.io/libs/
Apache License 2.0
216 stars 160 forks source link

Syscalls of interest might end up excluding all syscalls #162

Closed mstemm closed 2 years ago

mstemm commented 2 years ago

I think there is a bug, or at least some unintended behavior, associated with those changes. The bug is that certain code paths which are trying to get the previous/default behavior (track all system calls), fail to do so.

The source of the bug involves three sections of code:

1) scap.c, function scap_open_live_int()

Look at this code, which populates the handle->syscalls_of_interest[] table:

166 scap_t* scap_open_live_int(char *error, int32_t *rc,
167                            proc_entry_callback proc_callback,
168                            void* proc_callback_context,
169                            bool import_users,
170                            const char *bpf_probe,
171                            const char **suppressed_comms,
172                            interesting_ppm_sc_set *ppm_sc_of_interest)
173 {
...

343         if (ppm_sc_of_interest)
344         {
345                 for (int i = 0; i < PPM_SC_MAX; i++)
346                 {
347                         // We need to convert from PPM_SC to SYSCALL_NR, using the routing table
348                         for(int syscall_nr = 0; syscall_nr < SYSCALL_TABLE_SIZE; syscall_nr++)
349                         {
350                                 // Find the match between the ppm_sc and the syscall_nr
351                                 if(g_syscall_code_routing_table[syscall_nr] == i)
352                                 {
353                                         // UF_NEVER_DROP syscalls must be always traced
354                                         if (ppm_sc_of_interest->ppm_sc[i] || g_syscall_table[syscall_nr].flags & UF_NEVER_DROP)
355                                         {
356                                                 handle->syscalls_of_interest[syscall_nr] = true;
357                                         }
358                                         break;
359                                 }
360                         }
361                 }
362         }
363         else
364         {
365                 // fallback to trace all syscalls
366                 for (int i = 0; i < SYSCALL_TABLE_SIZE; i++)
367                 {
368                         handle->syscalls_of_interest[i] = true;
369                 }
370         }

If the caller specifies a NULL pointer for ppm_sc_of_interest, then the code sets ALL elements of handle->syscalls_of_interest[] to TRUE (lines 365-369), thus preserving the original default behavior of tracking all system calls.

But, if the caller specifies a non-NULL pointer, then code examines ppm_sc_of_interest AND the g_syscall_code_routing_table[] (lines 345-361) to figure out which elements of handle->syscalls_of_interest[] to populate.

2) syscall_table.c, syscall_code_routing_table[] definition

The g_syscall_code_routing_table[] DOES NOT contain any entry for the execve or umount2 system calls. So, anytime scap_open_live_int() lines 345-361 runs, to build the handle->syscalls_of_interest[] based on the caller-specified ppm_sc_of_interest, it NEVER sets syscalls_of_interest entry for those system calls.

3) sinsp.cpp, function fill_syscalls_of_interest()

If no specific set of system calls is specified, the logic wants to request the default behavior -- tracking all system calls. It TRIES to achieve this by populating oargs->ppm_sc_of_interest with all 1s:

442 void sinsp::fill_syscalls_of_interest(scap_open_args *oargs)
443 {
444         // Fallback to set all events as interesting
445         if (m_mode != SCAP_MODE_LIVE  || m_ppm_sc_of_interest.empty())
446         {
447                 for(int i = 0; i < PPM_SC_MAX; i++)
448                 {
449                         m_ppm_sc_of_interest.insert(i);
450                 }
451         }
...
467
468         // Finally, set scap_open_args syscalls_of_interest
469         for (int i = 0; i < PPM_SC_MAX; i++)
470         {
471                 oargs->ppm_sc_of_interest.ppm_sc[i] = m_ppm_sc_of_interest.find(i) != m_ppm_sc_of_interest.end();
472         }
473 }

But, as described under above, what ends up happening is, execve and umount ARE not set in the handle's syscalls_of_interest[], and thus are not captured.

Here's a localized workaround that I implemented, in scap_open_live_int(), to work around this issue:

FedeDP commented 2 years ago

I'd like to understand why we have no routing table entry for execve and umount; moreover, there is even no PPM_SC_EXECVE.

Btw thanks for catching this! Another solution would be to let sinsp::fill_syscalls_of_interest() set NULL oargs->ppm_sc_of_interest pointer when no m_simpleconsumer is set; i think this is cleaner; but, again, i'd like to understand why those syscalls are missing from routing table in the first place.

FedeDP commented 2 years ago

@mstemm @ldegio any idea why the routing table is missing entries for execve and umount?

Instead, PPM_SC_EXECVE does not exist.

FedeDP commented 2 years ago

A fix is proposed in #177 .

I fixed it using the cleanest solution, ie: by adding the proper routing table values.