Colin Mc Hugo

0 %
Colin Mc Hugo
Security Engineer Manager & CEO at Quantum Infinite Solutions Group Ltd.

Integer overflow: How does it occur and how can it be prevented?

April 16, 2022

Make no mistake, relying on a pc shouldn’t be as straightforward as it might appear. Right here’s what occurs when a quantity will get “too massive”.

For many individuals within the IT neighborhood, 2022 bought off to a nasty begin after a bug in on-premises variations of Microsoft Trade Server brought about emails to turn out to be caught en route resulting from a failed date examine. Put merely, a bug subsequently dubbed Y2K22 (named within the model of the Y2K bug that spooked the world beginning a few quarter century in the past) brought about the software program to be unable to deal with the date format for the yr 2022. Microsoft’s fix? Set the date on malware detection updates again to the fictional December 33rd, 2021, giving the date worth sufficient “respiration area” earlier than reaching the very best worth the underlying integer kind can maintain.

But the actual lesson for builders is that implementing your personal date-handling code is simply too fraught with the chance of constructing such errors and arising with unusual fixes that will use fictional dates. So except you’re writing these routines for an working system, a compiler, and so on., it’s best to all the time use normal date APIs.

Again in 2015, a bug of comparable ilk was discovered to have an effect on the software program of Boeing’s 787 Dreamliner jet. Had it not been noticed and squashed in time, the bug may have led to a total loss of all AC electrical power, even in midflight, on the plane after 248 days of steady energy. The answer to keep away from pilots shedding management of their airliner midair? Reboot your 787 earlier than 248 days are up, or higher, apply the patch.

So why did Microsoft have to faux updates for its Trade antimalware part had been nonetheless from 2021? Why does a aircraft should be turned off and again on simply so it doesn’t crash? In each circumstances, the blame fell squarely on an integer overflow, a vulnerability that could be a concern in all forms of software program, starting from video video games to GPS methods to aeronautics. Within the 2021 CWE Top 25 Most Dangerous Software Weaknesses listing, which checked out round 32,500 CVEs printed in 2019 and 2020, integer overflow or wraparound ranked in twelfth place.

Are software program builders so mathematically challenged that they’ll’t anticipate once they could be working out of numbers? The fact is extra advanced, in reality. Let’s look slightly extra deeply at how computer systems retailer and deal with numbers to see how elusive an integer overflow may be.

What’s an integer?

In arithmetic, integers embrace optimistic numbers like 1, 2, and three, the quantity 0, and detrimental numbers like −1, −2, and −3. Integers don’t embrace fractions or decimals. Which means the set of all integers may be represented with the next quantity line:

 

Generally, a programming language has a number of integer variable sorts – each shops a spread of integer values relying on the variety of bits that kind makes use of on a selected machine. The extra bits that an integer kind consumes, the better the values that may be saved therein.

Let’s take into account the integer sorts within the C programming language, assuming bit sizes that we’d anticipate on a typical x64 machine.

A char consumes 8 bits of reminiscence, that means that it may possibly retailer the next values:

 

Discover {that a} char can solely retailer values all the way down to a minimal of -128 and as much as a most of 127.

However there’s one other “mode” of the char integer kind that solely shops non-negative integers:

 

Integer sorts have each signed and unsigned modes. Signed sorts can retailer detrimental values, whereas unsigned sorts can’t.

The next desk exhibits a number of the principal integer sorts within the programming language C, their sizes on a typical x64 machine, and the vary of values they’ll retailer:

Desk 1. Integer sorts, their typical sizes and ranges for the Microsoft C++ (MSVC) compiler toolset

Kind Dimension (bit width) Vary
char 8 signed: −128 to 127
unsigned: 0 to 255
brief int 16 signed: −32,768 to 32,767
unsigned: 0 to 65,535
int 32 signed: −2,147,483,648 to 2,147,483,647
unsigned: 0 to 4,294,967,295
lengthy lengthy int 64 signed: −9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
unsigned: 0 to 18,446,744,073,709,551,615

What’s an integer overflow?

An integer overflow or wraparound occurs when an try is made to retailer a worth that’s too massive for an integer kind. The vary of values that may be saved in an integer kind is best represented as a round quantity line that wraps round. The round quantity line for a signed char may be represented as the next:

 

If an try is made to retailer a quantity better than 127 in a signed char, the depend wraps round to −128 and continues upwards, towards zero, from there. Thus, as a substitute of what needs to be 128 the worth −128 is saved, as a substitute of 129 the worth −127, and so forth.

Wanting on the drawback in reverse, if an try is made to retailer a quantity lower than -128 in a signed char, the depend wraps round to 127 and continues downwards, towards zero, from there. Thus, as a substitute of what needs to be −129 the worth 127 is saved, as a substitute of −129 the worth 126, and so forth. That is typically referred to as an integer underflow.

