A Declspec SAL to Attribute SAL Rosetta Stone

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 wronginconsistent 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.

About the Author
Michael Howard

Principal Security Program Manager

Michael Howard is a principal security program manager on the Trustworthy Computing (TwC) Security team at Microsoft, where he is responsible for managing secure design, programming, and testing techniques across the company. Michael is an architect of the Security Development Read more »

Join the conversation

3 comments
  1. Anonymous

    The old declspec syntax shows __nullterminated on a typedef. Is there an equivalent way to do this in the attribute syntax? Thanks!

  2. michael_HOWARD

    Melanie – you really shouldn't use __nullterminated directory, rather stick with high-level macros that use the _z suffix, same with the new macros – use those with _z_ – they use the _$maybenull primitive (which you should not use directly :)

  3. Anonymous

    Which header is needed for attributed SAL?

Comments are closed.