can't get bpfptr_r uattr parameter when tracing __sys_bpf - ebpf

my environment:
ubuntu 20.04
kernel version: 5.15.0-46-generic
x86_64
below is my code:
import sys
import signal
from bcc import BPF
import os
# define BPF program
bpf_text = """
#include <linux/bpf.h>
#include <linux/version.h>
#include <linux/sockptr.h>
#include<linux/bpfptr.h>
int fn_kprobe(struct pt_regs *ctx,int cmd, bpfptr_t uattr){
int pid=bpf_get_current_pid_tgid();
char tmp[40];
switch (cmd) {
case BPF_MAP_CREATE:
memcpy(tmp,"BPF_MAP_CREATE",39);
bpf_trace_printk("the cmd is %s",tmp);
break;
case BPF_MAP_LOOKUP_ELEM:
memcpy(tmp,"BPF_MAP_LOOKUP_ELEM",39);
bpf_trace_printk("the cmd is %s",tmp);
break;
default:
break;
}
return 0;
}
it raises this error:
/virtual/main.c:21:30: error: initializing 'bpfptr_t' (aka 'sockptr_t') with an expression of incompatible type 'unsigned long'
int cmd = ctx->di; bpfptr_t uattr = ctx->si;
^ ~~~~~~~
1 error generated.
I think this error may due to bpfptr_t is a struct rather than a pointer, but I am not sure.
I have tried to get the second parameter through PT_REGS_PARM2, but it did not work.
————————————————————————————————————
I opened an issue in github and have partially solved my problem.
below is the modified code:
import sys
from bcc import BPF
bpf_text = """
#include <linux/bpf.h>
#include <linux/version.h>
#include <linux/sockptr.h>
#include<linux/bpfptr.h>
int fn_kprobe(struct pt_regs *ctx){
union bpf_attr attr;
bpfptr_t uattr={0};
unsigned int size=0;
int cmd=0;
bpf_probe_read_kernel((void*)(&cmd), sizeof(int), &PT_REGS_PARM1(ctx));
bpf_probe_read_kernel((void*)(&uattr), sizeof(bpfptr_t), &PT_REGS_PARM2(ctx));
bpf_probe_read_kernel((void*)(&size), sizeof(unsigned int), &PT_REGS_PARM3(ctx));
bpf_trace_printk("the first parameter is %d",cmd);
bpf_trace_printk("the uattr.user is %lx",uattr.user);
bpf_trace_printk("the third parameter is %u",size);
return 0;
}
"""
b = BPF(text=bpf_text)
b.attach_kprobe(event="__sys_bpf", fn_name="fn_kprobe")
while True:
try:
b.trace_print()
except KeyboardInterrupt:
sys.exit(0)
I can successfully get the first and the seconde parameter of __sys_bpf, but it's weird that the third parameter is always 0(the third parameter represents the size of bpf_attr, it should not be 0):
b' <...>-73493 [002] d...1 510983.010254: bpf_trace_printk: the first parameter is 5'
b' <...>-73493 [002] d...1 510983.010268: bpf_trace_printk: the address of uattr.user is 0x7ffcbedebd60'
b' <...>-73493 [002] d...1 510983.010269: bpf_trace_printk: the third parameter is 0'
b' <...>-73493 [002] d...1 510983.010476: bpf_trace_printk: the first parameter is 18'
b' <...>-73493 [002] d...1 510983.010485: bpf_trace_printk: the address of uattr.user is 0x7ffcbedebe90'
b' <...>-73493 [002] d...1 510983.010485: bpf_trace_printk: the third parameter is 0'
another question is: before I meet this problem, I can get parameter like this:
int fn_kprobe(struct pt_regs *ctx,int cmd){
bpf_trace_printk("the cmd is %d",cmd);
return 0;
}
but in __sys_bpf I have to use bpf_probe_read_kernel, which really confuses me.

For static int __sys_bpf(int cmd, bpfptr_t uattr, unsigned int size);,
the second argument is struct (TYPEDEF of sockptr_t), NOT pointer to struct.
And in BTF, we have:
[2905] STRUCT '(anon)' size=16 vlen=2
'(anon)' type_id=2904 bits_offset=0
'is_kernel' type_id=40 bits_offset=64 bitfield_size=1
[2906] TYPEDEF 'sockptr_t' type_id=2905
The size of bpfptr_t is 16, so the second argument should span two registers.
So we need to read the fourth register(namely, PT_REGS_PARM4(ctx)) to get the third parameter:
bpf_probe_read_kernel((void*)(&size), sizeof(unsigned int), &PT_REGS_PARM4(ctx));
One thing interesting is what will happen if the size of a parameter too huge to be stored in registers?

Related

eBPF Program Not Capturing Syscall Argument

I have the following eBPF program configured to get information about openat calls.
from bcc import BPF
from bcc.utils import printb
import sys
BPF_SOURCE_CODE = r"""
BPF_PERF_OUTPUT(events);
#define MAX_FILENAME 100
struct data_t {
int dirfd;
int flags;
int mode;
char filename[MAX_FILENAME+1];
};
TRACEPOINT_PROBE(syscalls, sys_enter_openat) {
struct data_t data = {};
data.dirfd = args->dfd;
data.flags = args->flags;
data.mode = args->mode;
bpf_probe_read_user(&data.filename, sizeof(data.filename), args->filename);
events.perf_submit(args, &data, sizeof(data));
return 0;
}
"""
bpf = BPF(text = BPF_SOURCE_CODE)
def handle_event(cpu, data, size):
output = bpf["events"].event(data)
print(output.filename)
bpf["events"].open_perf_buffer(handle_event)
while True:
try:
bpf.perf_buffer_poll()
except KeyboardInterrupt:
print()
sys.exit(0)
I wrote the following test program to verify that the eBPF program works correctly.
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
int main()
{
int fd;
fd = open("/tmp/test.txt", O_RDONLY);
fd = open("/tmp/test1.txt", O_RDONLY);
fd = open("/tmp/test2.txt", O_RDONLY);
}
The eBPF program reliably captures the filenames for the second and third open calls, but never captures the filename for the first call:
$ sudo python3 listen.py
<-- snip -->
b''
b'/tmp/test1.txt'
b'/tmp/test2.txt'
Any ideas on why the eBPF program isn't getting information about the filename for the first open call?

Stuck with netlink, connektor, socket and co

I'm completely new to netlink & co. and I am trying to establisch a connection from user space to the w1-kernel module of a raspberry pi.
Unfortunately the documentation i found is spotty and contradictory.
Here some of the things not clear to me:
basic communication is:
generate a socket: socket()
int s = socket(AF_NETLINK,SOCK_DGRAM, NETLINK_CONNECTOR);
bind it to a local name: bind()
int b = bind(s,(sockaddr*)&sa,sizeof(sa));
with
sa.nl_family=AF_NETLINK;
sa.nl_pid=getpid();//0?
sa.nl_groups=0; //23? -1?
create the message
send it: send()? sendmsg()?
wait for answer: poll()
read answer: recv()
+In examples i found (w1d.c and ucon.c) they use the send() command (not sendmsg) without a connect(), even though the man pages of send say that wouldnt work.
+I am not clear about the structure of the message:
send can send any buffer (char*)
netlink expects a struct nlmsghdr header;
connector expects a struct cn_msg header.
w1_netlink expects a w1_netlink_msg header and w1_netlink_cmd data.
Do i need all headers in a row? Ther are 2 sequence / message number variables, one in nlmsghdr and on in cn_msg???
The test program i wrote is not producing the result i expect: every thing works withour producing an error but i am getting no answer :-(
#include <iostream>
#include <linux/netlink.h>
#include <sys/types.h>
#include <sys/socket.h>
#include<sys/poll.h>
#include <unistd.h>
#include<cstring>
#include "w1_netlink.h"
__u32 nl_seq;
static int netlink_send(int s, struct cn_msg *msg) //copy from (ucon.c)
{
struct nlmsghdr *nlh;
unsigned int size;
int err;
char buf[128];
struct cn_msg *m;
size = NLMSG_SPACE(sizeof(struct cn_msg) + msg->len);
nlh = (struct nlmsghdr *)buf;
nlh->nlmsg_seq = nl_seq++;
nlh->nlmsg_pid = getpid();
nlh->nlmsg_type = NLMSG_DONE;
nlh->nlmsg_len = size;
nlh->nlmsg_flags = 0;
m = (cn_msg*) NLMSG_DATA(nlh);
memcpy(m, msg, sizeof(*m) + msg->len);
err = send(s, nlh, size, 0);
return err;
}
int main(int argc, char *argv[])
{
nl_seq=0;
int s = socket(AF_NETLINK,SOCK_DGRAM, NETLINK_CONNECTOR);
if(s==-1) {std::cout<<"no socket"; return s;};
std::cout<<"socket "<<s;
sockaddr_nl sa;
sa.nl_family=AF_NETLINK;
sa.nl_pid=0;//getpid();
sa.nl_groups=0;
int b = bind(s,(sockaddr*)&sa,sizeof(sa));
if(b==-1){std::cout<<"bind error";return b;}; //prints 3
std::cout<<"bind "<<b; //prints 0
int si=sizeof(struct cn_msg)+sizeof(struct w1_netlink_msg)+sizeof(w1_netlink_cmd);
char * buf;
buf=(char *)malloc(1024);
memset(buf,0,1024);
cn_msg *cnh = (cn_msg*)buf;
w1_netlink_msg* wnh=(w1_netlink_msg*)&cnh->data;
w1_netlink_cmd* wcmd = (w1_netlink_cmd*)&wnh->data;
cnh->id.idx=CN_W1_IDX;
cnh->id.val=CN_W1_VAL;
cnh->seq=nl_seq;
cnh->flags=0;
wnh->type=W1_LIST_MASTERS;
wnh->len=0;
cnh->len=sizeof(struct w1_netlink_msg)+sizeof(w1_netlink_cmd);
int len=netlink_send(s,cnh);
std::cout<<"send "<<len<<" "<<(int)wnh->status; //prints 52 0
pollfd pfd;
pfd.fd=s;
pfd.events=POLLIN;
pfd.revents=0;
int p=0;
while(p<1) {
p=poll(&pfd,1,1000);
std::cout<<"poll "<<p<<pfd.revents; //prints 0 0 in infinite loop
std::cout.flush();
};
memset(wcmd,0,128);
len=recv(s,buf,255,0);
std::cout<<"recv "<<len;
close(s);
return 0;
}
Result is socket 3 bind 0 send 52 0 poll 00 poll 00 ...
Thanks

How to display the result of function called using object reference in c++

#include "pch.h"
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <stdio.h>
using namespace std;
class LetterDistribution
{
public: char district, trace;
public: int random_num;
public : LetterDistribution(){}
public: LetterDistribution(char dis)
{
district = dis;
trace = 'Z';
}
public: string LetterNumbers()
{
random_num = rand();
string letter_no ( district + " " + random_num);
return letter_no;
}
};
int main()
{
srand(time(0));
cout << "Enter district\n"<<endl;
char dis ;
cin >> dis;
LetterDistribution ld(dis);
cout << ld.LetterNumbers();
return 0;}
I am getting error in second last line inside main "cout << ld.LetterNumbers();". I am new to c++ , I have been working on C# earlier. I shall be thankful if someone could help me .
You have 2 issues in LetterNumbers function:
You can't add to string a number, you should convert the number to string first. you can do so by std::to_string(random_num)
You can't start concatenate string with a character, since character is like number in c++, and adding anything to number is a number. You should start from a string, even an empty one.
So the whole function can be something like:
string LetterNumbers()
{
random_num = rand();
string letter_no ( std::string("") + district + " " + std::to_string(random_num));
return letter_no;
}
Another issues: (but not errors!)
in c++ you can specify public: once, and everything after it is still public, until you change it. same thing for private and protected.
instead of <stdio.h> you should use <cstdio> which is the c++ wrapper for the c header.

Socket programming, process blocked on select?

I want do a simple program, where a father process create some child processes; before child pause(), they notification father process.
Child processes run correctly, but father wait on select, otherwise child have written on socket; where is the mistake?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>
typedef struct{
pid_t pid;
int sockfd;
}Child;
void err_exit(char* str)
{
perror(str);
exit(EXIT_FAILURE);
}
int convert_int(char* str)
{
int v;
char*p;
errno = 0;
v = strtol(str,&p,0);
if(errno != 0 || *p != '\0')
err_exit("errno");
return v;
}
void child_job(pid_t pid,int sockfd)
{
int v = write(sockfd,"1",1);
if(v == -1)
err_exit("write");
printf("process %d in pause()\n",pid);
pause();
}
int main(int argc, char* argv[])
{
int nsel;
fd_set masterset;
int n_child,i;
int sockfd[2];
pid_t pid;
Child* c = NULL;
if(argc != 2)
err_exit("usage: <awake2> #children\n");
FD_ZERO(&masterset);
n_child = convert_int(argv[1]);
c = malloc(n_child*sizeof(Child));
if(c == NULL)
err_exit("malloc");
for(i = 0; i <n_child; i++){
if ((socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd)) < 0) { //create socket between child and father
perror("errore in socketpair");
exit(1);
}
if ((pid = fork()) > 0) {
if (close(sockfd[1]) == -1) { //father process closes sockfd[1]
perror("errore in close");
exit(1);
}
c[i].pid = pid;
c[i].sockfd = sockfd[0];
FD_SET(c[i].sockfd, &masterset);
}
else if(!pid)
child_job(getpid(),c[i].sockfd);
}
for(;;){
if ((nsel = select(n_child+1, &masterset, NULL, NULL, NULL)) < 0) {
perror("errore in bind");
exit(1);
}
int i;
for(i = 0; i <n_child; i++){
if(FD_ISSET(c[i].sockfd, &masterset)) {
printf("changed fd\n");
}
}
}
}
One thing that's wrong is you're passing c[i].sockfd to child_job(). In the parent process, it was set to the first socket fd in the pair, but child_job() is called in the child process, where c never gets set to anything. You're passing the original contents of the malloc memory. Change that to child_job(getpid(), sockfd[1]); and you'll be getting closer.
Another thing is that the first argument to select is probably too low. n_child is the number of children, but you need to pass a number here that's greater than the highest file descriptor in your set. For example, run the program with the argument 1 so it creates 1 child. It is likely to start out with file descriptors 0, 1, and 2 open, so the socket pair will be file descriptors 3 and 4. The 3 goes into the fd_set, but the first argument to select is 1+1=2. select ignores your fd 3 because it's above the limit.
To fix that, create a new variable int maxfd; near your fd_set, initialize it to -1 when you FD_ZERO the set, and after every call to FD_SET, update it:
if( [whatever fd you just gave to FD_SET] > maxfd)
maxfd = [whatever fd you just gave to FD_SET];
and call select with maxfd+1 as the first argument.
(Or maybe switch to poll)
That should get you far enough that your first select call works. After that, you'll find more problems.
The fd_set you pass to select will be modified (that's why you can do FD_ISSET tests on it afterward). If you go back to the top of the loop and pass it again without reinitializing it, select will not be looking at all the file descriptors any more, just the ones that were ready in the first call. To fix this, make a second fd_set and copy the master into it just before the select call, and never pass the master to select. (Or you can rebuild the set from scratch each time by scanning the child table.)
If you get a readable fd from select, you should read it before calling select again, otherwise you're just in a "eat CPU calling select over and over" loop.

Behavior of select() on stdin when used on a pipe

I am trying to understand an observation on behavior of select() when used on stdin, when it is receiving data from a pipe.
Basically I had a simple C program using the following code:
hello.c:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <termios.h>
int main(int argc, char *argv[])
{
int flags, opt;
int nsecs, tfnd;
fd_set rfds;
struct timeval tv;
int retval;
int stdin_fileno_p1 = STDIN_FILENO+1;
char c;
int n;
/* Turn off canonical processing on stdin*/
static struct termios oldt, newt;
tcgetattr( STDIN_FILENO, &oldt);
newt = oldt;
newt.c_lflag &= ~(ICANON);
tcsetattr( STDIN_FILENO, TCSANOW, &newt);
while (1)
{
FD_ZERO(&rfds);
FD_SET(STDIN_FILENO, &rfds);
tv.tv_sec = 0;
tv.tv_usec = 0;
retval = select(stdin_fileno_p1, &rfds, NULL, NULL, &tv);
if ( retval && (retval!=-1) )
{
n = read(STDIN_FILENO, &c, 1);
write(STDOUT_FILENO, &c, 1);
}
else printf("No Data\n");
usleep(100000);
}
tcsetattr( STDIN_FILENO, TCSANOW, &oldt);
}
If I ran the program as follows I could see characters echoing when I type keys on while the program is running. When keys are not pressed, it displays "No Data" as expected.
./hello
However, if use the program as follows, the program never gets to a state where is displays "No Data". Instead last character "c" is repeatedly displayed.
echo -n abc | ./hello
I'm a bit puzzled by this observation, and would be grateful if you could help me to understand the observed behavior.
The problem is that your program does not detect an end-of-file condition when it reads from the STDIN_FILENO descriptor. After echo has written the c character it will close its end of the pipe, which will cause the select in your program to return immediately and your read to return 0 as an indication that no more data will ever be available from that descriptor. Your program doesn't detect that condition. Instead it just calls write with whatever character was left in the buffer by the last successful read and then repeats the loop.
To fix, do if (n==0) break; after the read.