A Declspec SAL to Attribute SAL Rosetta Stone

A Declspec SAL to Attribute SAL Rosetta Stone

  • Comments 4

Hi, Michael here.

A while back I wrote a blog post explaining the Standard Annotation Language (SAL) which is a technology we use to help static analysis tools find more bugs, including security vulnerabilities, in C and C++ code. If you look closely at VC++ 2005 and VC++ 2008, you’ll notice that almost all function prototypes are SAL annotated, which means you get the benefit of all the SAL work we did. But you might have also notice that the annotation style between the two compiler versions is different.

For example, in Visual C++ 2005, realloc() is annotated like this:

__checkReturn __bcount_opt(_NewSize) 
void * __cdecl realloc(
__in_opt void * _Memory,
__in size_t _NewSize);

But in VC++ 2008, realloc() is annotated like this:

_Check_return_ _Ret_opt_bytecap_(_NewSize) 
void * __cdecl realloc(
_In_opt_ void * _Memory,
_In_ size_t _NewSize);

So what’s going on? In short, there is an updated flavor of SAL that offers greater flexibility and strictness. The older version is usually referred to as ‘declspec’ SAL, and the newer version is called ‘attribute’ SAL. They get their names from the structure of the underlying primitives and the following should make it clear:

SAL Macro

SAL Primitives

Declspec SAL

void Foo( 
__in_bcount(cb) BYTE* pBuf,
size_t cb );


void Foo(
__declspec("SAL_pre")
__declspec("SAL_valid")
__declspec("SAL_pre")
__declspec("SAL_deref")
__declspec("SAL_readonly")
__declspec("SAL_pre")
__declspec("SAL_readableTo(byteCount(""cb""))") BYTE* pBuf, size_t cb );

Attribute SAL

void Foo( 
_In_bytecount_(cb) BYTE* pBuf,
size_t cb );

void Foo(
[SA_Pre  (Null=SA_No,ValidBytes="cb")]
[SA_Pre(Deref=1,Valid=SA_Yes)]
[SA_Pre(Deref=1,Access=SA_Read)] BYTE* pBuf, size_t cb );

Aren’t you happy we created macros for the low-level primitives!? You should never have to use the low-level primitives in your code: the table is to show you why the two SAL formats got their names.

So why a new SAL syntax? I have good news and really good news. First, the good news: other than a simple macro syntax change, there is not a lot new to learn in part because the macros are similar (not identical, however) and the major difference, the low-level primitives, are abstracted away.

Now for the really good news. Attribute SAL is much more rigorous than declspec SAL, which means analysis tools can find more bugs with lower false positives (‘noise’). For example, declspec SAL is often silent in the face of an incorrect annotation.

The introduction of attribute SAL does not mean declspec SAL is dead, but it does mean that we will not be investing any more resources into declspec SAL, all our energy improving SAL and our analysis tools use of SAL will be in attribute SAL. At a pragmatic level, this means:

· If you have already invested in using declspec SAL you should migrate over to attribute SAL as time allows, and use new attribute SAL for new functions. Both syntaxes can co-exist.

· If you have never used SAL, you should use attribute SAL. As far as you’re concerned, declspec SAL never existed.

One noticeable difference in macro names is the use of declspec SAL’s “count” and attribute SAL’s “cap” and “count.” The former is a buffer size in elements or bytes, but the latter two are the buffer’s writing capacity and the size of the buffer for reading, respectively.

An important addition to attribute SAL is _Printf_format_string_ which can be used to find many printf-related format-matching ills.

The following table shows some of the major differences:

 

declspec SAL

Attribute SAL

Syntax

Loose, allows macros in places they don’t make sense

Strict, annotations can be only put on parameters and return values

Consistency checks

Few, allows wrong macros

Many, exhaustive set of warnings for wrong\inconsistent annotations

Wrong annotations

Ignored

Flagged

Constant expressions buffer sizes

Simple expressions only

Fully supported including templates, but requires different macros.

Return values

Loose syntax and consistency rules allow the use of ‘__out’ family

Special set of macros for return values required

Naming consistency

Overloaded use of ‘count’ for writable and readable  extent. Hard to understand _full and _part postfixes

Consistent use of ‘cap’ (capacity) for writable extent and ‘count’ for readable extent

