September 1st, 2009

Compiler Warning C4789

When Visual Studio 2010 ships, it will have improvements to warning C4789; allowing it to catch more cases of buffer overrun. This blog post will cover what C4789 warns about, and how to resolve the warning.

What does C4789 mean?

 

When compiling your source file, you may receive the warning: “warning C4789: destination of memory copy is too small.”

This message means that the compiler has detected a possible buffer overrun in your code.

Example 1

Let’s say we have the source file a.cpp that contains the following:

1: #include <memory.h>

2:

3: int p[1];

4:

5: void bar() {

6:     memset(&p[1], 1, sizeof(int));

7: }

 

From the ”Visual Studio 2008 Command Prompt”, if you compile this with the command:

cl /c /O2 a.cpp

You will receive the warning:

a.cpp(6) : warning C4789: destination of memory copy is too small

For the example above, the compiler has detected a buffer overrun for the variable ‘p’. ‘p’ has been allocated as an array with one element. Arrays are zero-indexed, so the memset on line 6 is taking the address of the second element of an array; this means that we are actually writing to memory outside the array, corrupting memory!

In this case, the user most likely meant to memset the first element, and thus to fix this issue, the memset would be changed to

memset(&p[0], 1, sizeof(int));

 

Typical User Scenarios

 

In practice, a lot of buffer overruns will not be as obvious as Example 1, so I’ll provide some more examples to help you in your investigations.

Example 2

Let’s say we have the source file a.cpp that contains the following:

1:  short G1;

2:

3:  void foo(int * x)

4:  {

5:      *x = 5;

6:  }

7:

8:  void bar() {

9:     foo((int *)&G1);

10: }

From the ”Visual Studio 2010 Command Prompt”, if you compile this with the command:

cl /c /O2 a.cpp

You will receive the warning:

a.cpp(9) : warning C4789: destination of memory copy is too small

In this example, we’ve created a variable ‘G1’ of si ze short (which is only two bytes), but we’ve taken the address of it and casted it to ‘int *’ to pass to ‘foo’. ‘foo’ then writes 4 bytes to the memory location pointed at by ‘x’. As ‘G1’ is only 2 bytes in length, the store “*x = 5” will write past ‘G1’, resulting in a buffer overrun.

There are a couple of important things to note about this example. This buffer overrun will only be caught with the improvements made in Visual Studio 2010. Also, this warning is caught by inlining ‘foo’ into ‘bar’. This means that this buffer overrun is only caught when optimizations are enabled.

To fix the buffer overrun in Example 2, we declare ‘G1’ as int. If that isn’t an option, we can create a new variable to pass to ‘foo,’ and assign that variable to ‘G1’ (which truncates the int to a short):

   int y;

   foo(&y);

   G1 = y;

 

Example 3

Let’s say we have the source file a.cpp that contains the following:

1:  int G1;

2:  int G2;

3:

4:  void foo(int ** x)

5:  {

6:      *x = &G2;

7:  }

8:

9:  void bar() {

10:     foo((int *)&G1);

11: }

From the ”Visual Studio 2010 X64 Cross Tools Command Prompt”, if you compile this with the command:

cl /c /O2 a.cpp

You will receive the warning:

a.cpp(10) : warning C4789: destination of memory copy is too small

This example is exactly like Example 2 with a key difference. We’ve casted “int” to “int *”. On x86, this is a harmless cast (int and int * are the same size, 4 bytes). However, on x64, “int” is 4 bytes, and “int *” is 8 bytes, so this code is no longer correct when this code is run on x64.

C4789 False Positives

 

You may hit cases of C4789 where the warning is incorrect. This can happen because the compiler detects a buffer overrun along a code path that will never fire.

Example 4

1:  __int64 G1;

2:  int lengthOfG1 = 8;

3:

4:  void foo(char * x, int len) {

5:      if (len > 8) {

6:          x[8] = 1;

7:      }

8:  }

9:

10: void bar() {

11:     foo((char *)&G1, lengthOfG1);

12: }

From the ”Visual Studio 2010 Command Prompt”, if you compile this with the command:

cl /c /O2 a.cpp

You will receive the warning:

a.cpp(11) : warning C4789: destination of memory copy is too small

In this example, the compiler thinks that ‘G1’ can be buffer overrun because of “x[8] = 1” would assign outside of the size of ‘G1.’ However, as long as ‘lengthOfG1’ is the correct length of ‘G1,’ “x[8] = 1” will never fire for ‘G1,’ and thus a buffer overrun will never occur.

For some of these false positives, the only option will be to disable the warning. In this particular example, however, changing “int lengthOfG1 = 8” to

const int lengthOfG1 = 8;

would solve the problem.

Workarounds

 

If you have proven that the warning is a false positive, there are a couple of different ways to disable the warning.

1.       Disable the warning for one function (recommended)

2.       Disable the warning for all functions

Disable the warning for one function

The compiler allows you to disable a warning for a particular function. This is done by putting

#pragma warning ( disable : 4789 )

before the function, and putting

#pragma warning ( default : 4789 )

after the function. This will disable the warning (in this case warning 4789) for that function (and any functions which inline it).

Example 5

1:  __int64 G1;

2:  int lengthOfG1 = 8;

3:

4:  #pragma warning ( disable : 4789 )

5:  void foo(char * x, int len) {

6:      if (len > 8) {

7:          x[8] = 1;

8:      }

9:  }

10: #pragma warning ( default : 4789 )

11:

12: void bar() {

13:     foo((char *)&G1, lengthOfG1);

14: }

15:

16: void bar1() {

17:     foo((char *)&G1, 9);

18: }

With the #pragma around ‘foo,’ you will receive no warnings; while without it you will receive the warnings:

a.cpp(13) : warning C4789: destination of memory copy is too small

a.cpp(17) : warning C4789: destination of memory copy is too small

You can also choose to disable the warning for one of the functions where the warning occurs. In the example above, we could put the #pragma around ‘bar’ instead of ‘foo’, and then we’d eliminate the warning for line 13, but still receive the warning on line 17.

Disable the warning for all functions

If you need to ignore warning 4789 completely, you can specify /wd4789 on the command line.

cl /c /O2 /wd4789 a.cpp

This option isn’t recommended as it will hide potentional buffer overruns in your code.

 

0 comments

Discussion are closed.