Get on-the-go access to the latest insights featured on our Trustworthy Computing blogs.
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);
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
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.
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, 17warning 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.
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);
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 usedwarning 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, 15warning C6387: 'argument 1' might be '0': this does not adhere to the specification for the function 'FuncThree': Lines: 14, 15, 16, 17warning C6001: Using uninitialized memory 'blah': Lines: 14, 15, 16, 17, 18, 19warning 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.
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)
PingBack from http://www.csharphacker.com/technicalblog/index.php/2009/06/10/the-old-rediscovered-and-the-new-microsoft-net-contracts/
The old declspec syntax shows __nullterminated on a typedef. Is there an equivalent way to do this in the attribute syntax? Thanks!
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 :)
Which header is needed for attributed SAL?