From a969154a5b1319dd06e117592f419e2464b98294 Mon Sep 17 00:00:00 2001 From: Nick Loginov Date: Mon, 20 Jul 2020 13:30:02 +0200 Subject: [PATCH] Fix the "Consumer not found error" (#31) 1. Add similar inode/dev match logic (see ) to the rest of the probes 2. remove bpf_map_update_elem call from trace_done_path_create probe. This call creates the situation when we have a rule in ebpf, but don't have a rule on the agent side. In combination with 1 it leads to "Consumer not found error" Exact scenario: create subdirectory in directory which has the same inode number as monitored directory but different device id, due to bug 1 we will treat this event as relevant and due to bug 2 we add that inode to our rule map and therefore will monitor any changes for the unrelevant subdirectory. Agent on the contrary doesn't know anything about that directory (because it belongs to diffrent fs) and will spam with "Consumer not found" message on every such event --- e2etests/basic_test.go | 33 +++++++++++ e2etests/bpfink.go | 19 +++++- e2etests/fs.go | 7 +++ pkg/ebpf/vfs-3.10.o | Bin 8728 -> 9264 bytes pkg/ebpf/vfs-4.14.o | Bin 8728 -> 9264 bytes pkg/ebpf/vfs-4.18.o | Bin 8728 -> 9264 bytes pkg/ebpf/vfs-4.19.o | Bin 8728 -> 9264 bytes pkg/ebpf/vfs-4.9.o | Bin 8728 -> 9264 bytes pkg/ebpf/vfs.c | 130 +++++++++++++++++------------------------ 9 files changed, 112 insertions(+), 77 deletions(-) diff --git a/e2etests/basic_test.go b/e2etests/basic_test.go index 16f8e89..5a92777 100644 --- a/e2etests/basic_test.go +++ b/e2etests/basic_test.go @@ -5,6 +5,7 @@ package e2etests import ( "path" "testing" + "time" ) func TestBPfink(t *testing.T) { @@ -12,6 +13,7 @@ func TestBPfink(t *testing.T) { defer world.TearDown() t.Run("generic file create/modify/delete", world.SubTest(testCreateGenericFile)) t.Run("sudoers file create", world.SubTest(testCreateSudoersDir)) + t.Run("generic file in newly created dir", world.SubTest(testCreateDirectory)) } @@ -54,3 +56,34 @@ func testCreateSudoersDir(t *testing.T, w *World) { Message: "Sudoers file deleted", }) } + +func testCreateDirectory(t *testing.T, w *World) { + dirToCreate := path.Join(w.FS.GenericMonitoringDir, "dir1") + w.FS.MustCreateDir(t, dirToCreate) + + // TODO: bpfink can't process dir creation + file creation immediately + // need some time to handle dir creation properly + time.Sleep(100 * time.Millisecond) + fileToCreate := path.Join(dirToCreate, "sample_file.txt") + f := w.FS.MustCreateFile(t, fileToCreate) + w.BPFink.ExpectEvent(t, Event{ + File: fileToCreate, + Message: "generic file created", + }) + + f.WriteString("hello world") + w.BPFink.ExpectEvent(t, Event{ + File: fileToCreate, + Message: "generic file Modified", + }) + w.FS.MustRemoveFile(t, fileToCreate) + w.BPFink.ExpectEvent(t, Event{ + File: fileToCreate, + Message: "generic file deleted", + }) + + // TODO this exceptation is failing with 'failed to remove consumer' log line + // w.FS.MustRemoveFile(t, dirToCreate) + // time.Sleep(100 * time.Millisecond) + // w.BPFink.ExpectNothing(t) +} diff --git a/e2etests/bpfink.go b/e2etests/bpfink.go index 41540ea..cfcb3e7 100644 --- a/e2etests/bpfink.go +++ b/e2etests/bpfink.go @@ -3,6 +3,7 @@ package e2etests import ( "bufio" "encoding/json" + "io" "os" "os/exec" "os/user" @@ -198,7 +199,7 @@ func (instance *BPFinkInstance) ExpectEvent(t *testing.T, e Event) { time.Sleep(10 * time.Millisecond) line, err := instance.stdErr.ReadString('\n') if err != nil { - t.Errorf("unable to read line from the file: %s", err) + t.Errorf("Expected [%+v] but unable to read line from the file: %s", e, err) return } @@ -227,6 +228,22 @@ func (instance *BPFinkInstance) ExpectEvent(t *testing.T, e Event) { } } +func (instance *BPFinkInstance) ExpectNothing(t *testing.T) { + // give the event time to happen (file sync) + time.Sleep(10 * time.Millisecond) + line, err := instance.stdErr.ReadString('\n') + if err == io.EOF { + return + } + + if err != nil { + t.Errorf("Expected EOF but got the error: %s", err) + return + } + + t.Errorf("Expected EOF but get a log line: %s", line) +} + func (instance *BPFinkInstance) Shutdown() { done := make(chan error) go func() { done <- instance.cmd.Wait() }() diff --git a/e2etests/fs.go b/e2etests/fs.go index 92e6304..afbfe96 100644 --- a/e2etests/fs.go +++ b/e2etests/fs.go @@ -19,6 +19,13 @@ func (fs *FS) MustCreateFile(t *testing.T, filePath string) *os.File { return f } +func (fs *FS) MustCreateDir(t *testing.T, dirPath string) { + err := os.MkdirAll(dirPath, 0666) + if err != nil { + t.Fatalf("unable to create directory %s: %s", dirPath, err) + } +} + func (fs *FS) MustRemoveFile(t *testing.T, filePath string) { if err := os.Remove(filePath); err != nil { t.Fatalf("unable to remove file %s: %s", filePath, err) diff --git a/pkg/ebpf/vfs-3.10.o b/pkg/ebpf/vfs-3.10.o index 39e6ded7845940e29eaf4da460ea041276e22bc1..6f70a8bdd0937ad1bcc25d4b6264dfacec29c865 100644 GIT binary patch literal 9264 zcmeHNPfVQ06`wUF#CDy~CM0B&T0~7%rw7N5ofcJIRaQdU(jZmsvi|(ppcvTf zZfw5qt5g)Z5=Dx{A(1#l5zAI8M;{`=Z8^F(AARJ|14j;)%E1kNzxUqk4zmNqE>RQJ zBYnJY-prf#XWpNgZ~5~Z7vDUR%Ng5p=3l1Wvshzpoov4s__bi3p_$ER1iz|8S$=B= zm@L1ka6{o;g?AL*R=BQkP2nwtD+-qsE;4M^gA;g4A65! z;fQ&Y-i%42GDDYs@k^nH!l3^S=GE7~#KgYbF9A%_SmVA+H>`9+G~T9LP`W{;o2Uz-#p`*P=4&~8HtQ0vQ`HD(O(j4|uRG?#%jriX?Xh+jX}aT@pn z)n|Uf*rZ1Wlg145wfhm}@3Y-{y2L)N9{1IL(A(-Ed}I%?Vrg0En?IEGuhKf-rPtz9 z(r>;f5PE{q4U!k*??J7dC(S$Gm+^OA75IlAKg}=T_XItV6M95>*zYWELA^G)z8xnmN!e)&r|jDRQ)_%*KzgpRQ)_vKTk!!EPYr%zs{0|{CrsV+4a|E zB#OZ=#_84diy@s4yTZO47tt8K?Ve{2Sa;N$zpZiOt{bJB?5|``hDl?H?F6|f5_ZY8 z6`)TVLWlVUZ~msvcm2Dx;`7^PzF(DJrt(_8BVEMU<69uRbem(0Z^`v{?wTI*AIR1G zK>EmEu07m+z&gpY$UAZbQ9|~3R_OeE(5wg^`-Pc(hVl%xtjM{n^C|9TiH{#g&|Bh%>M^x; zU2XF{MR?b4mTySEb{)RQ{2@2&>eAm+eO#)!tULEd(dc4nwAMhhFSVrKZy!eOp z^XS+oJO=wnJznH~QWv{s`K8wQL2(CtA&As12kG<>AZ(ts$4z>9{P#uCXs73QL%s*6zW>nw(>yXeN zULE?G%5_uxX?|Ow^1%Uw8qUq*1l|3#^71}cd0OjSNcZ5Do@YF|gZ!Z2&A%D2OKC;q zb>(r_ZFa~bdcp1wa<0Ys0XK}VdOvQTZ^h64XZK_rqNrv0MY-SQ?Y(_Y`?Ct=dqtKn zYkyqfn8Hzo!wQEK78ttrFYA2|=c~QXuM;d+=$>rvr#JwD9&zBVe-HPz29q1!$ADja zP`}4=;d@AdruOB2&jhQj_>+5AHoqcz%r7xa8oe!l?I#MC?~>|;C9GUYL+Buf@82uI z`N9wL3*P#n??9!x%9$5FZoYy%d*!Pp#VJ}}&o3Az5u$dtK32XV!ngEEcpBYP@31|h z`2+5!#0UF>{Jej@l64L(U%mVy3+nEdAJ4OD{{foW($Cr7mk96f?`UAZKqvQF@$>dM z{B^bu#^oF?@sQMIpR&BfN&EgP@q_vBAEeU(dhB}c{k1@H{I}&iP{G5lsqWA?5|0~6hdirII?=k(J-D-?K+JT>B{Bbkp zty5(D3A5#`15LX4WYgBkzIB;y`??Eu87(fmuvkYwa(3X4dl>0=*D{Do&X{*R3BPua?ct%)X--ys8vc)T)a`4sKv#2s#ZzN=~_Hf zGn1uq^_BQKp{nv;`S4m?H8TDBm1?mvd3mB@PFLb<@0Tmnm*Q92$l~?5Qk|Hd(%F}% zi1-c%@RBj9nGuDIh#4DvGVvDz(xjbGn*L0$#ju;cRgb;EV-7MenuZYcj^*eDOSb z=LEbe;H(Abg#4_0ljV(i$Ui@)4GyFt)Bgima1jpRG_Py$vnOzmIdkw@@cOpvr9<|! z{pj5qhc$3sa2sk)DBEp6FS7=8GGZ2bT6$YwPZ9GFe_Q|jI%?^4S@RMRhxqqvKo00c z@JAN6^p^iJ;X?cuG<8`ybeR^t?SD=$A^w8_{#erI|2pA9{MY2?x{h;zrMLVG^b+Df z8sLw4mfq_BPr`-xZ)j=o&l#4U$hd9L-w^+q0Dn{!OK#jJn%aL%CnElkt1P{>{{^D=d4psW R8$)eL2O%R|!qTgF=06&Y@}&R( literal 8728 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 R8$)eL2O%R|!qTgF=06&Y@}&R( literal 8728 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 R8$)eL2O%R|!qTgF=06&Y@}&R( literal 8728 zcmeHNO>9#~5MGm};ir@o2&8U>f=VUS133ItwCce^v_usWX+=t;mcAJCAQAa7wt+mm zQcUq~smQUxnL&(7@3 z&d<)y?t96%A0PWcNN}IxS;U1!dnXG6wWZLRYYG0>ASnUyga|gm`);nPY>WZdV1)&tZ={_rJgZS zHpkGFpLs0ua2WEhfUk1rF&MiNX#f*d7Z`Wt234+)#v5|eD)#})jZ_91KS^_&C1b)w zn^_Jp??E=SD{+7gZ8ziqwXVcIV}<~Gjae|JHVdjT9W*>m`noZX)1VimT89w3I^N@s zU59M)1nZBgR~UA*Nqk&CPN*O7+xn5Ih+J*6%zKXXzee3|k|#%a4bTh7T(sI2fhTDk zgO|xD@U!2`o6K=?2krPyfl*%Ism)ut1HG@)1H0i*HYw|D*Dt&NChG*>CXBC_e26L% z?{ZS|1mkPz@z=k0lZezh$jKSXms&>fZV9^}^jl;9$c}bi29C#@M4wlVpA^>fUE^LZ zX}*^vHszA$dufsFE|)alOLrC5d@n61uK8XPKguP|_mafHU9Z2F?}%J{z7Ohtar3s8 z6~~Yt!_;;4^(#N(ium&W$PQ7jnQ!eX^A39aCT8AjX^Hcc;;DJt&+&m>DNp{P=53Q* zRgnXK+T&k}%kw#3pLr^MWwkd_y#sBe*wfobezu$SOt0JZ3!|ok@&|U+E(ne~=KABV z1Llcl#omE6*d-MI?IP#bg_`a!tQR;N^?~XQwfS`nO!meDjdQB*H|nRW_d#6TDkAsq z*M}W!XjE;i%OTIcMa`ogqTBogL_Nvgko~h-9VuUx{YUjg_M_I5sM4qS0>h}H`I40V z3q4q`m-R*tf^6x!#H%!=@Cw5x)t4(Vb%4$*mW$i>Q?dM_ufD%3v?$bYKe(>^&F7oi znUwWzI%kRB-%oZQb(t=n*X9@ah@EZ4z*C%C5}%jnmg%9m+5I_SUfevQGW#-*p4(E@ko9)GW1FrIAYxk_!gZCz!7qzP5v~E}@zrN37 za%!mSn8)?g>gV-t@w1T+PjfGUy|^j<)AJ&#*mb(idHkQ>L&$!#a&kuQ>q+|_c1`gq zh4S86PEIPGQ#h<}Na3KueuaGuo7O?^p}3#IUqJufHDE@F8vovdgBtr87h=d`$=&%M z;U4bfNm}{cyt`0&#CGAmHcj$fi7)Aw_7ReUTK4J@vH$8Vg>wv}YB#SF>P9#5xq9c+ zFHB+WimDnz65NA|{W`C@G zeMGnZ*tkU1dmIng>Az>NofHJviFI=A6?>NI=VNA8?6188?UV;JAHFvP`W>RSlga%3 z0YaB)KK>qo^~d*!%`$#mt{W!O_pehsMbKR@e_u~&JbP#g>z~WNi|Fq94#lh&u65=k3A zGs~9>A>@#BPQ&;K2k&!aLB@D-mGSG&_(MtMItB8-)-mdM`rRS7e+`L1C;Osj&!E!# z7&at6Q@V^d=sz&M-5m1f8B_X{M?b6dYaabmr4M=bf5-HWmify}U)Mq}GJS)&@5L!# z`bM+l(IpBoM2=2-!%?T#X6TX`)7p%Ftn$X%%m$AlXq(yCLVrY*PV+_!eSqnD7&xOl zn2rkE-0*e1lTWT@`qxZv)%zRMTkYIJaqBd#aY!+LtG_=oy)`}ve<$B0spGU^UKcsD z@x|h{<^{-G>9KhN9OH*HKkRtUlkfE8R~w)6_;@Pp?2U* zMgxwh4+5bE9OvpJ5Ng1&iqk--0Y@#m4TKtS&Wd6_lMRE*`H^B6gyY40WcC!p$)Xv{ zt@?D15#y{>9J404&`aFET1nPTY9aAv|A3r0t>;dnuq_JqU1 z*?eX!bX^R`LGY}|E45I}7c-~Lo%XThuGFoM%sxch5Y`f%}8o za0azj1NxgM{wPJQhks=c!%r)3&)eOkJWhW~gW{j7R$iwyyGS@r|5A*8R5L4Y_0JM5 zPXCOSGXLDR@>c)P)QQvInG}v5WkgNElvdvAKSH!P{r7d!)%2%HK2CosMnCv`{lBlE zKZPPj&A)e8c^Nfkkvehu`&EBR8R0)p87pu7$9qMb|GO=Nm#|h|r#1N85vTu3jQ^;^ zR^CpJG=YA95AMbxL`~l7q>o?E9P=*G;^fnCnA&Rce-cle{Gk57g0By}Zu^3eIQg$u zkoV_XN5YWT0_#XUXj(92fr?jsK7`A{?kyR^G<{FvIRaQdU(jZmsvi|(ppcvTf zZfw5qt5g)Z5=Dx{A(1#l5zAI8M;{`=Z8^F(AARJ|14j;)%E1kNzxUqk4zmNqE>RQJ zBYnJY-prf#XWpNgZ~5~Z7vDUR%Ng5p=3l1Wvshzpoov4s__bi3p_$ER1iz|8S$=B= zm@L1ka6{o;g?AL*R=BQkP2nwtD+-qsE;4M^gA;g4A65! z;fQ&Y-i%42GDDYs@k^nH!l3^S=GE7~#KgYbF9A%_SmVA+H>`9+G~T9LP`W{;o2Uz-#p`*P=4&~8HtQ0vQ`HD(O(j4|uRG?#%jriX?Xh+jX}aT@pn z)n|Uf*rZ1Wlg145wfhm}@3Y-{y2L)N9{1IL(A(-Ed}I%?Vrg0En?IEGuhKf-rPtz9 z(r>;f5PE{q4U!k*??J7dC(S$Gm+^OA75IlAKg}=T_XItV6M95>*zYWELA^G)z8xnmN!e)&r|jDRQ)_%*KzgpRQ)_vKTk!!EPYr%zs{0|{CrsV+4a|E zB#OZ=#_84diy@s4yTZO47tt8K?Ve{2Sa;N$zpZiOt{bJB?5|``hDl?H?F6|f5_ZY8 z6`)TVLWlVUZ~msvcm2Dx;`7^PzF(DJrt(_8BVEMU<69uRbem(0Z^`v{?wTI*AIR1G zK>EmEu07m+z&gpY$UAZbQ9|~3R_OeE(5wg^`-Pc(hVl%xtjM{n^C|9TiH{#g&|Bh%>M^x; zU2XF{MR?b4mTySEb{)RQ{2@2&>eAm+eO#)!tULEd(dc4nwAMhhFSVrKZy!eOp z^XS+oJO=wnJznH~QWv{s`K8wQL2(CtA&As12kG<>AZ(ts$4z>9{P#uCXs73QL%s*6zW>nw(>yXeN zULE?G%5_uxX?|Ow^1%Uw8qUq*1l|3#^71}cd0OjSNcZ5Do@YF|gZ!Z2&A%D2OKC;q zb>(r_ZFa~bdcp1wa<0Ys0XK}VdOvQTZ^h64XZK_rqNrv0MY-SQ?Y(_Y`?Ct=dqtKn zYkyqfn8Hzo!wQEK78ttrFYA2|=c~QXuM;d+=$>rvr#JwD9&zBVe-HPz29q1!$ADja zP`}4=;d@AdruOB2&jhQj_>+5AHoqcz%r7xa8oe!l?I#MC?~>|;C9GUYL+Buf@82uI z`N9wL3*P#n??9!x%9$5FZoYy%d*!Pp#VJ}}&o3Az5u$dtK32XV!ngEEcpBYP@31|h z`2+5!#0UF>{Jej@l64L(U%mVy3+nEdAJ4OD{{foW($Cr7mk96f?`UAZKqvQF@$>dM z{B^bu#^oF?@sQMIpR&BfN&EgP@q_vBAEeU(dhB}c{k1@H{I}&iP{G5lsqWA?5|0~6hdirII?=k(J-D-?K+JT>B{Bbkp zty5(D3A5#`15LX4WYgBkzIB;y`??Eu87(fmuvkYwa(3X4dl>0=*D{Do&X{*R3BPua?ct%)X--ys8vc)T)a`4sKv#2s#ZzN=~_Hf zGn1uq^_BQKp{nv;`S4m?H8TDBm1?mvd3mB@PFLb<@0Tmnm*Q92$l~?5Qk|Hd(%F}% zi1-c%@RBj9nGuDIh#4DvGVvDz(xjbGn*L0$#ju;cRgb;EV-7MenuZYcj^*eDOSb z=LEbe;H(Abg#4_0ljV(i$Ui@)4GyFt)Bgima1jpRG_Py$vnOzmIdkw@@cOpvr9<|! z{pj5qhc$3sa2sk)DBEp6FS7=8GGZ2bT6$YwPZ9GFe_Q|jI%?^4S@RMRhxqqvKo00c z@JAN6^p^iJ;X?cuG<8`ybeR^t?SD=$A^w8_{#erI|2pA9{MY2?x{h;zrMLVG^b+Df z8sLw4mfq_BPr`-xZ)j=o&l#4U$hd9L-w^+q0Dn{!OK#jJn%aL%CnElkt1P{>{{^D=d4psW R8$)eL2O%R|!qTgF=06&Y@}&R( literal 8728 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 R8$)eL2O%R|!qTgF=06&Y@}&R( literal 8728 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 + } else { + sb = inode->i_sb; + } + + dev_t kdevice = 0; + bpf_probe_read(&kdevice, sizeof(kdevice), (u64)&sb->s_dev); + + // transform device_id from the kernel-space format to the user-space format + return expected_device == (u64)new_encode_dev(kdevice); +} + SEC("kprobe/vfs_write") int trace_write_entry(struct pt_regs *ctx){ struct data_t data = {}; @@ -98,21 +120,7 @@ int trace_write_entry(struct pt_regs *ctx){ } u64 *rule_exists = bpf_map_lookup_elem(&rules, &inode.i_ino); - if (rule_exists == 0) { - return 0; - } - - // file is uniquely identified by the (inode, dev) pair - // so far we catch a write event for the file with matched inode - // we need to verify dev id is also matched to be sure we catch the event for the right file - dev_t kdevice = 0; - bpf_probe_read(&kdevice, sizeof(kdevice), (u64)&inode.i_sb->s_dev); - - // transform device_id from the kernel-space format to the user-space format - u64 actualDeviceID = (u64)new_encode_dev(kdevice); - u64 expectedDeviceID = *rule_exists; - - if (actualDeviceID != expectedDeviceID) { + if (rule_exists == 0 || !inode_matches_device(*rule_exists, &inode, false)) { return 0; } @@ -165,19 +173,8 @@ int trace_vfs_rename(struct pt_regs *ctx) { // rule exists either if we are monitoring target directory or target file u64 *rule_exists = newInode == 0 ? bpf_map_lookup_elem(&rules, &new_dir.i_ino) : bpf_map_lookup_elem(&rules, &newInode); - if (rule_exists == 0) { - return 0; - } - - // check if the destination file or directory belongs to the same device as a - // monitored one - dev_t kdevice = 0; - bpf_probe_read(&kdevice, sizeof(kdevice), (u64)&new_dir.i_sb->s_dev); - u64 actualDeviceID = (u64)new_encode_dev(kdevice); - u64 expectedDeviceID = *rule_exists; - - if (actualDeviceID != expectedDeviceID) { + if (rule_exists == 0 || !inode_matches_device(*rule_exists, &new_dir, false)) { return 0; } @@ -203,26 +200,24 @@ SEC("kprobe/vfs_unlink") //delete file int trace_vfs_unlink(struct pt_regs *ctx) { struct data_t data = {}; if (bpf_get_current_comm(&data.comm, sizeof(data.comm)) == 0) { + typeof(struct inode *) file_inode; + __builtin_memset(&file_inode, 0, sizeof(file_inode)); + bpf_probe_read(&file_inode, sizeof(file_inode), (u64)&({ + typeof(struct dentry *) _val; + __builtin_memset(&_val, 0, sizeof(_val)); + bpf_probe_read(&_val, sizeof(_val), (u64)&PT_REGS_PARM2(ctx)); + _val; + })->d_inode); u64 oldInode = ({ typeof(dev_t) _val; __builtin_memset(&_val, 0, sizeof(_val)); - bpf_probe_read(&_val, sizeof(_val), (u64)&({ - typeof(struct inode *) _val; - __builtin_memset(&_val, 0, sizeof(_val)); - bpf_probe_read(&_val, sizeof(_val), (u64)&({ - typeof(struct dentry *) _val; - __builtin_memset(&_val, 0, sizeof(_val)); - bpf_probe_read(&_val, sizeof(_val), (u64)&PT_REGS_PARM2(ctx)); - _val; - })->d_inode); - _val; - })->i_ino); + bpf_probe_read(&_val, sizeof(_val), (u64)&(file_inode)->i_ino); _val; }); u64 *rule_exists = bpf_map_lookup_elem(&rules, &oldInode); - if (rule_exists == 0) { + if (rule_exists == 0 || !inode_matches_device(*rule_exists, file_inode, true)) { return 0; } @@ -244,26 +239,24 @@ SEC("kprobe/vfs_rmdir") int trace_vfs_rmdir(struct pt_regs *ctx) { struct data_t data = {}; if (bpf_get_current_comm(&data.comm, sizeof(data.comm)) == 0) { + typeof(struct inode *) dir_inode; + __builtin_memset(&dir_inode, 0, sizeof(dir_inode)); + bpf_probe_read(&dir_inode, sizeof(dir_inode), (u64)&({ + typeof(struct dentry *) _val; + __builtin_memset(&_val, 0, sizeof(_val)); + bpf_probe_read(&_val, sizeof(_val), (u64)&PT_REGS_PARM2(ctx)); + _val; + })->d_inode); u64 inode_number = ({ typeof(dev_t) _val; __builtin_memset(&_val, 0, sizeof(_val)); - bpf_probe_read(&_val, sizeof(_val), (u64)&({ - typeof(struct inode *) _val; - __builtin_memset(&_val, 0, sizeof(_val)); - bpf_probe_read(&_val, sizeof(_val), (u64)&({ - typeof(struct dentry *) _val; - __builtin_memset(&_val, 0, sizeof(_val)); - bpf_probe_read(&_val, sizeof(_val), (u64)&PT_REGS_PARM2(ctx)); - _val; - })->d_inode); - _val; - })->i_ino); + bpf_probe_read(&_val, sizeof(_val), (u64)&(dir_inode)->i_ino); _val; }); u64 *rule_exists = bpf_map_lookup_elem(&rules, &inode_number); - if (rule_exists == 0) { + if (rule_exists == 0 || !inode_matches_device(*rule_exists, dir_inode, true)) { return 0; } @@ -283,13 +276,9 @@ SEC("kprobe/done_path_create") //mkdir int trace_done_path_create(struct pt_regs *ctx) { struct data_t data = {}; if (bpf_get_current_comm(&data.comm, sizeof(data.comm)) == 0) { - u64 parent_inode_number = ({ - typeof(dev_t) _val; - __builtin_memset(&_val, 0, sizeof(_val)); - bpf_probe_read(&_val, sizeof(_val), (u64)&({ - typeof(struct inode *) _val; - __builtin_memset(&_val, 0, sizeof(_val)); - bpf_probe_read(&_val, sizeof(_val), (u64)&({ + typeof(struct inode_sm*) parent_dir; + __builtin_memset(&parent_dir, 0, sizeof(parent_dir)); + bpf_probe_read(&parent_dir, sizeof(parent_dir), (u64)&({ typeof(struct dentry *) _val; __builtin_memset(&_val, 0, sizeof(_val)); bpf_probe_read(&_val, sizeof(_val), (u64)&({ @@ -300,13 +289,16 @@ int trace_done_path_create(struct pt_regs *ctx) { })->dentry); _val; })->d_inode); - _val; - })->i_ino); + + u64 parent_inode_number = ({ + typeof(dev_t) _val; + __builtin_memset(&_val, 0, sizeof(_val)); + bpf_probe_read(&_val, sizeof(_val), (u64)&(parent_dir)->i_ino); _val; }); u64 *rule_exists = bpf_map_lookup_elem(&rules, &parent_inode_number); - if (rule_exists == 0) { + if (rule_exists == 0 || !inode_matches_device(*rule_exists, parent_dir, true)) { return 0; } @@ -327,14 +319,9 @@ int trace_done_path_create(struct pt_regs *ctx) { _val; }); - u64 flag = 0; - u64 value = 2; - bpf_map_update_elem(&rules, (void *)&child_inode_number, (void *)&value, flag); - struct dentry_sm *d_child; bpf_probe_read(&d_child, sizeof(d_child), &PT_REGS_PARM2(ctx)); - bpf_probe_read(&data.name, sizeof(data.name), &d_child->d_name.name+2); u64 id = bpf_get_current_pid_tgid(); @@ -390,22 +377,13 @@ int trace_do_dentry_open(struct pt_regs *ctx) { } bpf_probe_read(&inode, sizeof(inode), (void *)PT_REGS_PARM2(ctx)); - - dev_t kdevice = 0; - bpf_probe_read(&kdevice, sizeof(kdevice), (u64)&inode.i_sb->s_dev); - - u64 actualDeviceID = (u64)new_encode_dev(kdevice); - u64 expectedDeviceID = *rule_exists; - - if (actualDeviceID != expectedDeviceID) { + if (!inode_matches_device(*rule_exists, &inode, false)) { return 0; } bpf_probe_read(&data.name, sizeof(data.name), &file.f_path.dentry->d_name.name+2); bpf_probe_read(&data.device, sizeof(data.device), &file.f_path.dentry->d_name.len); - - u64 id = bpf_get_current_pid_tgid(); data.mode = 4; //constant defining create new file, data.pid = id >> 32;