-
Notifications
You must be signed in to change notification settings - Fork 177
Description
Describe the bug
The Falco libs use the /proc filesystem to retrieve information about processes when they start up. However, if a thread, during its lifecycle, changes its name using a prctl system call, the proc.name value is not updated. This means that Falco may not be able to accurately identify the process and could lead to errors or security issues.
In the following situation Falco doesn't take into account the renaming because it's only aware of the information of the process at its creation.
Falco starts
|
|
|----malicious process---------------- -\
| | |
| | |
| | |
| | thread renaming |
| | | |
| | | |- malicious process lifecycle
| | | |
| | | |
| | | |
| | | |
| | | |
| v v |
|------------------------------------- -/
|
|
v
On the contrary, in the following situation, given that the renaming has already taken place before Falco has started, Falco will display correctly the proc.name.
----malicious process---------------- -\
| |
| |
| |
| thread renaming |
| | |
Falco starts | | |
| | | |- malicious process lifecycle
| | | |
| | | |
| | | |
| | | |
| | | |
| v v |
|------------------------------------- -/
|
|
|
v
How to reproduce it
If we consider the first situation shown above:
- Compile the following simple c program (e.g.:
gcc worker.c -o worker):
#include <stdio.h>
#include <pthread.h>
void *worker(void *arg) {
if (pthread_equal(pthread_self(), *((pthread_t*)arg))) {
FILE *fp;
char buffer[1024];
int lines = 0;
pthread_setname_np(pthread_self(), "reader");
sleep(5);
fp = fopen("/etc/passwd", "r");
if (fp == NULL) {
perror("Error opening /etc/passwd");
pthread_exit(NULL);
}
while (fgets(buffer, sizeof(buffer), fp) != NULL)
lines++;
printf("Thread-0: /etc/passwd contains %d lines\n", lines);
fclose(fp);
} else {
pthread_setname_np(pthread_self(), "otherThread");
printf("%ld started\n", pthread_self());
sleep(10);
printf("%ld finished\n", pthread_self());
}
pthread_exit(NULL);
}
int main(void) {
pthread_t threads[10];
int rc;
pthread_setname_np(pthread_self(), "MainThread");
for (int i = 0; i < 10; i++) {
rc = pthread_create(&threads[i], NULL, worker, (void *)&threads[0]);
if (rc) {
fprintf(stderr, "Error creating thread %d\n", i);
return 1;
}
}
for (int i = 0; i < 10; I++)
pthread_join(threads[i], NULL);
return 0;
}- Start Falco with the following rule that will match the thread called reader:
- rule: Read from /etc/passwd
desc: A process attempted to read from the /etc/passwd file.
condition: evt.type=read and fd.name="/etc/passwd"
output: "Process attempted to read from /etc/passwd (user=%user.name command=%proc.cmdline procname=%proc.name)"
priority: ERROR- Run the
workerprogram compiled in first step - Falco will output "MainThread" as
proc.name
If we consider the second situation shown above:
- Compile the following simple c program (e.g.:
gcc worker.c -o worker):
#include <stdio.h>
#include <pthread.h>
void *worker(void *arg) {
if (pthread_equal(pthread_self(), *((pthread_t*)arg))) {
FILE *fp;
char buffer[1024];
int lines = 0;
pthread_setname_np(pthread_self(), "reader");
sleep(5);
fp = fopen("/etc/passwd", "r");
if (fp == NULL) {
perror("Error opening /etc/passwd");
pthread_exit(NULL);
}
while (fgets(buffer, sizeof(buffer), fp) != NULL)
lines++;
printf("Thread-0: /etc/passwd contains %d lines\n", lines);
fclose(fp);
} else {
pthread_setname_np(pthread_self(), "otherThread");
printf("%ld started\n", pthread_self());
sleep(10);
printf("%ld finished\n", pthread_self());
}
pthread_exit(NULL);
}
int main(void) {
pthread_t threads[10];
int rc;
pthread_setname_np(pthread_self(), "MainThread");
for (int i = 0; i < 10; i++) {
rc = pthread_create(&threads[i], NULL, worker, (void *)&threads[0]);
if (rc) {
fprintf(stderr, "Error creating thread %d\n", i);
return 1;
}
}
for (int i = 0; i < 10; I++)
pthread_join(threads[i], NULL);
return 0;
}- Run the
workerprogram compiled in the previous step - Start Falco after the renaming with the following rule that will match the thread called reader:
- rule: Read from /etc/passwd
desc: A process attempted to read from the /etc/passwd file.
condition: evt.type=read and fd.name="/etc/passwd"
output: "Process attempted to read from /etc/passwd (user=%user.name command=%proc.cmdline procname=%proc.name)"
priority: ERROR- Falco will output "reader" as
proc.name
At the end the concept is simple: if Falco starts before the renaming, it will only use the information available at the start of the process; otherwise if Falco starts after the renaming, it will have the complete information and use the effective thread name.
Expected behaviour
Given that proc.name is the name (excluding the path) of the executable generating the event.
When a thread changes its name within a process, Falco should update the proc.name value to reflect the new thread name. This should happen regardless of whether Falco was started before or after the thread was renamed. This will ensure that Falco is correctly identifying the process and can take appropriate actions as needed.
Screenshots
Here's how Falco behaves if it starts before the matching process (first situation).
$ sudo falco -A
Tue Mar 28 10:30:10 2023: Falco version: 0.34.1 (x86_64)
Tue Mar 28 10:30:10 2023: Falco initialized with configuration file: /etc/falco/falco.yaml
Tue Mar 28 10:30:10 2023: Loading rules from file /home/ubuntu/src/read_rule.yaml
Tue Mar 28 10:30:10 2023: The chosen syscall buffer dimension is: 8388608 bytes (8 MBs)
Tue Mar 28 10:30:10 2023: gRPC server threadiness equals to 8
Tue Mar 28 10:30:10 2023: Starting health webserver with threadiness 8, listening on port 8765
Tue Mar 28 10:30:10 2023: Starting gRPC server at unix:///run/falco/falco.sock
Tue Mar 28 10:30:10 2023: Enabled event sources: syscall
Tue Mar 28 10:30:10 2023: Opening capture with Kernel module
10:30:40.852145252: Error Process attempted to read from /etc/passwd (user=ubuntu command=MainThread procname=MainThread)
10:30:40.852148051: Error Process attempted to read from /etc/passwd (user=ubuntu command=MainThread procname=MainThread)
10:30:40.852152596: Error Process attempted to read from /etc/passwd (user=ubuntu command=MainThread procname=MainThread)
10:30:40.852153490: Error Process attempted to read from /etc/passwd (user=ubuntu command=MainThread procname=MainThread)
Here's how Falco behaves if it starts after (second situation) the matching process and before the actual read (so when the process has already changed its name).
$ sudo falco -A
Tue Mar 28 10:34:48 2023: Falco version: 0.34.1 (x86_64)
Tue Mar 28 10:34:48 2023: Falco initialized with configuration file: /etc/falco/falco.yaml
Tue Mar 28 10:34:48 2023: Loading rules from file /home/ubuntu/src/read_rule.yaml
Tue Mar 28 10:34:48 2023: The chosen syscall buffer dimension is: 8388608 bytes (8 MBs)
Tue Mar 28 10:34:48 2023: gRPC server threadiness equals to 8
Tue Mar 28 10:34:48 2023: Starting health webserver with threadiness 8, listening on port 8765
Tue Mar 28 10:34:48 2023: Enabled event sources: syscall
Tue Mar 28 10:34:48 2023: Starting gRPC server at unix:///run/falco/falco.sock
Tue Mar 28 10:34:48 2023: Opening capture with Kernel module
10:34:51.958366528: Error Process attempted to read from /etc/passwd (user=ubuntu command=reader procname=reader)
10:34:51.958370300: Error Process attempted to read from /etc/passwd (user=ubuntu command=reader procname=reader)
10:34:51.958373326: Error Process attempted to read from /etc/passwd (user=ubuntu command=reader procname=reader)
10:34:51.958373935: Error Process attempted to read from /etc/passwd (user=ubuntu command=reader procname=reader)
Here's the output of the ps command after the renaming:
$ ps -T -o pid,tid,comm p 2155073
PID TID COMMAND
2155073 2155073 MainThread
2155073 2155074 reader
2155073 2155075 otherThread
2155073 2155076 otherThread
2155073 2155077 otherThread
2155073 2155078 otherThread
2155073 2155079 otherThread
2155073 2155080 otherThread
2155073 2155081 otherThread
2155073 2155082 otherThread
2155073 2155083 otherThread
Environment
- Falco version:
Falco version: 0.34.1
Libs version: 0.10.4
Plugin API: 2.0.0
Engine: 16
Driver:
API version: 3.0.0
Schema version: 2.0.0
Default driver: 4.0.0+driver
- System info:
{
"machine": "x86_64",
"nodename": "ip-172-31-37-210",
"release": "5.15.0-1031-aws",
"sysname": "Linux",
"version": "#35-Ubuntu SMP Fri Feb 10 02:07:18 UTC 2023"
}
- Cloud provider or hardware configuration: ec2 instance
- OS:
PRETTY_NAME="Ubuntu 22.04.2 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.2 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy
- Kernel:
Linux ip-172-31-37-210 5.15.0-1031-aws #35-Ubuntu SMP Fri Feb 10 02:07:18 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
- Installation method:
DEB
Additional context
I think that the problem is linked to the fact that a thread is added to the thread table via parse_clone_exit
libs/userspace/libsinsp/parsers.cpp
Line 1653 in 9096f42
| bool thread_added = m_inspector->add_thread(tinfo); |
In this way, when a
prctl occurs, the thread comm is never updated. A possible solution could be to hook the prctl and update the thread table accordingly.