As noted in the table above, there is one minor drawback to using attribute SAL. If you use constant expressions as count or cap arguments, you must use a special set of macros, which is a little less elegant than declspec SAL:

void Foo( _In_count_c_( 8 ) int* rgInt );

versus

void Foo( __in_count( 8 ) int* rgInt );

Note the _c_ portion of the attribute syntax, which is not needed when using declspec macros. With that said, attribute syntax supports accept any C++ conformant constant expression including enums and template arguments, but decspec SAL supports only simple expressions.

An Example

To put his altogether, let’s look at some simple code, and see how the VC++ 2008 /analyze static analysis performs when faced with the different SAL types.

First, declspec SAL:

   1: #include "stdafx.h"
   2:  
   3: struct SomeStruct {
   4:     int x;
   5:     float f;
   6: };
   7:  
   8: bool FuncOne(__in_z_opt const char* filename);
   9: void FuncTwo(const char *pFormat, ...);
  10: void FuncThree( __in const SomeStruct* setup );
  11: void FuncFour(__in HWND h, __in char *sz); 
  12:  
  13: void TestWarnings() {
  14:     char b;
  15:     FuncOne(&b); 
  16:     FuncTwo("%d %p %d", 10.0, "Hello");
  17:     FuncThree(0); 
  18:     SomeStruct blah;
  19:     FuncThree(&blah);
  20:  
  21:     char buff[100];
  22:     FuncFour(NULL,buff);
  23: }

When compiled with /W4 /analyze, the compiler gives us:

warning C6309: Argument '1' is null: this does not adhere to function 
specification of 'FuncThree'
warning C6309: Argument '1' is null: this does not adhere to function
specification of 'FuncFour'
warning C6387: 'argument 1' might be '0': this does not adhere to the
specification for the function 'FuncThree': Lines: 14, 15, 16, 17
warning C6387: 'argument 1' might be '0': this does not adhere to the
specification for the function 'FuncFour': Lines: 14, 15, 16, 17, 18, 19, 21,
22

Now, let’s take the same code, but decorate the function prototypes with attribute SAL rather than declspec SAL.

   1: #include "stdafx.h"
   2:  
   3: struct SomeStruct {
   4:     int x;
   5:     float f;
   6: };
   7:  
   8: bool FuncOne(_In_opt_z_ const char* filename);
   9: void FuncTwo(_Printf_format_string_ const char *pFormat, ...);
  10: void FuncThree(_In_ const SomeStruct* setup );
  11: void FuncFour(_In_ HWND h, _In_ char *sz); 
  12:  
  13: void TestWarnings() {
  14:     char b;
  15:     FuncOne(&b); 
  16:     FuncTwo("%d %p %d", 10.0, "Hello");
  17:     FuncThree(0); 
  18:     SomeStruct blah;
  19:     FuncThree(&blah);
  20:     
  21:     char buff[100];
  22:     FuncFour(NULL,buff);
  23: }
warning C6273: Non-integer passed as parameter '2' when integer is required 
in call to 'FuncTwo': if a pointer value is being passed, %p should be used
warning C6064: Missing integer argument to 'FuncTwo' that corresponds to
conversion specifier '3'
warning C6309: Argument '1' is null: this does not adhere to function
specification of 'FuncThree'
warning C6309: Argument '1' is null: this does not adhere to function
specification of 'FuncFour'
warning C6001: Using uninitialized memory 'b': Lines: 14, 15
warning C6387: 'argument 1' might be '0': this does not adhere to the
specification for the function 'FuncThree': Lines: 14, 15, 16, 17
warning C6001: Using uninitialized memory 'blah': Lines: 14, 15, 16, 17, 18,
19
warning C6387: 'argument 1' might be '0': this does not adhere to the
specification for the function 'FuncFour': Lines: 14, 15, 16, 17, 18, 19, 21,
22

As you can see, using attribute SAL found many more code bugs, and all of them are real. I’ll let you sift through the list to see what attribute SAL found over and above declspec SAL! There are some duplicate bugs, however.

If you want to learn more about SAL, I would recommend you simply open sal.h and read the comments and examples.

The Rosetta Stone

Below is a partial Rosetta Stone to help you convert between the two SAL syntaxes if you need to do so.

Declspec

Attribute

