 Hey, and welcome to my talk. My name is Mahthesh Patilkisarajan, and today I'm going to present to you a talk on weak runtime irrelevant typing for security, a short paper by me and my co-author Augustine Mista. So in essence, this paper is about type systems. Well, and what is a type system? Well, they can be good because they make sure that your code follows the specification. There's no way for you to impart a partial specification of your code to the compiler. But the problem with formal specification is that they don't really leave any room for ambiguity, right? So when you're writing your code and you're kind of still in exploratory mode, or you're working with kind of complex type systems that enforce very specific properties and you don't quite know how to use them yet, it can be quite bad. And especially when you're doing prototypes, right? Because when you're writing a prototype, you don't really know what the specification is. You're still figuring it out. So as a running example, in this talk, we're going to be talking about labeled data. And there is a library for Haskell, and it works for the GHC compiler called MAC, which was introduced in this paper by Alejandro Russo. And it allows us to label values. So we can put data into boxes, and then we can label those boxes with the sensitivity of the data. Now, let's look at some code. So here we have the box, and the box has a label. And it's crucial to see that the label is on the left-hand side of the definition. It does not appear in the right-hand side, and that means that it is a phantom type. Now what are phantom types? In Haskell, phantom types are types that are not present at runtime. They are entirely erased during the compilation process and during type checking. Now so here we have the class for the labels. They can either be public or they can be private. This is to say that this data is either not sensitive at all, and it can be released to the public, or it's very private or like a password and should not be allowed to be spread. And we kind of explain that relationship using this less class. Now what does that mean? Well, it just says that public is less sensitive than private. And then it also says that anything is less sensitive than itself. Now we see that this class here is an empty type class. That also means that it's runtime irrelevant. It doesn't have any methods, which means that it's entirely erased during type checking. Now why is this important? Well, we want to be able to write code that uses these type level guarantees. But if we get them wrong, we still want to be able to run that code. And the thing is that the GHC type system, or Haskell's type system as implemented in GHC allows that, because you can model these security properties. But for the functional correctness of the code, they're not actually that relevant. They're only relevant during type checking. So let's look at some values, right? So here we have the salt. The salt is a public value. So it's an integer, and it is in a box, and the label is public. And then we have the password, and the password isn't also in a box, but the password is labeled as private. And so the last thing I want to explain is this max type. What does that say? Well, it just returns, it's just a function on the type level and returns the maximum sensitivity of either of its parameters. So it essentially says that, yes, if you have two parameters, one of them is private, you return the private one, otherwise you return the other one. OK, so how can we work with these values? Well, because they are new types, we have to first box something. So we have to kind of box integers into a value. And then to use them, we kind of pattern match on the boxes. We unbox the values, and then we reuse the data within, and then we have to re-box it. And this combined function here, it actually says that, OK, you give me two values, box A and box B, and then it compares the labels of those boxes, and it makes sure that the output has the label, which is the maximum of the two labels. And this is essentially saying, if there's any private data that enters into this computation, we want it to be private. We want the output to be private. We don't want to leak information via this channel, for some sense. And you can see here that if I use it correctly, I combine a password and a salt, and I call the hash, the resulting hash is public, right? Because there's some private information in the password that went into the computation. So the resulting output should be private as well. And if we look at it here, we say hash one, and we get a box with 84 in it, right? 21042 is 82. Okay, but what happens if I have a value that is not in the box, right? What if I try to use it incorrectly? So then we look at this hash two here, and we see that, okay, I'm trying to combine the password and salt, same as in hash one. But I want the output to be public, and that's not correct, right? And GHC tells us that, right? It shows us here that it couldn't match public with private. But the trick here is that public and private are labels, and they are only phantom types. So they're not actually present during runtime, which means that we can ignore this error at runtime. And how do we do that? Well, we introduce this kind of new mechanism that allows us to weaken the type system. How do we do that? Well, we introduce rules that instead of just returning an error, they return a message. So we allow the program to compile in the sense we weaken the type system. So after we've tried to compile it normally, we weaken it to allow more programs to compile. But additionally, we have some extra warnings in place. Now, let's look at the first rule. First rule is the discharge rule. And what does it say? Well, it's saying that if we're trying to compare the equality of two values of label A and B, and they have the discharge class defined, then it's okay for us to accept it, except we add this message defined to the output. So we turn the compilation error, essentially, to an error. So we do that by defining this discharge class. And it's saying that if you're trying to discharge a public and a private, and it's essentially saying, if you have two labels, because we know as the domain experts, we know that labels are always going to be runtime irrelevant, we say that it's okay to discharge them, but return this message. So what happens then? We enable this rule, and as it's enabled, this is compiled again, and it turns this here error into a warning. So now it's saying forbidden flow from public to private by allowing discharge, which is this message here, but instead of being read, it's no longer an error. It is a warning, which means that here I can still run the code, which means that as I'm prototyping, even if there are some security related errors, I can still work with that code. Right, so let's go on. So here I have another example, right? So as we work with these values, right, we have them in boxes. We always have to re-box them, and we have to unbox them and re-box them and unbox them, and it can become quite tedious, because we kind of just want to work with them as if they were regular values, but because we added these extra phantom type parameters, now we have to box them and unbox them constantly. So to get around that, we define this promote instance, right? So it's similar, it's actually a special case of discharge, but it's saying that, okay, if you have an A, like some data, and you're trying to treat it as box data, instead of returning a long error here, so here it's essentially saying, I tried to use a private int as an int, and then it says, you know, I'm trying to use a private int as an int, and then it's trying to use the result as a public int, right? What we do then is that we define this promote instance, and it says, okay, if you have some data and you're trying to treat it as label data, just allow it, but add this warning, right? And in the other direction, if you're trying to unbox something, if it's an int, we allow that, and then you just add this warning, right? So we enable this rule, and as we can see, now it turns those errors into warnings, and it's no longer red, it's an orange warning. But we don't want to let this go off the hook, right? We don't want people to be able to promote like an int to a string, right? So in the definition of promote, we can see, which we can see here, we define the type family promote here, right? But it's defined as essentially a special case of discharge for base types, right? So if this was an int or a string, it essentially asks, okay, if you have a promote instance for ints and strings, and ints can be coerced to strings, then you will accept this, right? So this only if here, essentially, it's a new type family, which allows us to kind of add additional constraints to these checks. So, and the key is that if you can coerce a type, from type A to type B, they actually have the same representation in memory, which means that the difference between the two types is irrelevant at runtime. So for purposes of running code, we can actually treat them as the same value. And we can see that here that if we enable promote, this still compiles, and we can run hash three, and as we see, it still works. Okay, let's move on. So, also what happens when you are prototyping, you are kind of still figuring out what the security should be and stuff like that, and you don't want to make a decision quite yet. Then it can often be convenient to be able to kind of pick a default, which is to say, I haven't decided yet what I want here, but if you need to pick something, then pick this value, right? So what happens here is that we try here to use this combined function, right? But the thing is that combined needs some label, right? It needs to figure out the max. It needs to figure out what the output should be, right? So it says here, okay, the inputs are private and public, which means that the result of the max is private. And then it's trying to compare label to private, but because we haven't decided anything yet, we don't know, which means that this won't compile. But then we add this default rule, which essentially says, if you ever have a type of type label, which you need to make a decision for, then just pick public. Like, if we haven't specifically stated that something is private, you can assume that it's public, and that might be the wrong default, but that's the one we're going with in this example, right? And then we just say, okay, just assume that it's public, and then this turns from an error into a, fixes the error, and it says, okay, I'm defaulting the label here to public. And then because we also have the discharge, and then it can compare the public to private, and it discharges that, which means that this all works out, okay? Final thing that we want to do, so yeah, here's the type for default, right? So it essentially says, you know, if you have, you're trying to prove something and it has a free variable in it, which essentially means that there's some information we're missing, then you can just make that value, take the value defined by default K, which in our case was public, and then you can just keep on proving, right? So now we weaken the type system, we allow defaulting, which is kind of good, right? So last example, and the last addition is this ignore family. So let's see how we're using that. So here we have the function called ensure private, and essentially it takes in any box value and just makes sure that whatever the label is, at least is more sensitive than the private value, which means essentially that it has to be private for our to value system, right? And so if we use it and we already have something that's private, it works great, there's no complaint, right? But if we try to use it here, it will complain that there's no instance for this type class, right? Which is exactly what we want, right? We don't want, that's how we were defining the instance, right? But we might want to be able to kind of define security properties, even if we haven't quite worked out where to put all the publics and private so that it all finishes in the end, right? So we just add this ignore here and the trick is that because this here is a runtime irrelevant class, like it doesn't have any methods that will get erased at runtime anyway, then it doesn't matter. So we can just ignore it and throw it away and turn it into a warning. And now we can still run the code and say, okay, we have non-privash here, right? So in conclusion, yeah, we can also do this for more functions, right? So we can kind of, because we already have this function here, and it just takes whatever it gets in and returns it, right? And that's just the identity function. But we can just use the identity function. But we want to lift away all the kind of type parameters. And because we have defaulting, like we can default this label L here to be public, and then we can discharge that. Now, we can actually just use id directly. So it kind of means that we lift everything to the whatever level we need. And we can prototype way faster. And this is all done by a plugin into the type checker. So we have ghc already. We can have all the other things that you want from Haskell. But this just adds some extra prototype capabilities. And then of course, if you turn off the plugin, then this will all fail to compile again, right? You can have all the exceptions in place, but without the plugin. So it's kind of an opt in to kind of weaken the time system while you're prototyping. And then before you publish a library, you can turn it up and opt out again, right? So in conclusion, we can run this main function here. And we can run all our code. So if you're doing testing, you're doing prototyping, you want to see what's happening. And it's functionally correct. It can still run, and we can still check it. And we can kind of ignore security properties for a while until we figured out how we want them to be. All right, that's all I have for you. Thanks a lot for coming to talk. And if there are any questions, I will answer them now. And they can also find me on Twitter. All right, thanks a lot.