Searching down the elusive integer overflow

Considering of an integer overflow as a circle of values that wrap round makes it pretty straightforward to grasp. Nonetheless, it’s once we get all the way down to the nitty gritty of “casting” integer sorts and porting and compiling applications that we are able to higher perceive a number of the challenges concerned in avoiding integer overflows.

Watch your casts

Typically it’s helpful, and even required, to retailer a worth in a unique kind than the one initially used – casting, or “kind conversion”, permits programmers to do this. Though some casts are secure, others usually are not as a result of they’ll result in integer overflows. A solid is taken into account secure when the unique worth is assured to be preserved.

It’s secure to solid a worth saved in a smaller (by way of bit width) integer kind to a bigger integer kind throughout the identical mode – from a smaller unsigned kind to a bigger unsigned kind and from a smaller signed kind to a bigger signed kind. Thus, it’s secure to solid from a signed char to a signed brief int as a result of a signed brief int is massive sufficient to retailer all of the potential values that may be saved in a signed char:

 

Casting between signed and unsigned integers is a crimson flag for integer overflows because the values can’t be assured to stay the identical. When casting from a signed kind to an unsigned kind (even when it’s bigger) an integer overflow can occur as a result of unsigned sorts can’t retailer detrimental values:

 

All of the detrimental signed char values to the left of the crimson line within the picture above from −128 to −1 will trigger an integer overflow and turn out to be excessive optimistic values when solid to an unsigned kind. −128 turns into 128, −127 turns into 129, and so forth.

In reverse, when casting from an unsigned kind to a signed kind of the identical dimension, an integer overflow can occur as a result of the higher half of the optimistic values that may be saved in an unsigned kind exceed the utmost worth that may be saved in a signed kind of that dimension.

All of the excessive optimistic unsigned char values to the left of the crimson line within the above picture from 128 to 255 will trigger an integer overflow and turn out to be detrimental values when solid to a signed kind of the identical dimension. 128 turns into −128, 129 turns into −127, and so forth.

Casting from an unsigned typed to a signed kind of a bigger dimension is extra forgiving as a result of it carries no threat of an integer overflow: all of the values that may be saved in a smaller unsigned kind will also be saved in a bigger signed kind.

Implicit upcasts: Watch out for the “prejudice” for 32 bits

Though being able to explicitly solid integer sorts is beneficial, you will need to pay attention to how compilers implicitly upcast the operands of operators (arithmetic, binary, Boolean, and unary). In lots of circumstances, these implicit upcasts assist stop integer overflows as a result of they promote operands to larger-sized integer sorts earlier than working on them — certainly, the underlying {hardware} generally requires operations to be finished on operands of the identical kind and compilers will promote smaller-sized operands to bigger ones to realize this objective.

Nonetheless, the principles for implicit upcasts favor 32-bit sorts, that means that the results can at instances be surprising for the programmer. The principles observe a precedence. First, if one or each of the operands is a 64-bit integer kind (lengthy lengthy int), the opposite operand is upcast to 64 bits, if it isn’t already one, and the result’s a 64-bit kind. Second, if one or each of the operands is a 32-bit integer kind (int), the opposite operand is upcast to a 32-bit kind, if it isn’t already one, and the result’s a 32-bit kind.

Now right here is the exception to this sample that may simply journey up programmers. If one or each of the operands are 16-bit sorts (brief int) or 8-bit sorts (char), the operands are upcast to 32 bits earlier than the operation is carried out and the result’s a 32-bit kind (int). The one operators which might be an exception to this habits are the pre- and postfix increment and decrement operators (++, –), which signifies that a 16-bit (brief int) operand, as an example, shouldn’t be upcast and the end result can also be 16 bits.

The “prejudiced” upcast of 8-bit and 16-bit operands to 32-bits is important to grasp when making checks to forestall integer overflows. In the event you assume you’re including two char or two brief int sorts, you’re mistaken as a result of they’re implicitly upcast to int sorts and return an int end result. With a 32-bit end result being returned by the operation, it’s essential to downcast the end result to 16 or 8 bits earlier than checking for an integer overflow. In any other case, there’s a threat of not detecting an integer overflow as a result of an int gained’t overflow with the comparatively small values {that a} brief int or a char may present as operands.

Code portability points I – completely different compilers

Creating completely different builds of a program that may run on completely different architectures is a vital consideration at software program design time. Not solely can or not it’s a headache to rewrite code that was poorly deliberate out for various builds, however it may possibly additionally result in integer overflows if care shouldn’t be taken.

Compilers, that are used to construct code for various goal machines, are anticipated to help the usual of a programming language, however there are specific implementation particulars which might be undefined and left to the choice of compiler builders. The sizes of integer sorts in C is a kind of particulars with solely a naked skeleton of steering within the C language normal, that means that not understanding the implementation particulars in your compiler and goal machine is a recipe for potential catastrophe.

