CERT

Home
More Information
Related Links
References
Code Samples
Additional Articles
Events
Errata

Errata


Please send your comments and corrections to the author at rcs@cert.org

  1. Running with Scissors
  2. Strings
  3. Pointer Subterfuge
  4. Dynamic Memory Management
  5. Integer Security
  6. Formatted Output
  7. File I/O
  8. Recommended Practices
  9. References

Preface


None.

Chapter 1 : Running with Scissors


None.

Chapter 2: Strings


 
 Page 27,  Figure 2–1.  In line 1 of the example program, main should be declared as:
            int main(void) {

and not:

       void main(void) {

to be compliant with ISO/IEC 9899:1999 as described in Section 5.1.2.2.1 "Program startup".

 Page 29, Figure 2–4.  On line 1 of the example program, iostream should be included as:

#include <iostream>

and not:

#include <iostream.h>

Also, on line 2 of the example program, main should be declared as:

       int main(void) {

and not:

       int main() {

to be compliant with ISO/IEC 9899:1999 as described in Section 5.1.2.2.1 "Program startup".

Page 29, Figure 2–5. On line 1 of the example program, iostream should be included as:

#include <iostream>

and not:

#include <iostream.h>

Also, on line 2 of the example program, main should be declared as:

       int main(void) {

and not:

       int main() {

to be compliant with ISO/IEC 9899:1999 as described in Section 5.1.2.2.1 "Program startup".
 Page 31, first paragraph in "Null-Termination Errors" section. The paragraph explains the piece of code in Figure 2-7 as if everything was treated in 10 and 20 byte blocks, but in the figure blocks are 16 and 32 byte long.  
 Page 32. Section "String Truncation", "While not as bas as a" should be "While not as bad as a".

Page 39. Section "Stack Management", "Table 2-1 shows a sample stack frame foo() that takes two arguments and contains four local variables." should be "Table 2-1 shows a sample stack frame foo() that takes two arguments and contains two local variables.".
Page 43. Figure 2-20. "/0" should be '\0'.

Page 45.  Section 2.6 "Code Injection".  Command line should appear without quotes around the space.
% ./BufferOverFlow < exploit.bin

  Page 44. Figure 2-22. third block "(return to line 6 was line 3)" should be "(return to line 7 was line 3)". Also  "/0" should be '\0'.

Page 49, Figure 2-27, Line 4.   Because sizeof(user_input) returns the size of a char * and not the length of the data pointed to by user_input, it will always return 4. This is not sufficient to create a buffer overflow condition in this case. Therefore, sizeof(user_input) should be strlen(user_input)+1. The code for figure 2-27 should be:
1. #include <string.h>

2. int get_buff(char *user_input){
3. char buff[4];

4. memcpy(buff, user_input, strlen(user_input)+1);
5. return 0;
6. }

7. int main (int argc, char *argv[]){
8. get_buff(argv[1]);
9. return 0;
10. }

  Page 59, Section "Strsafe.h".  The reference to Figure 2-32 should reference "line 5" and "line 10" instead of  "line 8" and "line 13".

  Page 60, first paragraph, last sentence.   "In most cases" could be more precisely states as "For static buffers".

  Page 61, C++ std::string, paragraph 2. The sentence "The std::string class is convenient because language supports the class directly" is not exactly correct.  It is not the language but the standard library that supports it.

 Page 61, Figure 2–33.  In line 4 of the example program, main should be declared as:

       int main(void) {

and not:

       int main() {

to be compliant with ISO/IEC 9899:1999 as described in Section 5.1.2.2.1 "Program startup".

  Page 67, Compiler Generated Runtime Checks.  The /RTC option only works at debug.

  Page 68.  Section "Stackgap".  "return value" should be "return address".

  Page 68.  There is not a great deal of information published on Stackgap but some information is available in a presentation given by Theo de Raadt [de Raadt 03] given at CanSecWest, Vancouver, Canada. April 2003

  Page 73. section "Metamail". paragraph 2. "the substring that indentifies the character set" should be "the substring that identifies the character set"


Chapter 3: Pointer Subterfuge



 Page 79, Figure 3–1.  In line 4 of the example program, main should be declared as:

       int main(int argc, char *argv[]) { /* stack, local */

and not:


       void main(int argc, char **argv) { /* stack, local */

for consistency with the other examples.

Page 79, Figure 3–2. In line 2 of the example program, main should be declared as:

       int main(int argc, char *argv[]) {

and not:


       void main(int argc, char **argv) {

for consistency with the other examples.

Page 89, Figure 3-12. In line 5 of the example program, main should be declared as:

       int main(void) {

and not:

       void main(void) {

to be compliant with ISO/IEC 9899:1999 as described in Section 5.1.2.2.1 "Program startup".

Page 92, Section "Structured Exception Handling"

The intro paragraph:

SEH is typically implemented at the compiler level through try...catch blocks as shown in Figure 3-17.

Should read:

SEH adds try-except statements to C and C++ programs, allowing an application to catch and handle hardware and software exceptions as shown in Figure 3-17.

Figure 3-17 should be replaced by the following figure:

int main(void) {
int x = INT_MIN;
int y = -1;

__try {
x = x / y;
}
__except (GetExceptionCode() ==
EXCEPTION_INT_OVERFLOW ?
EXCEPTION_EXECUTE_HANDLER :
EXCEPTION_CONTINUE_SEARCH) {
// handle integer overflow
}

return 0;
}
Figure 3-17. A try-except statement.

The first paragraph at the top of page 93 should be replaced with the following paragraph:

Any exception that is raised within the section guarded by the
__try clause is handled by the matching exception handler. If no exception occurs during execution of the guarded section, execution continues with the statement following the __except block. The example in Figure 3-17 shows how SEH can be used to handle a hardware exception, in this case, an integer overflow. GetExceptionCode() is a Microsoft specific function that returns the exception code (a 32-bit integer value).

Chapter 4: Dynamic Memory Management


 Page 106, "Improperly Paired Memory Management Functions".  I asked Stephen Dewhurst what he meant by "a limited set of types" and received the following response:

The standard says you're allowed to implement the "usual" operator new with malloc (but not the other way around), and operator delete with free (but not free with operator delete).  For such an implementation, any type with no destructor or a trivial destructor may well execute correctly; but the addition of a non-trivial destructor or different implementation of operator new or delete will cause things to break.

A more subtle problem involves the use of class or hierarchy specific memory management.  One of the nice things about member operator new and delete is that you can plug them in without the users of your class having to rewrite source code.  However, it's likely that it's important for member delete to used for an object that was allocated with member new, no matter what is the case for the usual global operator new and operator delete.  So, you customize a member new/delete, things break (because somebody's freeing with free or allocating with malloc) and you search vainly within your code for "your" bug.

Page 107, "malloc(0)". "A border condition..." should read "A boundary condition..."

Page 114, paragraph before "Frontlink Technique" section. "..the boundary tag for the second argument..." should read "...the boundary tag for the second chunk..."

  Figure 4-21 on page 119, line 3 is:

3. "\xeb\x0cjump12chars_" 3. /* jump */

the extra line 3 was unintentional and should be removed:

3. "\xeb\x0cjump12chars_"

The same exact fix required for line 3 of Figure 4-22 on page 121.
The corrected figures are shown below:

 1. static char *GOT_LOCATION = (char *)0x0804c98c;
 2. static char shellcode[] =
 3.   "\xeb\x0cjump12chars_"
 4.   "\x90\x90\x90\x90\x90\x90\x90\x90"
 5.
 6. int main(void){
 7.   int size = sizeof(shellcode);
 8.   char *shellcode_location;
 9.   char *first, *second, *third, *fourth;
10.   char *fifth, *sixth, *seventh;
11.   shellcode_location = malloc(size);
12.   strcpy(shellcode_location, shellcode);
13.   first = malloc(256);
14.   second = malloc(256);
15.   third = malloc(256);
16.   fourth = malloc(256);
17.   free(first);
18.   free(third);
19.   fifth = malloc(128);
20.   free(first);
21.   sixth = malloc(256);
22.   *((char **)(sixth+0)) = GOT_LOCATION-12;
23.   *((char **)(sixth+4)) = shellcode_location;
24.   seventh = malloc(256);
25.   strcpy(fifth, "something");
26.   return 0;
27. }

Figure 4–21. Double-free exploit code

 1. static char *GOT_LOCATION = (char *)0x0804c98c;
 2. static char shellcode[] =
 3.   "\xeb\x0cjump12chars_"
 4.   "\x90\x90\x90\x90\x90\x90\x90\x90"

 5. int main(void){
 6.   int size = sizeof(shellcode);
 7.   char *shellcode_location;
 8.   char *first, *second, *third, *fourth, *fifth, *sixth;
 9.   shellcode_location = malloc(size);
10.   strcpy(shellcode_location, shellcode);
11.   first = malloc(256);
12.   second = malloc(256);
13.   third = malloc(256);
14.   fourth = malloc(256);
15.   free(first);
16.   free(third);
17.   fifth = malloc(128);
18.   *((char **)(first+0)) = GOT_LOCATION - 12;
19.   *((char **)(first+4)) = shellcode_location;
20.   sixth = malloc(256);
21.   strcpy(fifth, "something");
22.   return 0;
23. }

Figure 4–22. Overwriting freed memory exploit

 Page 130, Figure 4-31.  The blink pointer refers to a four byte value, and not a three byte value as the figure appears to indicate.  (The shading in the figure is incorrect.)

Page 131, Figure 4-31. The memory content is incorrect. The correct data is shown below:

00ba0680 22 00 08 00 00 01 0c 00 61 61 61 61 61 61 61 61
00ba0690 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
:
00ba0780 61 61 61 61 61 61 61 61 61 61 61 61 00 00 00 00
00ba0790 0e 01 22 00 00 10 00 00 78 01 ba 00 78 01 ba 00



Page 134, Figure 4-35. The figure is incorrectly captioned. The correct caption is:

Fig4-35. RtlHeap double-free vulnerability
Page 136, The following sentence in the last paragraph:

Second, FreeList[3] now points to 0x00BA06A0—the original location of h2.
Should read:

Second, FreeList[0] now points to 0x00BA06A0—the original location of h2.



Page 141, Section "phkmalloc". "malloc/" should be "malloc".

Page 149, Section 4.9 "Further Reading". First full sentence should read:

A security researcher explains the internals of Poul-Henning Kamp's malloc() implementation (phkmalloc) [Smashing 05].

Chapter 5: Integer Security


 Page 152, Figure 5-1.  In line 1 of the example program, main should be declared as:
    
int main(int argc, char *argv[]) {

and not:

       int main(int argc, char *const *argv) {

to be compliant with ISO/IEC 9899:1999 as described in Section 5.1.2.2.1 "Program startup".

  Page 155. section "Standard and Extended Types". "following types, in increasing order". Technically, these types are in 'non-decreasing order'. Increasing order implies that each type is larger than the ones before and the standard does not actually require that the an increasing size.

  "Sign Errors" and "Truncation Error" sections, pages 166-167

These two sections describe the differences between sign errors and truncation errors.  These sections espouse the theory that a truncation error occurs when a unsigned value is converted to a signed value of the same length because the signed respresentation has one less bit to represent the result.  After giving this some more consideration, I've come to believe that sign errors are errors in which there is no loss of data but the bytes are simply misinterpreted and that truncation errors involve a loss of data.


  Precondition for Subtraction section, pages 173 should be:

For signed integers of mixed signs, the following applies:
- If LHS is negative and RHS is positive, check that the lhs >=  INT_MIN + rhs.
- If LHS is non-negative and RHS is negative, check that the lhs <= INT_MAX + rhs.

Likewise, the inequalities in Table 5-5 should be "<=" instead of "<"

Table 5–5. Addition of Signed Integers of Type int
LHS RHS Exceptional Condition
Positive Positive Overflow if INT_MAX – LHS <= RHS
Positive Negative None possible
Negative Positive None possible
Negative Negative Overflow if LHS <= INT_MIN – RHS

  Page 179,  Last paragraph.  Also Page 181, Figure 5-22.

This paragraph and the associated figure demonstrate how a signal handler can be used on Linux to throw a C++ exception in response to a hardware error such as integer overflow or divide-by-zero. 

This solution only works on older Linux systems which use Setjmp-Longjmp (SjLj) exception handling system, but not on most modern Linux systems that use DWARF2 (aka, Unwind-dw2) exception handling system.

The following code, for example:


#include <csignal>
#include <cstdio>
#include <stdexcept>

using namespace std;
static void divide_error(int val) {
printf("SIGNAL_HANDLER\n"); fflush(stdout);
throw runtime_error("error"); // trigger abort() (under "DWARF2")
}
int main(int argc, char** argv) {
signal(SIGFPE, divide_error);
try {
int a = 123 / (argc - 1); // div-by-zero
printf("---- A ----\n"); fflush(stdout);
} catch(...) {
printf("---- B ----\n"); fflush(stdout);
}
return 0;
}

Performs as follows on the specified systems:

[test 1 - DWARF2, Red Hat Enterprise Linux 4]

% g++ handle_signal.cpp
% ./a.out
SIGNAL_HANDLER
terminate called after throwing an instance of 'std::runtime_error'
what(): error
Aborted


[test 2 - DWARF2(default), RedHat 7.3]

% g++ handle_signal.cpp
% ./a.out
SIGNAL_HANDLER
Aborted


[test 3 - SjLj(optional), RedHat 7.3]

% g++ -fsjlj-exceptions handle_signal.cpp
% ./a.out
SIGNAL_HANDLER
---- B ----
%

SjLj is obsolete mainly because of its performance characteristics. According to David, et. al [David 06]:

This implements exceptions using a setjmp call to save context at C++ try blocks and in all functions with local objects that need to be destroyed when an exception unwinds stack frames. The longjmp call is used to restore saved contexts during stack unwinding. Since the context saves are performed irrespective of whether an exception is eventually raised or not, this implementation suffers a performance penalty.
The -fsjlj-exceptions option is no longer supported on recent versions of gcc for Linux.

[Thanks to SATO Yusuke / Sony Digital Network Applications Inc.]

The text should be changed as follows:

The last sentence on page 179 and Figure 5-22 should be discarded.

  Page 182,  section "Integer Overflow".  Last sentence, second paragraph should read:

The size variable is declared as an unsigned int (line 2), resulting in a very large positive value of 0xffffffff (from 1 minus 2) when memory is copied on line 5.

  Page 182,  section "Integer Overflow".  Last sentence, second paragraph should read:

The size variable is declared as an unsigned int (line 2), resulting in a very large positive value of 0xffffffff (from 1 minus 2) when memory is copied on line 5.

  Page 186, In section 5.6 "Nonexceptional Integer Logic Errors", replace:

   table[pos] = value

is equivalent to

*(table + (pos * sizeof(int))) = value;

with the following:

table[pos] = value;

multiplies pos by the size of the array element type (an int), adds this value to the start of the array (table), and assigns value to this location.

  Page 188, Figure 5-28. For elucidatory purposes,  this example does not include checks to ensure that the correct number of arguments is passed to the function.  As a result, this program as listed is susceptible to crashes if argc < 3.

  On page 190 the section titled "Compiler-Generated Runtime Checks" is too narrowly scoped and should also have included a discussion of compile-time checks and warnings.

Compiler Checks

In a perfect world, C and C++ compilers would identify the potential for exceptional conditions to occur at runtime and provide a mechanism (such as an exception, trap, or signal handler) for applications to handle these events. Unfortunately, the world we live in is far from perfect. A brief description of some of the capabilities that exist today follows.

Visual C++.

The Visual C++ .NET 2003 compiler generates a compiler warning (C4244) when an integer value is assigned to a smaller integer type.  At warning level 1, a warning will be issued if a value of type __int64 is assigned to a variable of type unsigned int. At warning level 3 and 4, a “possible loss of data” warning is issued if an integer type is converted to a smaller integer type.  For example, the assignment in the following example is flagged at warning level 4:

// C4244.cpp
// compile with: /W4
int main() {
   int b = 0, c = 0;

   short a = b + c;   // C4244
}

Visual C++ .NET 2003 also provides runtime error checks that are enabled by the /RTC flag.  The /RTCc compiler flag provides a similar function to compiler warning C4244 by reporting when a value assigned to a smaller data type results in a loss of data.  Visual C++ also includes a runtime_checks pragma that disables or restores the /RTC settings, but does not include flags for catching other runtime errors such as overflows. Visual C++ 2005 adds the ability to catch overflows in operator::new (and is on by default).

Runtime error checks are not valid in a release (optimized) build for performance reasons.9

GCC

The gcc and g++ compilers include an -ftrapv compiler option that provides limited support for detecting signed integer exceptions at runtime. According to the gcc man page, this option “generates traps for signed overflow on addition, subtraction, and multiplication operations.” In practice, this means that the gcc compiler generates calls to existing library functions rather than generating assembler instructions to perform these arithmetic operations on signed integers. If you use this feature, make sure you are using gcc version 3.4 or later because the checks implemented by the runtime system before this version do not adequately detect all overflows and should not be trusted.10



9. See Visual C++ Compiler Options, /RTC (Run-Time Error Checks), Visual Studio .NET help system.
10. See VU#540517 “libgcc contains multiple flaws that allow integer type range vulnerabilities to occur at runtime” at http://www.kb.cert.org/vuls/id/540517.


 Page 191, Figure 5-29.  The correct caption is:

Figure 5-29. When to use, and not to use, safe integers.

Page 192, Figure 5-30. The correct caption is:

Figure 5-30. Postcondition approach for overflow checking

 Page 193, Figure 5-32.  In line 1 of the example program, main should be declared as:
    
int main(int argc, char *argv[]) {

and not:

       int main(int argc, char *const *argv) {

to be compliant with ISO/IEC 9899:1999 as described in Section 5.1.2.2.1 "Program startup".

Page 194, Figure 5-33. The correct caption is:

Figure 5-33. Precondition approach for overflow checking
  The "Use Safe Integer Operations" code shown in Figure 5-29 on page 191 does not compile using version 2 of  SafeInt.  Use the following code instead:


void* CreateStructs(int StructSize, int HowMany) {
  SafeInt<unsigned long> s(StructSize);

  s *= HowMany;
  return malloc(s);

}


  Figure 5-31 on page 193 is incorrect.

The first line of the figure appears as follows:


1. in bool UAdd(size_t a, size_t b, size_t *r) {

The correct line is:

1. int UAdd(size_t a, size_t b, size_t *r) {

The corrected example (sans line numbering) is shown below:

int UAdd(size_t a, size_t b, size_t *r) {
__asm {
mov eax, dword ptr [a]
add eax, dword ptr [b]
mov ecx, dword ptr [r]
jc short j1
mov al, 1 // 1 is success
jmp short j2
j1:
XOR AL, AL // 0 is failure
J2:
};
}

On page 194, the SafeInt Class section states that "Nearly every relevant operator has been overridden".  According to the author, David LeBlanc from Microsoft, the only relevant operator not overwritten in SafeInt version 2 is the [] subscripting operator. 

On page 193, the section titled "C Language Compatible Library" states that "Advantages of the Howard approach are that it can be used in both C and C++ programs and it is efficient:  assembly language instructions are the same  the same as those generated by a compiler except that they integrate checks for carry and other conditions that indicate exceptions and set a return code."  However, it has been shown that Howard’s library interferes with compiler optimization (as does any inline assembly), that the LeBlanc library is generally faster for sizeable applications (that have been optimized).

Similarly on page 195, the text states that "One disadvantages is that SafeInt is larger and slower than the Howard approach."  In fact, SafeInt may perform better for applications that have been compiled with optimization enabled.

Also on page 195, the text states that "RCSint combines the usability of the SafeInt template class with the performance of the Howard approach."  While this is true, RCSint also makes use of inline assembly code that could interfere with compiler optimization. 

Page 196.  GNU Multiple Precision Arithmetic Library (GMP). 
The GNU multiple precision library is licensed under the Lesser General Public License version 2.1 that accompanies the source code. 

Page  200. just before the summary. "bash -c 'ls\377who" is missing the trailing ' character.


Chapter 6: Formatted Output


Page 220. section "Overwriting Memory". para 2, after the example. "are written until the %n conversion specifier is encountered." is a little awkward and should be "are written before the %n conversion specifier is encountered."

Page 220. Section "Overwriting Memory".  The following code example:
printf("\xdc\xf5\x42\x01%08x.%08x.%08x%n");

should be:

printf("\xdc\xf5\x42\x01%08x%08x%08x%n");

so that the integer value written is correctly given as 28.
Page 225. Section 6.4  "Stack Randomization".

The following sentence:

However, many Linux variants (for example, Red Hat, Debian, and OpenBSD) include some form of stack randomization.

Should read:

However, many Unix variants (for example, Red Hat, Debian, and OpenBSD) include some form of stack randomization.

Page 227. Section "Bytes Output"

The following sentence:

This number, which depends on the distance variable summed with the length of the dummy address and address bytes, can be readily calculated.

Should read:

This value can be calculated from the distance between the argument pointer to the formatted output function and the start of the format string and the length of the dummy address and address bytes.

Page 233. Section "ISO/IEC TR 24731"

The following sentence:

These security-enhanced functions include:
fprintf_s(), printf_s(), snprintf_s(), sprintf(), vfprintf_s(), vprintf_s(), vsnprintf_s(), vsprintf_s(),
and their wide character equivalents [Meyers 04].

Should read:

These security-enhanced functions include:
fprintf_s(), printf_s(), snprintf_s(), sprintf_s(), vfprintf_s(), vprintf_s(), vsnprintf_s(), vsprintf_s(),
and their wide character equivalents [Meyers 04].

Chapter 7: File I/O



 Page 253, Section 7.3 "Files as Locks and File Locking".  

The sentence:
The code from Figure 7-3 arbitrarily selects an initial sleep time of 100 milliseconds

Should read:

The code from Figure 7-3 arbitrarily selects an initial sleep time of 100 microseconds.

 Page 258, Figure 7-5.  In line 5 of the example program, main should be declared as:
    
int main(void) {

and not:

       int main() {

to be compliant with ISO/IEC 9899:1999 as described in Section 5.1.2.2.1 "Program startup".


Page 258, the third paragraph reads:

Similar secure file opens are possible using fopen_s() and freopen_s() from ISO/IEC WDTR 2473 [ISO/IEC 04] These two functions are specified to open files in a safe mode, giving exclusive (nonshared) access.

This statement is, at the very least, misleading. The previous paragarph reads "test for file existence and the file creation are guaranteed to be atomic". The

fopen_s() and freopen_s() as currently defined do not provide this capability, although I am hoping this might be remedied. Also, these functions do attempt to open files with exclusive (also known as non-shared) access, but only to the extent that the underlying operating system supports the concepts.

Page 259, Figure 7-6. In line 4 of the example program, main should be declared as:
    
int main(void) {

and not:

       int main() {

to be compliant with ISO/IEC 9899:1999 as described in Section 5.1.2.2.1 "Program startup".

 Page 267, Section "Know What is Shared".  Last sentence, next to last paragraph should read: 
All of these reasons suggest that it is best to close all open files, excepting perhaps stdin, stdout, and stderr, before executing a different program by calling execve() or one of the various front-ends to it (e.g., exec(), system(), or popen()).

Chapter 8: Recommended Practices



page 277
 
As a result, software development practices that are meant to reduce or eliminate vulnerabilities in system development must be process agnostic [Taylor 05], that is, capable of being integrated into a broad variety of existing processes.

page 278
 
Figure 8-2 incorrectly spells "Security" as "Securty"

Page 295, Section "Testing". Replace "NIL" with "null".

 Page 298, Section "Prevent".  Instead of referring to xgcc as an open compiler, it may be better to refer to it as an "inter-procedural analysis engine".
For more informatino, see:
http://www.am-utils.org/docs/kgcc-rpe/kgcc.pdf


page 299
 
Penetration Testing
Penetration testing generally implies probing an application, system, or network from the perspective of an attacker searching for potential vulnerabilities. Gary McGraw concisely summarizes the advantages and limitations of penetration testing [McGraw 04]:
 
Penetration testing is also useful, especially if an architectural risk analysis is specifically driving the tests. The advantage of penetration testing is that it gives a good understanding of fielded software in its real environment. However, any black-box penetration testing that doesn't take the software architecture into account probably won't uncover anything deeply interesting about software risk. Software that falls prey to canned black-box testing-which simplistic application security testing tools on the market today practice-is truly bad. This means that passing a cursory penetration test reveals very little about your real security posture, but failing an easy canned penetration test tells you that you're in very deep trouble indeed.

References



[de Raadt 03]    Theo de Raadt, Advances in OpenBSD, presented at CanSecWest, Vancouver, Canada. April 2003 http://www.openbsd.org/papers/csw03/mgp00001.html.

[McGraw 04] Gary McGraw. Software Security, IEEE Security & Privacy magazine 2(2):80-83, March/April 2004.
 
[Taylor 05] Taylor, D.; McGraw, G.; Adopting a software security improvement program. Security & Privacy Magazine, IEEE. Volume 3,   Issue 3,   May-June 2005 Page(s):88 - 91



Last updated January 21, 2008