__in

_In_

__in_opt

_In_opt_

__in_z_opt

_In_opt_z_

__out

_Out_

__out_opt

_Out_opt_

__inout

_Inout_

__inout_opt

_Inout_opt_

   

__in_ecount(count)

_In_count_(count)

__in_bcount(count)

_In_bytecount_(count)

__in_xcount(count)

_In_count_x_(count)

__in_ecount_z(count)

_In_z_count_(count)

__in_bcount_z(count)

_In_z_bytecount_(count)

__in_xcount_z(count)

_In_z_count_x_(count)

__in_ecount_opt(count)

_In_opt_count_(count)

__in_bcount_opt(count)

_In_opt_bytecount_(count)

__in_xcount_opt(count)

_In_opt_count_x_(count)

__in_ecount_z_opt(count)

_In_opt_z_count_(count)

__in_bcount_z_opt(count)

_In_opt_z_bytecount_(count)

__in_xcount_z_opt(count)

_In_opt_z_count_x_(count)

   

__out_ecount(count)

_Out_cap_(count)

__out_bcount(count)

_Out_bytecap_(count)

__out_xcount(count)

_Out_cap_x_(count)

__out_ecount_z(count)

_Out_z_cap_(count)

__out_bcount_z(count)

_Out_z_bytecap_(count)

__out_xcount_z(count)

_Out_z_cap_x_(count)

__out_ecount_part(cap,count)

_Out_cap_post_count_(cap, count)

__out_bcount_part(cap,count)

_Out_bytecap_post_bytecount_(count)

__out_ecount_full(capcount)

_Out_capcount_(capcount)

__out_bcount_full(capcount)

_Out_bytecapcount_(capcount)

__out_ecount_opt(count)

_Out_opt_cap_(count)

__out_bcount_opt(count)

_Out_opt_bytecap_(count)

__out_xcount_opt(count)

_Out_opt_cap_x_(count)

__out_ecount_z_opt(count)

_Out_opt_z_cap_(count)

__out_bcount_z_opt(count)

_Out_opt_z_bytecap_(count)

__out_xcount_z_opt(count)

_Out_opt_z_cap_x_(count)

__out_ecount_part_opt(cap,count)

_Out_opt_cap_post_count_(cap,count)

__out_bcount_part_opt(cap,count)

_Out_opt_bytecap_post_bytecount_(count)

__out_ecount_full_opt(capcount)

_Out_opt_capcount_(capcount)

__out_bcount_full_opt(capcount)

_Out_opt_bytecapcount_(capcount)

   

__inout_ecount(count)

_Inout_cap_(count)

__inout_bcount(count)

_Inout_bytecap_(count)

__inout_xcount(count)

_Inout_cap_x_(count)

__inout_ecount_full(count)

_Inout_count_(count)

__inout_bcount_full(count)

_Inout_bytecount_(count)

__inout_xcount_full(count)

_Inout_count_x_(count)

__inout_ecount_z(count)

_Inout_z_cap_(count)

__inout_bcount_z(count)

_Inout_z_bytecap_(count)

__inout_xcount_z(count)

_Inout_z_cap_x_(count)

__inout_ecount_opt(count)

_Inout_opt_cap_(count)

__inout_bcount_opt(count)

_Inout_opt_bytecap_(count)

__inout_xcount_opt(count)

_Inout_opt_cap_x_(count)

__inout_ecount_full_opt(count)

_Inout_opt_count_(count)

__inout_bcount_full_opt(count)

_Inout_opt_bytecount_(count)

__inout_xcount_full_opt(count)

_Inout_opt_count_x_(count)

__inout_ecount_z_opt(count)

_Inout_opt_z_cap_(count)

__inout_bcount_z_opt(count)

_Inout_opt_z_bytecap_(count)

__inout_xcount_z_opt(count)

_Inout_opt_z_cap_x_(count)

Acknowledgments

I would like to thank Hannes Ruescher (Dev Mgr in Office,) Dave Bartolomeo (Principal Software Design Engineer in Visual Studio) and Bruce Dawson (Principal Software Design Engineer in Windows) for their gracious help providing core content for this document.
Comments
Page 1 of 1 (4 items)
Leave a Comment
  • Please add 1 and 5 and type the answer here:
  • Post