The variety of bits consumed by every integer kind described in Desk 1 is the scheme utilized by the Microsoft C++ (MSVC) compiler toolset, which includes a C compiler, when focusing on 32-bit, 64-bit, and ARM processors. Nonetheless, completely different compilers that implement the C normal can use completely different schemes. Let’s take into account an integer kind referred to as a lengthy.

For the MSVC compiler, a lengthy consumes 32 bits no matter whether or not the construct is for a 32-bit or 64-bit program. For the IBM XL C compiler, nevertheless, a protracted consumes 32 bits in a 32-bit construct and 64 bits in a 64-bit construct. It’s important to know the sizes and most and minimal values an integer kind can maintain to be able to examine for integer overflows accurately for all of your builds.

Code portability points II – completely different builds

One other portability situation to be careful for is in using size_t, which is an unsigned integer kind, and ptrdiff_t, which is a signed integer kind. These sorts consume 32 bits in a 32-bit construct and 64 bits in a 64-bit construct for the MSVC compiler. A chunk of code that decides whether or not to department based mostly on a comparability through which one of many operands is of one in all these sorts could lead on this system down completely different execution paths relying on whether or not it’s a part of a 32-bit construct or a 64-bit construct.

For instance, in a 32-bit construct, a comparability between a ptrdiff_t and an unsigned int signifies that the compiler casts the ptrdiff_t to an unsigned int and so a detrimental worth turns into a excessive optimistic worth — an integer overflow that then results in an surprising path in this system being executed, or an entry violation. However in a 64-bit construct, the compiler upcasts the unsigned int to a signed 64-bit kind, that means there isn’t a integer overflow and the anticipated path in this system is executed.

How an integer overflow results in a buffer overflow

The principal method through which an integer overflow vulnerability may be exploited is by circumventing any checks that restrict the size of information to be saved in a buffer in order to induce a buffer overflow. This opens the door to the huge array of buffer overflow exploitation strategies that result in additional issues like escalation of privilege and execution of arbitrary code.

Let’s take into account the next contrived instance:

Not accustomed to C? Run this code proper in your browser with a notebook in Google Colab.

At [2], there isn’t a examine for detrimental values of buffer_length that means that it passes the examine. Moreover, the MAX_BUFFER_LENGTH is a signed int, however it ought to have been declared at [1] as an unsigned integer kind as a result of detrimental values ought to by no means be used when assigning buffer lengths. As an unsigned integer kind, the compiler would have implicitly solid the buffer_length to an unsigned int on the examine at [2] resulting in detection of the integer overflow.

However with the −1 saved in buffer_length slipping previous the examine and the compiler implicitly casting it as an unsigned int within the initializeBuffer perform at [3] as a substitute, it overflows to a excessive optimistic worth of round 4 billion, fairly past the utmost anticipated buffer size of 11.

This integer overflow then leads on to a buffer overflow as a result of strncpy makes an attempt to make a duplicate of round 4GB of information from the supply buffer into the vacation spot buffer. Thus, regardless that an try is made to forestall a buffer overflow with the dimensions examine at [2], the examine is made incorrectly and an integer overflow happens that leads on to a buffer overflow.

When dealing with casts between signed and unsigned integer sorts, it’s not sufficient to make a dimension examine for a worth that’s better than the anticipated most worth. It’s additionally important to examine for a worth that’s lower than the anticipated minimal worth:

Guidelines of thumb

Varied methods may be employed to examine for and deal with potential integer overflows in your code, a few of which have a trade-off in portability vs. pace. With out contemplating these right here, take note a minimum of the next pointers:

  • Favor utilizing unsigned integer sorts at any time when potential. Bear in mind, there isn’t a sense in utilizing a signed integer kind to allocate reminiscence as a result of a detrimental worth isn’t legitimate on this scenario.
  • Overview and check your code by writing out all casts explicitly to see extra simply the place implicit casts may trigger integer overflows.
  • Activate any choices obtainable in your compilers that may assist establish sure forms of integer overflows. For instance, the GCC compiler has an -ftrapv possibility that checks for signed integer overflows.

Integer overflows usually are not an issue that’s going to vanish anytime quickly. Certainly, there are numerous older Unix-like methods which have the same bug to Y2K22 “scheduled” to seem in 2038, which is therefore referred to as Y2K38. Earlier than 64-bit methods had been commonplace, 32-bit methods dominated the land, that means that Unix time was saved as a signed 32-bit integer. As a result of Unix time begins counting seconds from 00:00:00 UTC on January 1, 1970, 32-bit time can solely take us a number of hours into January 19, 2038 earlier than wrapping round. Fortunately, understanding of the issue forward of time permits us to arrange and replace many weak methods earlier than we roll across the circle and slide again to 1901.

Posted in SecurityTags:
Write a comment