Re: Type narrowing security leak
Hi, if I understand correctly, you are supposing that method changeAmount() is part of a security sensitive application under attack. Its implementation first attempts a data validation by invoking isUserIdAllowedOrThrowException(), then transforms validated data in a lossy way (narrowing, then widening) for further usage (invoking doChangeAmount()). A seriously conducted review of security related code, whether by experienced engineers or by means of tools, would reject this code as fatally flawed in the first place, code that should never become productive. (BTW, (1L << Integer.SIZE) + 63 is a more efficient way to produce e rebound of 63 for your example.) Yours is just one example of the more general class of integer overflow attacks that potentially affects modular integer arithmetic in Java and many other languages. To prevent such attacks, operations on integer values should throw on overflow. Modifying the Java spec to mandate throwing on overflow would be a substantial change with major compatibility issues and preventing many legitimate usages of modular arithmetic as it is currently specified. Security sensitive code can (and does) make good use of j.l.Math.*Exact() methods (addExact(), toIntExact(), etc.) Greetings Raffaello On 2021-12-29 07:43, Fabrice Tiercelin wrote: Greetings, Any Java application may be concerned by a hacker attack using a type narrowing leak. If a program does the following things in this order: - Assert that a numerical id is allowed - Do a type narrowing among other things, even followed by a type widening - Do an action with the numerical id ...the hacker can do forbidden actions. Let's say that a given user doesn't have rights to change an amount for the id 63: public void changeAmount(long userId, double newAmount) throws IllegalArgumentException { isUserIdAllowedOrThrowException(userId); // userId = 4294967359 int theUserId = (int) userId; // theUserId = 63 userId = theUserId; // userId = 63 doChangeAmount(userId, newAmount); // userId = 63 } It will fail passing 63 but it will success passing 4294967359 because 4_294_967_359 is narrowed into 63. Let's call 4_294_967_359 a rebound of 63. 4294967359 can be retrieved in few seconds by a basic program like this: public class MyClass { public static void main(String args[]) { long targettedNumber = 63; for (long rebound = Integer.MAX_VALUE + 1; true; rebound++) { int typeNarrowing = (int) rebound; long typeWidening = typeNarrowing; if (typeWidening == targettedNumber) { System.out.println("Rebound for " + targettedNumber + " found: " + rebound); return; } } } } And it can be optimized. It works for any type narrowing. It not only works for numerical id but also for flags. If a numerical value should contain or not several flags, you can search a rebound among billions of rebounds until you find one with the perfect features. All the Java versions are concerned. The security layer can even be coded in another programming language. To fix it, I suggest to add a test just before the type narrowing in the bytecode. The test verifies if the type narrowing will alter the numerical value. If true, it throws an unchecked exception or an error. Otherwise, it continues as currently. Note that it changes the behavior but the current behavior is dangerous, useless and is a failing case. You can add a compiler option to restore the original behavior but the new behavior should be the default. Best regards,Fabrice TIERCELIN
Re: Type narrowing security leak
No it doesn't. It's still the same byte. However, the 0 to 255 range is for unsigned bytes, a type that does exist in some other language like C. In Java bytes are signed, so the same value is represented differently. However, both 200 (unsigned) and -56 (signed) represent the same binary value: 1100_1000. On 30/12/2021 13:13, Fabrice Tiercelin wrote: Hi, Le mercredi 29 décembre 2021, 11:35:12 UTC+1, Rob Spoor a écrit : An example is reading input streams byte-by-byte: the result is an int between 0 and > 255 (inclusive), or -1 for EOF. If the result is not -1, the result is > almost always cast to a byte Does your example mean that a whole virus can bypass an antivirus this way? Fabrice On 29/12/2021 07:43, Fabrice Tiercelin wrote: Greetings, Any Java application may be concerned by a hacker attack using a type narrowing leak. If a program does the following things in this order: - Assert that a numerical id is allowed - Do a type narrowing among other things, even followed by a type widening - Do an action with the numerical id ...the hacker can do forbidden actions. Let's say that a given user doesn't have rights to change an amount for the id 63: public void changeAmount(long userId, double newAmount) throws IllegalArgumentException { isUserIdAllowedOrThrowException(userId); // userId = 4294967359 int theUserId = (int) userId; // theUserId = 63 userId = theUserId; // userId = 63 doChangeAmount(userId, newAmount); // userId = 63 } It will fail passing 63 but it will success passing 4294967359 because 4_294_967_359 is narrowed into 63. Let's call 4_294_967_359 a rebound of 63. 4294967359 can be retrieved in few seconds by a basic program like this: public class MyClass { public static void main(String args[]) { long targettedNumber = 63; for (long rebound = Integer.MAX_VALUE + 1; true; rebound++) { int typeNarrowing = (int) rebound; long typeWidening = typeNarrowing; if (typeWidening == targettedNumber) { System.out.println("Rebound for " + targettedNumber + " found: " + rebound); return; } } } } And it can be optimized. It works for any type narrowing. It not only works for numerical id but also for flags. If a numerical value should contain or not several flags, you can search a rebound among billions of rebounds until you find one with the perfect features. All the Java versions are concerned. The security layer can even be coded in another programming language. To fix it, I suggest to add a test just before the type narrowing in the bytecode. The test verifies if the type narrowing will alter the numerical value. If true, it throws an unchecked exception or an error. Otherwise, it continues as currently. Note that it changes the behavior but the current behavior is dangerous, useless and is a failing case. You can add a compiler option to restore the original behavior but the new behavior should be the default. Best regards,Fabrice TIERCELIN
Re: Type narrowing security leak
This isn't a security leak in Java (because that would mean it would be a security leak in any language that supports narrowing). This is a security leak in the application that does the narrowing. Developers should be aware that narrowing can change values. And furthermore, I don't think there are many developers that would perform this bounds check before the narrowing. Well, at least I wouldn't. The fix you suggest could break many, many applications that use narrowing that actually *want* the value to be changed. An example is reading input streams byte-by-byte: the result is an int between 0 and 255 (inclusive), or -1 for EOF. If the result is not -1, the result is almost always cast to a byte. The suggested fix would break for all values between 128 and 255 (inclusive) - half the available values. I would rather have developers fix this in their code. There's already a method for "checked narrowing" from long to int: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Math.html#toIntExact(long). Rob On 29/12/2021 07:43, Fabrice Tiercelin wrote: Greetings, Any Java application may be concerned by a hacker attack using a type narrowing leak. If a program does the following things in this order: - Assert that a numerical id is allowed - Do a type narrowing among other things, even followed by a type widening - Do an action with the numerical id ...the hacker can do forbidden actions. Let's say that a given user doesn't have rights to change an amount for the id 63: public void changeAmount(long userId, double newAmount) throws IllegalArgumentException { isUserIdAllowedOrThrowException(userId); // userId = 4294967359 int theUserId = (int) userId; // theUserId = 63 userId = theUserId; // userId = 63 doChangeAmount(userId, newAmount); // userId = 63 } It will fail passing 63 but it will success passing 4294967359 because 4_294_967_359 is narrowed into 63. Let's call 4_294_967_359 a rebound of 63. 4294967359 can be retrieved in few seconds by a basic program like this: public class MyClass { public static void main(String args[]) { long targettedNumber = 63; for (long rebound = Integer.MAX_VALUE + 1; true; rebound++) { int typeNarrowing = (int) rebound; long typeWidening = typeNarrowing; if (typeWidening == targettedNumber) { System.out.println("Rebound for " + targettedNumber + " found: " + rebound); return; } } } } And it can be optimized. It works for any type narrowing. It not only works for numerical id but also for flags. If a numerical value should contain or not several flags, you can search a rebound among billions of rebounds until you find one with the perfect features. All the Java versions are concerned. The security layer can even be coded in another programming language. To fix it, I suggest to add a test just before the type narrowing in the bytecode. The test verifies if the type narrowing will alter the numerical value. If true, it throws an unchecked exception or an error. Otherwise, it continues as currently. Note that it changes the behavior but the current behavior is dangerous, useless and is a failing case. You can add a compiler option to restore the original behavior but the new behavior should be the default. Best regards,Fabrice TIERCELIN