 Okay, folks, so we'll start here in the afternoon, the last afternoon session of the main conference before the buffs, we are for the first time doing tutorials as part of the Linux security summit. So for those who wanna attend the tutorial, you can stay here. And there's also the unconference which is one level below outside the room. There are four tables down there where people can get together and work on different problems that they may wanna discuss. So I'm going to welcome Paul Moore and Tom Ramatka to give a tutorial on Libsec Comp. You can't escape me. So we're gonna do this, I'm gonna start off, give you a quick little introduction to Libsec Comp. Hey people in the hallway, we're starting. I just realized I don't know why I was cupping my hands, that really doesn't make sense when you've got a little Pell mic on, but... Can somebody at least close those doors, maybe? All right, anyway, I'm gonna give you a quick little introduction to Cisco Filtering and Libsec Comp. And then I'm gonna hand it over to Tom, and he's gonna walk you through a tutorial. And Tom is Superman, he's gonna do it on the fly. So if you have any questions or whatnot, feel free to think about it while I'm presenting and he'll make it all happen. Also, kind of a bit of a pause for Tom. Tom just recently agreed to step up and help me maintain Libsec Comp upstream, which is great. This is good for you, for your selfish reasons. If I get hit by a bus, there's at least somebody around that can still make releases and make sure the trains run out of time. So anyway, we're fine. If you're not gonna clap for him, I will, okay. So anyway. All right, so let's go ahead and get started. Okay, so first of all, why should I use Libsec Comp? This is the part I'll go over. And first off, the world's a scary place, right? This is, once again, hopefully not news to you. If it is, I'm really sorry. So bugs are inevitable, there's nothing we can do. There's better languages out there that will help reduce the amount of bugs. Especially the really stupid ones. But there's always gonna be issues with logic decisions and various poor practices. There's nothing we can do about it. And bugs can also be a security risk. Depending on where that bug happens to be and the context of execution and whatnot. There's always a risk that that small little bug that you thought wasn't a big deal could all of a sudden become a very big deal lead to an escalation of privileges. You know, break some confidentiality rules on data. You know, tamper with the data so you no longer have the integrity you wanted. All sorts of things. You know, malicious code execution. And in the context of what we're talking about today with syscall filtering, we also wanna look at that boundary between the kernel and user space. Cause that's really what we're getting at when we start talking about syscall filtering. And you know, syscall filtering allows us to shrink down that attack surface into the kernel and hopefully prevent some of those kernel vulnerabilities. So anyway, that's it. I won't spend a whole lot of time in this slide cause I think, like I said, hopefully this is not new to Nubia. But anyway, what's syscall filtering? Once again, probably not new, but just in case. Syscall filtering in the context of seccomp and in the context of Linux is all about allowing the kernel to interrupt the syscalls as they're being taken and perform some sort of action. You know, you can call or signal the process, notify a separate tracing process, or simply call as the syscall to abort early with a return code or an error node value. And in the context of the new version of seccomp, seccomp-bpf, seccomp-mode2, this is configured application runtime. It's inherited by the processes children. So, you know, for example, if you set a seccomp filter in a NIT or systemD, whatever you wanna call it, it's gonna get inherited across every process on the system. If you set your seccomp filter and then fork an exact, a bunch of worker processes, it's gonna get inherited. And even though it's up to the individual process to define the filter and install it into the kernel, unless you're using some of the user notification stuff we heard about earlier today, it's still actually the decisions are going to be enforced inside the kernel based on the filter you installed. And even in the case of, you know, where you're delegating that off to another user space program in the user space notification, that decision to delegate it is still done from inside the kernel. So once you load this, it's difficult slash impossible to bypass. And a bit of a history lesson, Cisco filtering is actually much older than what many of us see today with the mode two stuff. Seccomp mode one traces itself back to 2623. However, very crude, very minimal, wasn't really all that useful outside of a very small set of, you know, worker computation types of tasks. You basically, you had those four syscalls, that was it. So basically you can read and write a file descriptor to get the data in, do some transforms on it, send it back out and you could exit. That was about all you could safely do. Was it really a general purpose solution? You had no way of filtering on ABIs like you do with Seccomp. It was a fixed set of syscalls. So yeah, so even though it's been there for ages, nobody's ever really used it. What we talk about today with Seccomp is really what the second attempt. Seccomp mode two, Seccomp BPF. I've heard a lot of people even just, it's just starting to call Seccomp now because we've pretty much ignored the other one because it really wasn't any use. This has been around since Linux three fives when it first made its introduction. So it's actually pretty old itself at this point. It's seen a lot of activity since then, adding multiple architectures, new functionality. We've got pretty much, not all of the architectures that Linux supports, but most of them, I think the one notable exception is still risk five. We've been talking with them. Supposedly the kernel support is any day now, but they've been saying that for a while. So I honestly don't know. But don't worry, we do support your 32 bit PA risk machine. So that one person who's watching on the video, you're all set. The syscall filters are defined by the filtering language. I think we're all aware of that at this point. When I first started talking about this, nobody knew what BPF was. Now everybody seems to know what BPF was. There is perhaps one important thing to mention. Seccomp uses classic BPF, normal BPF, not EBPF. From the purposes of Libseccomp, you don't really care about that, but just if you are gonna be writing filters on your own, something to keep in mind of. This also, Seccomp mode too, also allows you to filter on the ABI in architecture. Most people at first glance say, why do I care about that? But since I think most everybody's laptop in the room is Intel 64-bit chip, and you're running a x8664 OS, you probably also support 32-bit x86. You might even also support 32-bit x32. Seccomp mode too allows you to filter not only on the syscall, but also on the ABI. So you could, for example, just completely shut off x32 or 32-bit x86, which could be advantageous. If your operating system decides it wants to keep supporting that, and you said, now I don't wanna support all those crafty 32-bit bug-ridden applications, it's easy to do with Seccomp. And once again, they kept the same provisions, as in with first attempt. Still, all the enforcement is done by the kernel. Even if it's being delegated off to user space, to the notification mechanism, that delegation decision is done inside the kernel. So once again, it's very hard to bypass. You shouldn't be able to bypass this. Once you push a filter down into the kernel. We already just talked about this. Filters are written in BPF, which stands for Berkeley Packet Filter, which can trace its origins back to the socket filter, which is really, really old. But it works out really well in this particular case. And as we've seen from the past few years, it actually has a lot of applications way beyond this. So it's a pretty nice, little low-level assembly language thing. There are, as I think LLVM actually even has a compiler, which will do BPF. Having said that, I'm not sure if that's classic BPF. It might be, or it might just be BPF, but either way. Once again, you're listening because you want to use Libseccomp and you don't want to have to write assembly language yourself. And I can't blame you. So anyway, the other thing to note about Mode 2 is that you can have multiple filters loaded on the system, and these can be filters that you've inherited and you're adding onto, or you could even have one process which stacks filters on itself. So you could set up something so that when you first start, first thing your process does is it loads a somewhat permissive filter because you still have to do, say, a bunch of initialization and setup. Maybe you need to read your config files or whatnot. But then once you actually get to the point where you want to start accepting input or now you're entering in one of the riskier aspects of your execution phase, then you can go ahead and load an additional filter and it becomes additive and basically the most restrictive decision wins. So each of these stacked filters will get executed on each syscall invocation. And if, say, the filter A says, okay, I want to allow this particular syscall invocation and the second seccomp filter said, no, I want to disallow it, then it'll be disallowed. So it is relatively easy to go to that point of a permissive filter to allow for initialization and then as you move along in your stages of execution, you can load more and more restrictive filters to meet your needs. Yeah, and that's pretty much it for the seccomp mode too. However, there is a lot of room for improvement and this is where Libseccomp came about, actually. The idea for Libseccomp came very early when the kernel patches for seccomp mode two were first going in, I was kind of looking at it and was like, this is really cool, but kind of looking at it from a developer's perspective, there was a lot of things that could be done better. It makes sense the way the kernel does it from the kernel's perspective but from an application developer's perspective, I think there was room for improvement. The biggest one is that the filters are ABI dependent. The most obvious thing here is that syscall numbers are not consistent across architectures. Open on one ABI might end up being I-Octol on another. And of course, the filters are written not with syscall names, but with syscall numbers. So that's something you need to be aware of. There's also things like the clone system call, which who actually thinks the clone system call is the same across all the ABIs, like the parameters. Okay, you're smart people. Usually, most people think it is, but yeah. There's a bunch of syscalls, which actually are completely different when you go to different ABIs. And not every ABI supports every syscall. So there's a lot of ABI specifics in here that you need to be aware of if you're writing GarabBPF. So that's something to keep in mind and one of the main reasons why libseccomp came around, the idea being that you could abstract away all of the ABI specifics into a common API. So you could write your application for libseccom's APIs and then not have to worry about where you run. The other problem is that the BPF language is 32-bit. And that's fine, there's nothing wrong with that. I mean, you know, been doing 64-bit computation with 32-bit hosts forever. That's not new. But you have to make sure you do that when you're comparing all the syscall arguments. And that adds a level of complexity to it, especially when we're talking about doing this in assembly language. And so perhaps asking application developers that, one, they need to go off and learn a new assembly-level language so that they can use seccomp. And two, it's going to be different depending on what platform they run on. And three, you have to convert every single syscall argument comparison you make into 32-bit comparisons. I can let you guess how those discussions went over. And there's other things we can get into on this things about, you know, the jump is only, I think, like an 8-bit value. So once you start writing long complex filters, you have to be careful and start putting jumps in there so you can make it. And like in the assembly-level language, you know, once it starts becoming more than, you know, a page or two, it starts to become difficult to maintain unless you're really good about commenting things and structuring it correctly. So basically that was why Libsec comp was born. The idea is, like I said, we wanted to provide a nice API for filters that was ABI-independent, worked regardless of 32 or 64-bit systems. And we didn't want application developers to have to go out and learn BPF and how to do that. We take an effort, we take some pride in the fact that we try to write decent documentation. We've got 33 different man pages for the APIs. We also have over 100 tests and examples written in both C and Python. We also have Go language bindings, but because of the way Go does things, that's actually often a separate repository so it is Go-like as possible. And we really try to make sure that we generate safe, optimized filters by default. So you don't have to put a whole lot of effort into that. And here is a bit of an eye chart. I'm gonna apologize. The one on the left, that's actually Libsec comp and I think you can see that a little better. The one on the right's the raw EVPF, which you really can't, I mean, that's hard for me to read on the laptop screen here. But you can get kind of a rough idea, like the red there, those are the actual actions. The blue is the syscall numbers and that green, that's the argument comparison. So I mean, that gives you an idea. You can see on the left in Libsec comp we're making three syscall argument comparisons and you can see what that expands out to in the raw BPF. So, and that's three syscall comparisons for one syscall. So you can imagine as this were to expand out, like let's say somebody was telling me that some of the container run times right now have over 300 rules for syscalls because they're taking a whitelist approach. So you can see here we're doing three and only one of those syscalls is actually doing any argument comparisons. So you can see how if you're running the raw BPF it gets real complicated real fast. So anyway, that's it for my part. I was gonna hand this over to Tom but before I do this, anybody have any quick questions? I don't know. I mean, I imagine you could make some arguments about not wanting to divulge that to an application that's potentially been compromised. You can check the system state. So you can, here's everything. Assuming that sec comp hasn't turned off the ability to check to see whether sec comp has enabled, there are mechanisms to see if sec comp has enabled. But in general rule of thumb, Libsec comp is a library. So if the kernel were ever to grow that capability, Kase? You can get the filters back out if you have checkpoint restore enabled on the kernel because that was one of the things they needed. So you can just read the filters back out. But it's not a general recommend. I don't recommend it. Yeah. Sec comp, it's sort of spread across a bunch of binaries, right? So we're gonna have a system that's gonna have a bunch of binaries using sec comp and it would be nice if we could understand the state of the system and it doesn't even have to be individual applications getting their own. But some other, let's say, policy monitor. So I think there's a few differences between sec comp and SCLNX. And there always seems to be a temptation to compare them, right? And think one could substitute the other. And they're not. I think they're very complimentary. And so sec comp, first off, is discretionary, right? It's up to the applications themselves. They could inherit it, true, and in which case it's not really their choice. But the other thing I'll say is if you're trying to look at this from a global system security policy point of view, a good sec comp filter in my mind, don't think of sec comp as a way to enforce access controls between an application and its various resources. That's SCLNX, that's App Armor, that's SMAC. That's what Nellis Emmett's for. In my mind, a good sec comp filter is basically a way of saying, okay, I don't need this kernel functionality. I don't need this particular mechanism. So I wanna shut it off because if I'm compromised, I don't wanna be the attack vector into the kernel. Absolutely. And what I'm saying is like if we're building a system, I need to be able to look at the system and know what the state is, right? Like not just assume or guess or like hope or... Well, yeah, I guess some of it also comes down to when you talk about state, you know, what do you mean? I mean, we could probably talk about this for the next hour, so I'll just kind of pun, say that outside of the checkpoint stuff that Kace just mentioned, we don't really support that. But that said, if the kernel ever were to grow that capability, we'd include it in libsec comp. Sure. Any other questions before I hand it off to Tom? All right. So let's all hope. So this question's come up a couple times today. Whitelist or Blacklist? As the LXC guys said, whitelists are generally better for things that you trust. Or excuse me, for the further unprivileged, or for the privileged ones, sorry, I'm getting his words all mixed up there. And then they use Blacklist for his unprivileged ones. At Oracle, we've found that the whitelists are good for the smaller, the surgical cases. So in other words, if you wanna run this one little app in your one container, a whitelist is really good because you wanna allow these 12 CIS calls or 15 CIS calls or whatever. They tend to get a little more challenging as the whitelist gets larger. For example, Docker has a 305, last I looked, I think, CIS call whitelist. And again, their goal is to block those 40, 50 odd CIS calls that they don't trust. The snag with that is it's a 300 CIS call whitelist. So you have at least 300 BPF instructions that the colonel's gonna walk through. Now we do have shortcuts we do in the assembly we write in Libset Comp. And so we may not walk all 300, but if you happen to hit the CIS call that is number 299 in that list, you walked most of that CBBF filter and that's low. We have a couple of optimizations we're working on right now to make that faster, but again that is as tricky. The other downside then of a blacklist, so say you wanna block exec for whatever. The colonel has a new CIS call. We heard that a few are in the pipeline. That new CIS call gets added. That's not explicitly near blacklist. Now that suddenly got allowed. So you may have another attack vector. If you don't keep your colonel in sync with Libset Comp or however you're writing your set comp filters. So there's a big risk there if security is your highest priority that you could accidentally have a hole when you do an update. So other considerations that you should take into account, do you wanna block the entire CIS call? So for example, do you wanna block all of open or there are just some sub features of it you wanna block? Maybe you wanna allow them to open DevNol or whatever. Obviously that gets a little more tricky, the more specific you get with your filters and your arguments. Do you wanna support additional architectures? Like Paul mentioned, so most of us are running x86, 64 machines. Are you still running 32-bit applications? If so, that's technically another ABI and you should have a filter for that. Otherwise, well either it's gonna be fully allowed or fully blocked depending on how your system's set up. Then a couple be carefuls. Be careful with strings. Setcomp uses the pointer itself. So if you're not careful with, and we'll do this in the tutorial, if you're not careful with your pointer, it won't allow or if you have multiple copies of the strings at different addresses, then you may need to allow those multiple copies. And then finally, Paul also mentioned this, parameter filtering across architectures. So some CIS calls, the parameters will be different. So what's parameter two in one OS or in one architecture? Maybe parameter three in another. That can easily be a gotcha. Right now there's no graceful handling for that. Generally that's an exception case if you're gonna handle that within your code. All right, who wants to add some code? Cool, all right, you got him out of here. Sorry, bad joke. So let's make a file. All right, and feel free to jump in if you have questions, just raise a hand. This is meant to be more interactive. I've written these filters before, so. So one question that I've just started to get into the libsec comp in the past week. And when you talk about blacklist versus whitelist. Yes. Could that be characterized as, say, the default action is not allow? Yes, exactly. And your rules that you add are allow, that's a whitelist. If you say yes, that would be a whitelist. You have whitelisted in the CIS calls you wish to allow. So in general, that will be a slightly safer filter because if new CIS calls show up, they will default to the... And in that case, you don't have to worry about other architectures either if you want them blocked, because they're blocked by default. By default, they will be blocked. And by default, they will be blocked, period. The question is, again, you're on an x8661 machine, that 32-bit application wants to run. Uh-oh, by default, it will be dead if you have loaded that filter. Again, remember, set comp isn't loaded by default, so obviously it'll just run, period. So then as a blacklist, anything that is an action other than allow? Yes, so blacklist inverted. So for example, say you don't trust fork, exec, clone. So you would explicitly say in your filter, set comp, do not allow fork. Set comp, do not allow clone. But your default in that case is... And then the default then would fall to generally an allow, yes. So anything else open, read, write, whatever would be allowed. Sorry, just one more question. No worries, that's why we're here. Yeah, so if you try to produce a filter where let's say your default is allow and then you add a rule for a CIS call that is also allow, that throws an error. No, it's totally legal. And so this is where, I mean, so again, this is just a, you're building up a set of assembly instructions. And so in that case, you would say if CIS call equals open, I'm going to explicitly allow this. Other other thinking, oh, I fell through all these, nothing else happened, also then allow. So that should work. That would totally work fine. Obviously it's, Paul's got to, do we actually catch that in libset comp? Okay. So yeah, if in libset comp, you'll notice when he starts going, there's a, you know, an init. And in the init, you give it the default action. So if it doesn't match anything, you know, kill, allow, whatever. And so libset comp, because, it does this as a way to help you because it doesn't make any sense. You know, if you're saying that the default rule is to allow and then you also say, I want to allow open, it's gonna return there because well, just don't write that rule. Your default is to allow. So yeah, that's... Cool, thanks Paul. I was trying to produce something that was general enough to do that and getting errors. So I just wanted to make sure I understood what was going on. From an assembly point of view, it is legal from libset, thankfully. Yeah, libset comp doesn't, doesn't guarantee any ordering in the rules. So like, you know, you might call rule ad one, two, three, four, thinking that that's how it's gonna generate. Because we do a number of optimizations and we don't want it to always be stuck with a particular optimization, we wanna be able to change it, we don't provide any hard, fast guarantees about the ordering of the rules. You said it throws an error because it doesn't make sense, but it might make sense if a application later on changed the default policy or a child process changed the default policy, right? So like, I wanna say allow, allow, allow, allow, but like my regular policies allow and then later on do not or you know, kill. So why do you, yeah. So we don't allow, so in that case of default being allow and then you add a rule for open to say allow. We don't allow that, but what you're talking about is when you've got stacks, multiple filters. So that's a different filter. No, no, they are, but I'm saying like, you can have the first filter have a default of kill and the second filter be a default of allow. That's acceptable. And then the field of fact, right? That's what I'm saying, I'm like restricting it down. Okay, so if you're going the restrictive approach like we talked earlier where you've got, you know, you wanna do your initialization then later like, all right, now we're doing the real risky stuff, do allow. So obviously you need to make sure that your first filter you install has a default of allow. Because otherwise if your first filter defaults to kill but generally if you're doing that approach where you're writing a very permissive filter and then you're iteratively making it more restrictive, general approach is your first one, the default isn't kill. Because of the nature of what you're trying to do. So in practice, no one's ever come back and said this has been a real issue. Just by virtue of the way people write code and people put filters on. So that's the way it is. So let's first just make a really, really simple C program. We'll just do something silly like we'll fprintf send that standard out main. All right, and we're probably gonna need some pounding clues, right? So we'll pound include, let's see. We'll do some standard, oh. And let's see what that happens with this. So our function's pretty lame. All right, great, we have a function. Congratulations, C101. Let's make a child process. So let's do some forking. So we'll do see PIDT, we'll make a child PID. And again, pretty free to jump in if you have questions. Obviously this part rather mundane and boring, so. So we'll say child PID, we'll fork then. All right, so we've now created a process, okay? So I guess we should be good citizens. Yeah, we will. If C PID equals equals zero. Actually, we should do some error checking first, right? We'll print out that our fork failed for some reason, out of processes or whatever, and maybe we'll exit. So we'll kill it, probably need some more IO or we need, oh yeah. So ah, sorry, thank you. Yeah, we probably should write that standard out, good job. Thank you for, oh man, I do not like this keyboard. As you can tell, this is not my usual setup. Okay, so we'll probably include some standard lib, and let's see what else do we need, how we need fork. All right, so let's see what this does. Okay, we obviously didn't change any printouts yet. So we forked but we didn't do anything with it. So now let's do something with it. So we'll say if the child PID equals equals zero, now we're in the child. We'll just say for now we'll f print f, else this'll be, ooh, we need to close that. Thank you somebody, I heard you say that even though it wasn't out loud. Okay, nothing too exciting so far. So now we've seen we're in main, we're in a parent, we're in a child. Okay, this is where we'll start introducing some set comp. So feel free to jump in if you have questions. I'm gonna branch off to a function here just to keep this a little less spaghetti, although it's gonna get spaghetti by the end. So we'll say call child. Obviously that's a bad choice of a name but that's okay. So typically this is the way where you would introduce that comp. You don't wanna introduce it before you do the fork because that would be a sys call you would have to allow. So this is a common mistake you would see. In newer filters they would do set comp here. That's obviously not what you would call it but they would do that there. Suddenly fork would have to be allowed in their filter if they wanna really lock this down that child process would also then be able to fork. Now maybe you won't allow that, maybe you don't but let's say we don't wanna allow that. So inside of our child then we're gonna build up a filter. And so in set comp, in lib set comp we use something called an SCMP or set comp filter context. And this is what we build up the filter inside of. It's basically a state holder inside of this function and then once we build it up how we like it we're gonna send that into the kernel. So we'll need to include set comp.h for this. If you are on a YUM based system so on my system I would have to do YUM install lib set comp YUM install lib set comp dash develop similar idea if you're on a Ubuntu system. All right so first thing we'll do is we're gonna initialize our context. So we'll call set comp init. And now this is where we're gonna decide do we want a whitelist, do we want a blacklist. Current thoughts in my opinion I like the whitelist better. Again there are potentially performance trade offs especially if your filter gets large. Leonard and system D currently prefer a whitelist and they like to return E-perm as their error. His thought process is and I think this is valid. That'll make it look like it doesn't exist. Hopefully your system or it's not supported. Hopefully your system will fall back and think it's on an older kernel and try to do what it did prior to that thing existing. That's their vision. It seems reasonable to me. So I'm gonna say scmp and we have a bunch of then helpers like this and we'll say E-perm. So by default we're going to initialize this filter. If the syscall is not in the whitelist we're gonna return an E-perm. And so let's actually not go too much further just to make sure my code isn't crazy. So at this point I've created an empty filter. I haven't loaded into the kernel. I haven't had any rules. But just trying to make sure that my code is same before I have too many typos and too many bugs. Okay, so I've got an error there. Let's add our pound include. That is not right. Okay, so let's see. Undefined reference to setcomp release. Oh yes, okay. So now what I need to do. So I'm calling setcomp properly but I now need to compile with it. So we need gcc-l setcomp and then I need to tell it my file I'm compiling. Hey, now we're happy. Oops. So let's call it. Okay, so as before, we're not saying anything new and exciting and we're still C-101 here. So we've got our filter. Let's now do a little bit more stuff with this finally and maybe finally get to some real setcomp. Any questions as far by the way? Yeah, let's get in my, otherwise the viewers at home are gonna hate us. Where did you say whether it was a whitelist or a blacklist? So I've implicitly stated that. And where I've said it right here is my init. And so what I've stated by this one is if the syscall doesn't match a rule that I've said my default is going to return an E-perm. So I've explicitly stated that this is, I've explicitly stated this is E-perm which is implicitly saying I've made a whitelist. So everything that I then add after this will likely be some sort of allow. And that's where I'm gonna build up my whitelist, my filters that I'm allowing. Make sense? It's kind of not tremendously intuitive but once you play with it a little bit it will be. Oh, okay, so yeah, let's add a little comment. We can. Hold on, let's repeat that for the week. Yeah, so the quick. I just said if you show example of blacklist also how different it would like maybe it would be. So I would do something like this for a blacklist and obviously doing these both at the same time doesn't make sense. So this is a bad example in its current form. So if you're doing a whitelist, one question, normally you would be adding allow rules for specific syscalls but correct. Is it allowed to add, let's say your blacklist, your default action is, let's say log. Can I explicitly add rules for syscalls that would make them? Yes. Certain syscalls would be an error and some would be kill and. Correct, correct. In fact, you can, one of our tests in fact, we ran into a really quirky bug. And basically I did a bunch of pretend syscalls, syscall one, two, three, four, five, six, eight, nine, 10, 12. In each one returned error, no, one, two, three, four, five, six, eight, nine, 10, 11, 12. E, perm, E, whatever. But we were just using numbers. Again, just to try to flash out the bug. So yeah, totally legit, you can have different numbers but as Paul said, it's explicitly comparing. So if your default is error, no, number five and you set a rule then error, no, number five, then it will yell at you. Still alive? Everybody still awake? Okay, let's add some rules. So we're gonna, like most good apps, we return error code. So we'll say error code and we're gonna say set comp rule add. And so we need to pass in our context. So it's gonna build off that context each time. And then let's add, so let's allow. Oh, and I have a typo in my blacklist. I forgot, by the way, there we go. So thanks for catching me. And let's see, what do we wanna allow? So let's do, let's just allow write. All right. So I've added a rule to allow write. Again, I'm passing this into my context, allow. The syscall is right. This is our preferred way of using it. Again, because the syscall number for write may not be consistent across all ABIs. This will let us, in Libset Comp, go and identify the right syscall number you've had in your mind and get that mapped in. And then the last zero is telling it that there's no parameters we're gonna be filtering. I'll play with that in a second. So we allow variable arguments. So if we wanna filter on extra parameters, we can. But for now, let's keep it simple. So we're gonna allow a write, and then we need to load this into the kernel. So, oh, I suppose I should check my error code too. Can I offer some heckling from the back? Please do. Set comp rule at the third argument should be a one to indicate that we're doing one argument comparison. Third argument. Third, third, third. Right here. Before the scmp sys. Yep. Oh, yes, good spot. Are you saying that wouldn't have compiled? Yeah, and it probably would have given you a really weird compiler, which is why I felt I'd mentioned something. We're debugging as we go, thank you. Somebody's example code they have written up here is in a size 10 font, and I don't have size 10 font glasses right now. All right, so if it's lesson zero, I don't know, we'll do some error again. I should probably write that to standard error, but rule add failed, R C, something like that, and then we'll return. And that's actually bad. I should probably say go to out, or I'm gonna have a memory leak, and then we'll say out. All right, so at this point, we've got the rule added. We're checking to see if the rule add failed, but again, we still don't have it in the kernel yet. So let's add this to the kernel. So we'll say set comp load, and I'm gonna heckle you again. Please do. Actually, but this is more of a heckle for myself. I do apologize, that one I told you to put in, take it out, because you have this error at the end. That's my mistake, I'm sorry. Yeah, no worries. Remember why I told you earlier that Tom was a superman for doing this, and that I would never do it? This is, you're now seeing why. No, it's a good, so Paul and I talked about this over lunch, and it's stressful to stand in front of people and talk about things like this, but it shows that even people who've been living and breathing this for quite a while make mistakes. This isn't always straightforward, and it's easy to have a screw up where maybe it won't compile, or maybe even more nefariously, maybe you'll think you've built the filter right and you haven't, so get more eyes on it, work with it, feel free to contact Paul or me. I just put you on the spot there, sorry about that. But let us, I mean, if you want me to look at your filter, I'll look at your filter. I'd rather have secure systems in the wild than people thinking they know what's working and it doesn't, so yeah. Yeah, yeah. Yeah, say it to the mic, yeah, perfect. Sorry, to repeat, I just, it's easy, it would be easier to verify that what we wanted got in if we could get it back out and print it. I think you get that, yeah. That just shows you that the kernel recorded what you wrote. You actually wanna test that it behaves the way you want, so you don't actually wanna pull it out, who cares? You wanna be able to test the filter that you handed it. And there's one other thing I will say. Yeah, you guys should sit next to each other. And what, the other thing I'm gonna say is if you've looked at, if you've looked at the BPF that you get from a SACCOM filter, if it's beyond just a few lines, you're now into the problem of having to analyze assembly code and reverse engineering that back into rules. I mean, I'm not saying it's an impossible problem, but it's not as easy as analyzing SCLinux policy. And just to, I mean, on case, you're right. I can see what I sent in that is gonna be what the kernel has. The harder case is when you have a bunch of stacked filters that are coming from different processes, right? Like I can set some filters in system D and then my process can set some more filters, right? And that's where we're gonna get into a, man, what the hell is even going on on my system? All right, so, as we said, we've loaded this into the kernel. Let's add a quick little error handling there just so we can know in case something went wrong. And then there we go. All right, let's see what fails on this compile. Nothing. So, we've created a really simple rule. We've loaded into the kernel, but we're actually not doing anything yet. So, when we run this, it still looks the same. Now, let's start being a little fancier. So, after we load it, let's call it right. So, we've, oops, wrong button. There we go. So, we're now calling fprintf after we've explicitly allowed right. And theory, this should work. Will it? I don't know. Oh, it didn't work. Hmm. Ideas? Check DMessage, I like that. I got some protection faults. That's kind of fun. Not particularly user friendly to read there, but, hmm, other thoughts? Audit log. Audit log. Let's see, where is my audit log in this machine? Let's see, where is that located on this? Do you know? Audit? I would type AUsearch. AUsearch. Do I need to be sudo for that, Paul? That's a good question. Yeah, I think you probably do. Yeah, throw sudo on there. Less than that. Can you search dash m? Dash low, okay. And then sec comp seconp, capitals. Maybe lowercase works. I don't know, I always use capitals. Return, yeah. No matches. Hmm. So, okay, let's look through and see what we've done here. So, we've said ePerm, we're allowing the right. Oh, what else do we want to look at here? And after the load. This is an important point that needs to be recorded. So, let's do this in the microphone. When your default action is an error, by default, lib sec comp does not log to audit log. So, but there is an attribute, I forget the exact call, but you can set the attribute so that it logs basically everything except for allows, and then you'll see a message in your audit log. The other way to do it is to just make your action kill instead of error, yeah, yeah. So, I was gonna say, yeah, you're right, there is recent builds of lib sec comp and recent kernels does have a much improved logging capability and what, I don't know. Act log, okay, thanks. Yeah, and I was gonna say the people who did that are here in this room, I saw them, but there it is. So anyway, so thank you for that. But the other thing I would suggest that's also handy before we go that route, got a friend of mine who says the most powerful debugging tool in the world is S Trace. That might be something that would be useful showing here. Now, this is interesting though. I changed the error from ePerm to act log and it printed out, so, but there we go. There's our audit. Good question. Yeah. If you had left it as, the way it was is ePerm, would the fprintf return code have been ePerm? Return value. So, here's what we had before, okay, like that? Yeah, and then at line 33, if you get the return code for fprintf. Ah, yes. Would that have? Ha, ha, ha, exactly, yes. So now what are we gonna do? Sorry, if you just run it through S Trace, you'll see the return code too. Let's do that, because obviously I don't have a graceful way without going to like GDB or something. Oh, thank you. Cool. One second, well, I'll steal that for you in a second. I wanna caution if you're on an old distribution kernel, you're not gonna have a logging capabilities. So, I mean, the logging capabilities is cool, don't get me wrong, but you're not gonna have it on all systems. So, interestingly, I must not have recompiled it or something, because it is now working, and I'm gonna say I know why, but I really don't, because that really wasn't a question to the user, I really expected that to work, and as you can see it did. But anyway, S Trace not installed. Like I said, I don't use this machine tremendously often. Other questions while I wade through YUM, that wasn't too painful. So there we go, this one actually allowed it, but let's not, and let's go look at the S Trace. That's the fun thing about these, you never know which direction they're gonna go, and I can't always say I'm ready for it, and that's okay. We'll just say error 042, arbitrarily picking a phone number, okay, so we've recompiled it, let's see what happens. Oh, yes, good point. What's the option for that, do you recall? Dash FF, thank you. There we go, and so we got an ePerm on the exit, which isn't a surprise, so in this child, it's sooner or later going to exit, but we never explicitly allowed that, so that will obviously not work, but we don't care, because we've kind of done all our thinking prior to that, oops, ooh. And our write then returned, let's see, message, and that's 42, thank you. I'm glad you remembered, I should've thought of a number. You looked it up, good, thanks. I knew that, so. Quick question, yeah. Is there a way, so I saw the audit behavior was excellent, is there a way to take the audit log of a running process and say, okay, here's what this process is supposed to do in its correct behavior, can I just turn that into a policy? I don't think there is, but interestingly, I may have known a guy who was working on a similar idea, and it went half-heartedly, so if you're interested, give me your contact info. I'm interested in having it, I don't have time to write it. Alas, I was as well, but I don't have the time to write it. I know there's interest in either statically analyzing a program or running it or whatever and saying, here's what it should be calling, allow these. I don't know that there's anything that exists at the moment, so we're still on the same page? So let's switch this rule back to and allow and I don't know, let's add some parameters to it. So I've changed that last zero, so I was on line 19. I've changed it now to, let's change that to a one, so in other words, we're gonna say there's now one parameter I'm gonna filter for this particular rule and I don't know, let's see, what do we wanna do? So we'll say scmp compare, oops. And, oh, excuse me, I need it, we'll see, we'll do a zero. And we'll do scmp compare and we'll say equal. And if you've looked at BPF, the actual assembly language, you'll notice that this is very reminiscent of it in some places and there's only so much we can hide it from you. Unfortunately, this is one spot where you kinda see some of it. And I'm gonna allow standard, we'll say standard out file, so nice, did I get the right number? No, that's the right number, okay. So what this rule is now doing is I'm allowing the right syscall if and only if it's to standard out. So if I try to write to anything else, it should get blocked and in fact it should fail with ePerm. So let's see what happens. So we'll leave it as is right now, get rid of that for the moment. Okay, it compiled and let's run it. Okay, so it's still allowed, that's good. That's kind of what we were hoping for. Let's try to change that a little bit. So let's now write the same thing to the standard error. So in theory, if this didn't work, we should see hello world twice. In theory, if this didn't work, we should see it once. What happens? And we only got one hello world. So it blocked, I'm gonna argue the second one, we don't actually know, let's verify that quick. So as you can see, it blocked the standard error one. So it did go and properly process that argument, say I'm only allowing standard out. Pretty cool. Questions on that? Now to get to, I'm going to show this just because it's annoying and sooner or later it will bite you. I'm not fond of this. So strings, strings can be awkward. Again, it does a pointer comparison and that pointer needs to match exactly and there's a bazillion ways that it can go wrong. Here's one way it can go right, maybe. So in this case, we're gonna say, we're gonna look at parameter A1 this time and we're gonna do a compare equal again and I'm gonna intentionally cast this to be very careful. It may work without it, it may not, I won't guarantee. And I'm just gonna call something LSS. And up here, I'm gonna make a string and I will say static const, character const and we'll say LSS and it equals 2019. All right. So we've created a string, completely constant, both the pointer and the value. And I'm now saying in parameter A1, I'm allowing the printing of it. So, but we've got to print us here, which are not pointing at that. So these two are gonna be blocked but if we allow, if we print the standard out, which is the one we allowed and I say LSS, we should see that on 23. Oh yes, thank you. I'm glad you're watching it, not me. All right, it compiled, good call. Oh, but we didn't get it. All right, what did we do this time? So let's see. And again, this one isn't a trick. I don't actually know the exact answer. Like I said, I've been bitten by the string stuff enough that it's annoying. I hate to say it, I feel like, oh, dot-gon keyboard. Is this just utterly annoying when I go the wrong way and it's all scrolling like that? That's gotta be horrible. This should make no difference. But let's see, still did not print it. Yep, I think we're gonna ask Tracy because I don't readily see the why. I would try getting rid of that cast. The cast, okay. Yeah, because remember, stuff that most of you probably don't care about, but we did have a bug and we changed around some of those SCMP 80s and the A1s to better support 32 and 64 of it. And I think it might be some funny with that cast. And we may not, I may not actually get it. It actually puked on, oh, that was just a warning though, wasn't it? Let's see what happens then with that warning. Yeah, so this laptop, I got it working on this earlier and I don't remember the exact syntax. Again, though, this is a good example of string matching is very, very awkward. I would use it as a last resort. I think a better answer would be to do the user notification stuff that recently went in. Use that to watch and watch what you want, especially in something like this. Oh, I'm gonna allow you to do standard error, standard out, dev know whatever. And then return back to the kernel, allow that rather than trying to play this string matching game. I don't remember what, I was looking in one program and they had a string that was reproduced in it like 30 different times. And so had they wanted to do the string matching, they would have had to add 30 rules for each instance of that pointer wherever it was located in their code. It was insane. I don't really see a need, go ahead, there's over there, Ted. So I don't see a need to burrow into this rabbit hole unless you have to. Can you raise your hand again? I didn't see that, that was Ted. Sorry about that. Yeah, is it because you're using buffered IO and so you're using fprintf if you did a direct write, I suspect it might work? It could be, I swear that I had it working on this laptop, you may be right. Like I said, I don't want to burrow into it other than to show you that even people who play with this fairly frequently don't get it right immediately. You could dump the raw BPF and we could look at it on screen. Oh, that's for later. Let's get the mic just again for the poor schmucks on the phone. Sorry, can you raise your hand again? I didn't see that was. I was gonna say that you have the standard error still in there? I may actually, that's a good question. That's the reason why it's probably flunking out before. Oh, good call. Yes, thank you. Yeah, so we bombed before that. Yeah, let's, in fact, let's get rid of both of those. I realize it doesn't matter, but I think you win the prize. No. Good try. I really thought you had that one. So it shouldn't be that. So like I said, I've tried this at home and it's been happy. I'll try it that way. Just switching it to a pointer instead of an array. I don't, so, so anyway. Could you grab the, Paul, can you get the mic please? Thank you. Sorry, once again, can you raise? Yeah. So there is a scmp underscore a zero underscore 64 version of the same rule. Just maybe that is a problem because you're casting it to 64. So you think I need a underscore 64 on these? Maybe I haven't tried it yet, but I think so. I haven't tried it either. Let's see what happens. Yeah, it shouldn't matter on the first one, but for the A1. So anyway, like I said, I fully expected this part of it to not work and that was not tremendously unintentional because this is, well, again, awkward. Let's see, other directions we want to go. So we could S trace this. Let's see what happens. ff dot slash a dot out. So let's see, we got an E perm, which isn't a surprise, that's our default action. It didn't say why. If we wanted, Paul said we could dump out the BPF, that's what I would do at home, but I don't know that that's age appropriate for today. Sorry, that's not age appropriate for 430 either. Other directions you guys want to go. This was really mostly what I was thinking about covering. We could add more, like for example, we could allow some exits and things just to make this child fully happy. Obviously, I'll get rid of these silly string comparisons. It's up to you guys. What are you guys interested in? Anywhere you would like to see this go? Who said that? Do you need a backslash N at the end of LSS maybe? That is quite possible. In fact, you know what? You've intrigued me enough that I will try it. I thought I was done with this. We thought we were done with this. All right, so let's add a backslash N. I like that idea. Maybe it didn't get flushed or something. Oh yes, I have angered it again. Let's see, so why doesn't it? Line 21, what's on point? Get rid of that parentheses and the semicolon at the end of that line. Oh yes, thank you. Yes, yes, that I had just added. And now we need a two there. There we go, thank you Paul. Okay. I feel like we should have a prize for this. All right, so get rid of the cast, do scmpa1 underscore 64. All right, that's a very good point. I will say, I had this work on this laptop. Switch it, change it over to a, yeah, change it over to right. So maybe the problem is that because you disallowed exit and then process dies and there is no call to FF flush. That's part, then that is, this is a problem. Yes, yes, like I said, I didn't expect this to work. Syntax for right, anyone handily know it? It's in the S trace, yeah, you just, now you need the length at the end. Okay. Size of LSS, something like that. Plus one, probably. Plus one. And it's, I mean, just, oh yeah, I was thinking Stuart won. Thank you. This is what, no, this is good. It's more fun to hear what people come up with. File no, 39, thank you. Oh, I thought that was good. Are you saying I should do like we had a standard out file no, like I have up there? Oh, it was more fun the other way. Just put one in there. No. Oh. All right, so we got, let's see. It compiled-ish. No? You wouldn't sell this code? Come on. All right, so now we're back to a more reasonable amount of warnings. Again, my machine doesn't like that comparison, but we'll live with that. Hey, hey, good job. Trial by fire, everybody bugged it, thank you. So that is a really good example, though, of all the various pitfalls you can face. I think that was better than me just sitting here and handing you code. Yes, go ahead. One last example you're missing is how do I do the same thing without messing up with my source? So how do I write it separately and load it without touching of my binaries and recombining? Ah, I got you. So as was asked earlier, you're gonna have to identify your sys calls and you're gonna have to allow that. So what I would do, if you wanna allow, if you have untrusted code you wanna run in this, so right here we'll go to main. So we're calling, we're doing call child. So go up to call child and you're gonna load in your filter and then at that point, you call that untrusted code, however we wanna get into that binary and it might be trial by fire just like we saw. You might be S-tracing, you might be printing out raw BPF if you have to. We also have a not quite so unfriendly and we call it pseudo filter code where we print it out and it's more like human readable, just let you know if you wanna debug that as well. But yeah, it'll be a little bit of trial and error. So yeah, I don't have a glorious answer, I'm sorry. So I'm not, it sounded like you were trying to say or you were asking if you could do this without modifying your binaries or modifying your source? Okay, so yeah, so system D. So system D, you can actually just sort of like preload your filters and then it'll just run with them. And system D has like nice little categories so you can just sort of like say no unsafe or you know, something like that. Yeah, probably outside of my scope and I probably haven't practiced enough for that one, but yes. The other thing that you could do if you're on a system that didn't have system D and. No, I don't think so. Yeah. The point being, if you wanted to pre-compute the filters and you know, write some simple little loader for your binary app, which you didn't have, we kind of joked about, you know, exporting the raw VPF. Libsec.com does have a provision for exporting the raw VPF. So what you could do is you could write a small Libsec.com tool that would generate the filter. You could export the raw VPF and then take that and feed that into another small little bootstrapping shim for your other program, which would load that raw VPF. Or more sane way, you know, if you were able to compute, if you had Libsec.com on the system, you could just write a Libsec.com program normally that would do this. And then the last thing that thing would do was it would exact your program, which. But I guess the one gotcha on that is you're gonna have to allow exec. Has anyone created a S-trace post-processor that you could run a program on, gather all the system calls, all the arguments, and generate the code from that? What's your email address? Yeah, there's actually been a lot of over the years. Libsec.com's not an old project, but it's also not a new project. And there's been several people that have come along and had various attempts at automatically generating filter either from S-trace output, some people have scanned sources, other people have scanned binaries. There's a variety of things. I haven't seen or I'm not aware of a project that is properly maintained that does any sort of S-trace S-comp automatic filter generation based on existing code bases. But as we said, that's not to say it's impossible. It's definitely doable. I mean, I think we can all see how that would work. It's just nobody stepped forward and done it outside of an academic exercise that I know of. Other directions, other questions? We got 20 minutes if we want, we can hack away. Can you talk a bit about, first an easy question. Once the filter was loaded, sec-complode, can we just get rid of, can we call sec-com-release at that point, is the context still there? Yes, yes, so once it's been loaded into the kernel, the context is no longer needed and you can free it whenever you're comfortable. Okay, and my second question is, what's actually happening here? At what point is the filter built? It's not being built as we go, is it? Not each rule ad is building. We have a big database of instructions, but they ultimately aren't put into the kernel until load. We have them ready to go and built up into that context, but no, they're not loading the kernel until we call load. Okay, thanks. One more question with regard to sec-complode-release. Does it mean that if you call this function, that it is not possible after this call or restore the context? So if you call sec-complode-release, after that you are not able to reconnect the context and drop the rules from the kernel. You cannot, no, it's, once you load a sec-complode-release in the kernel, it's there, you can't get rid of it. Okay, so. That process, I mean, because otherwise that would be a security hole. That process in any of its children, if you allow children, forever will have that sec-complode-release filter applied to it. Okay, thanks a lot. And again, the context is purely just our workspace, our building block area where we build this up, we remember some temporary states. So for example, again, Paul mentioned earlier, you can only jump like 256 BPF instructions. And so sometimes, well, Docker, for example, has a 300 instruction filter. So if we need to go from the very top down to the very bottom, we may need temporary jumps in there. So we can remember data like that as little placeholders. And in fact, one thing that we're working on is trying to speed that up by using a binary tree. So instead of going, oh, if it's equal to one, nope, oh, two, three, four. No, we'll try to jump into this tree and skip through many, many of the instructions. On some workloads, we've seen, in fact, some oracle ones. We've seen significant improvements. Yeah, for what it's, oh, sorry. I was just gonna say, I'm walking. The current approach that lipsec-com takes to optimizing the filter is to try and get a very high return density at the top of the filter, the thought being that think you're returned quickly, basically. There is also a capability, sec-comp, syscall, hint or priority. You can give lipsec-com some information. If you've got a very IO intensive thing, you're gonna do lots of reads and writes. You can tell it to give a higher priority that it will move that closer to the top so that you'll have less jumps, higher cash hits. When you're doing these programs, can you have some rules that are more general that might be an allow and then a more specific one that's a reject where ordering would actually matter or is that something that's not really encouraged? Because it sounds like, for optimization purposes, you wanna allow lipsec-comp to be able to freely reorder rules, but that implies that the rules can't be order sensitive. So in theory, yes, you could do, because obviously BPF will allow you to do greater than comparisons, less than comparisons, greater than equal, all the standard stuff. We currently are doing, if it equals equals, whatever syscall you specified, do what you specified. In theory, that'd be possible. I can't imagine that would be easy to build up programmatically, though. I'd feel bad for the person who had to build that, but yeah, in theory, that's possible. Right now, we don't do it. Yeah, I mean, there's always gonna be some cases where somebody's got a very specific filter and they're very performance sensitive, and I would say lipsec-comp is probably not for them. We're for 90%, 80% of the people out there, trying to make this more accessible and more available to application developers. Exactly, my guess is, if you're that performance sensitive, having to learn BPF is not the worst thing you've had to do in your job, so yeah. So there's always gonna be some cases where we're just gonna tell you, hey, write the raw BPF. I mean, we'll help you, because we spent a lot of time staring at BPF, but lipsec-comp is not gonna be the tool for everybody. And we've also, I know there's been a couple projects that have decided that they didn't want you to lipsec-comp because they didn't want another dependency, and that's perfectly fine too, and that we actually, we have an issue open. We talked about developing a BPF filter outside of it and load it in separately. One of the things I'd like to do at some point is develop a BPF filter into basically a header file. Let's see header file that you could just include in. So if you didn't want the dependency, you could still have the benefits and do it that way. There's other things as well, but the one thing I will say is it was in the iChart slide. There's some boilerplate and some other stuff that lipsec-comp takes care of for you. Yeah, like, see? It's line three and four up there. But anyway, no, there's certain things like lipsec-comp on x86-64. You have to be careful about x32. You have to be careful about x86. There's also, I think a lot of people who write raw BPF get that wrong and they leave themselves vulnerable because they're not taking care to do that. There's also annoying little things when you start talking about P-tracing and allowing the negative one syscall. There's stuff about that. There's also other ABI weirdness. If anybody's familiar with the socket and the IPC system calls on x86, you know they're multiplexed except a few revisions ago. We now got the direct wired versions. You have to make sure that when you're writing filters for x86 that you support both the multiplexed versions as well as the direct wired versions. Otherwise, you could have this real fun situation where it fails on some kernels but works on others and you're trying to figure out, why is that? Well, because on the newer ones, you'll see it's called the direct wired version. There's a lot of gotchas in here. That lipsec-comp just takes care of for you so you don't have to worry about it. But if you're going your own way, you have to be careful of otherwise you're gonna have some really weird air conditions. Yeah, Paul, you have a question in front of you. I'm sorry, oh, there you go. So does that mean there's a hypothetical future where lipsec-comp could be replaced by just a compile time tool that converts from this lipsec-comp to Robupf or are there other things that lipsec-comp handles at runtime that can't be replaced? So I think there's one thing at compile time, you don't necessarily know what system you're running on. So you don't know the kernel support and you don't necessarily know the APIs that are being supported, right? So those are only things that you're ever gonna be able to know at runtime. But, I mean, at the end of the day, it's all just software, right? I mean, there's no, you can look at the sources for a lipsec-comp, right? I mean, I always hesitate when it comes to software to say, no, you can't do that because there's a lot of smart people out there that do really clever things. But yeah, there is runtime stuff that goes on that would be difficult to do at build time. From an embedded perspective, let's say I wanted to run a busybox with lipsec-comp for a very small number of applications because busybox would basically be my init. Is there any BKMs for how to do that or am I kind of gonna be left blazing my own trail there? I don't know, Paul? Yeah, so that's an interesting point because you've got a shared library, right? So without having looked at the busybox sources, I'm gonna make some assumptions that you've basically got some sort of selector at the top that says, how was I called? And then it goes off and calls a function, which basically does, okay. So I think in that particular case, you could do very similar to what we saw Tom do where he created the callchild function and then he actually loaded the filter in the callchild. What I would almost recommend doing is doing that same thing where you have that function which basically emulates LS, for example, I'm just tossing one out. You'd go ahead and you'd write the filter just in that code, just in that function that handled LS and that way you could target specific functionality. That would be my suggestion, but there's probably other ways too. And I just wanna say, if you have questions like that beyond today, feel free to bring it up on the Libsec.com mailing list, file an issue in GitHub. We're more than happy to help out on the caller side. I mean, obviously we're not gonna be experts in your application, but we'll do our best. The Go bindings for Libsec.com automatically whitelist the system calls that the Go runtime makes for you that aren't under your control. So the Go bindings, I believe, do not. And I would actually, so I, responsibility for the Go bindings is somebody else. I kinda kicked that off cause I'm not a Go expert and he was much better at it than I was. So that's why he's doing it. But I would kind of almost recommend against that just because I don't like having default rules like that because if the implementation changes and whatnot, but there are also under github slash sec.com, we have a few other projects. Libsec.com is the main one. That's got the C, the Python. There's also the Golang bindings. There's an artwork repository. There's also a Go container. I'm just gonna call it toolkit for lack of a better term. And that does have some defaults specifically for container engines and whatnot. So yeah, it's best I can answer that probably. Other thoughts? All right, well, how about a big thanks for Tom for standing up and fielding questions on the fly.