Secure Development Guidelines

The following content will likely see significant revision, though can be used as a reference for security best practices to follow when developing code for Mozilla.

Introduction

  • Provide developers with information on specific security issues
  • Cover common coding mistakes and
  • How they affect a product
  • How to avoid making them
  • How to mitigate them
  • Everything is oriented toward C/C++

Introduction: Gaining Control

  • Specifics about the underlying architecture, using x86 as an example
  • 6 basic registers (EAX, EBX, ECX, EDX, EDI, ESI)
  • 2 stack-related registers (ESP, EBP)
    • Mark top and bottom of current stack frame
  • Status register (EFLAGS)
    • Contains various state information
  • Instruction pointer (EIP)
    • Points to register being executed; can’t be modified directly

Introduction: Gaining Control (2)

  • EIP is modified using call or jump instructions
  • Attacks usually rely on obtaining control over the EIP
  • Otherwise the attacker can try to control memory pointed to by an existing function pointer
  • A vulnerability is required to modify the EIP or sensitive memory
  • Saved return addr or function pointer get altered

Introduction: Gaining Control (3)

  • Common issues used to gain control
    • Buffer overflows
    • Format string bugs
    • Integer overflows/underflows

Writing Secure Code: Input Validation

Input Validation

  • Most vulnerabilities are a result of un-validated input
  • Always perform input validation
  • Could save you without knowing it
  • Examples:
    • If it doesn’t have to be negative, store it in an unsigned int
    • If the input doesn’t have to be > 512, cut it off there
    • If the input should only be [a-zA-Z0-9], enforce it

Cross Site Scripting (XSS)

  • XSS is a type of code injection attack
  • Typically occurs in web applications
  • Injection of arbitrary data into an HTML document from another site
  • Victim’s browser executes those HTML instructions
  • Could be used to steal user credentials
  • Think: webmail, online auction, CMS, online banking...

XSS: Example

The following snippet of JavaScript:

document.write("Welcome to " + document.location);

... is exploitable (in some browsers) with a simple request such as:

http://www.victim.com?something=<SCRIPT>alert('Oops')</SCRIPT>

XSS: Prevention

  • Escape all dynamic input that will be sent back to the user
  • HTML encoding
    • &amp; → &
    • &lt; → <
    • &gt; → >
    • &quot; → "
    • &apos; → '
  • URL encoding
    • % encoding
  • Java/VBscript escaping
    • Depends on the context; in a single-quoted string, escaping ' would suffice

SQL Injection

  • Occurs when un-trusted input is mixed with a SQL string
  • SQL is a language used to interact with databases
  • Code injection attack that is similar to XSS but targeted at SQL rather than HTML and JavaScript
  • If input is mixed with SQL, it could itself become an SQL instruction and be used to:
    • Query data from the database (passwords)
    • Insert value into the database (a user account)
    • Change application logic based on results returned by the database

SQL Injection: Example

snprintf(str, sizeof(str),
"SELECT * FROM account WHERE name ='%s'", name);
sqlite3_exec(db, str, NULL, NULL, NULL);

SQL Injection: Prevention

  • Use parameterized queries
  • Insert a marker for every piece of dynamic content so data does not get mixed with SQL instructions
  • Example:
    sqlite3_stmt *stmt;
    char *str = "SELECT * FROM account where name='?'";
    sqlite3_prepare_v2(db, str, strlen(str), &stmt, NULL);
    sqlite3_bind_text(stmt, 1, name, strlen(name), SQLITE_TRANSIENT);
    sqlite3_step(stmt);
    sqlite3_finalize(p_stmt);

Writing Secure Code: Arithmetic Issues

Integer Overflows/Underflows

  • Overflows occur when an arithmetic operation attempts to create a numeric value that is larger than can be represented within the available storage space
  • MAX + 1 will be 0 and 0 – 1 will be MAX!
Bits Maximum value that can be represented Data type
8 28-1 255 char
16 216-1 65535 short
32 232-1 4294967295 int
64 264-1 18446744073709551615 long long

Integer Overflows/Underflows

  • Example of an integer overflow

int main() {
unsigned int foo = 0xffffffff;
printf(“foo: 0x%08x\r\n”, foo);
foo++;
printf(“foo: 0x%08x\r\n”, foo);
}

Integer Overflows/Underflows

  • Example of an integer underflow

int main() {
unsigned int foo = 0;
printf(“foo: 0x%08x\r\n”, foo);
foo--;
printf(“foo: 0x%08x\r\n”, foo);
}

Integer Overflows/Underflows

  • Real-life example (bug 303213)

JSBool js_str_escape(JSContext *cx, JSObject *obj, unsigned int argc, jsval *argv, jsval *rval){
...
newchars = (jschar *) JS_malloc(cx, (newlength + 1) * sizeof(jschar));
...
for (i = 0, ni = 0; i < length; i++) {
if ((ch = chars[i]) < 128 && IS_OK(ch, mask)) {
newchars[ni++] = ch;
...
}
...
}
...
}

Integer Overflows/Underflows: Prevention

  • Difficult to fix: You need to check every arithmetic operation with user input
  • Arithmetic libraries like SafeInt can help

Signedness Issues

Bits Data type Range
8 signed char -128 - +127
unsigned char 0 - +255
16 signed short -32768 - +32767
unsigned short 0 - +65535
32 signed int -2147483648 - +2147483647
unsigned int 0 - +4294967295
64 signed long long -9223372036854775808 - +9223372036854775807
unsigned long long 0 - +18446744073709551615

int vuln_funct(int size) {
char buf[1024];
if (size > sizeof(buf) - 1) return -1;
}

Signedness Issues:

  • Don’t mix signed and unsigned integers
  • Use unsigned integers for sizes, offsets, and indexes

Casting and Truncation

  • Example:

void vuln_funct() {
u_int32_t size1 = 0xffffffff;
u_int16_t size2 = size1;

void *ptr = malloc(size2);
if (!ptr) exit(EXIT_FAILURE);
memcpy(ptr, user_data, size1);
}

Casting Issues: Sign Extension

  • Example:

int main() {
int32_t new_size = 0;
int8_t size = 0xFF;

new_size = size;

printf("0x%08x\r\n", new_size);
}

Casting Issues: Sign Extension Prevention

  • Be careful with signed integers
  • Use unsigned integers for sizes, offsets, and indexes

Denial of Service: Divide by Zero

  • Example:

int main() {
int a, b;

if (argc != 3) return 1;

a = atoi(argv[1]);
b = atoi(argv[2]);

return a/b;
}

Denial of Service: Divide INT_MIN by -1

  • Example:

int main(int argc, char **argv) {
int a, b;

if (argc != 3) return 1;

a = atoi(argv[1]);
b = atoi(argv[2]);

return b ? a/b : 0;
}

Writing Secure Code: Memory Management

String Handling

  • C-style strings are byte arrays that end with a \0 byte
  • Some string handling functions won’t perform any kind of length checking, so don’t use them
  • Ensure your string is always \0 terminated!

Buffer Bounds Validations (BBV)

Thou shalt check the array bounds of all strings (indeed, all arrays), for surely where thou typest "foo" someone someday shall type "supercalifragilisticexpialidocious".

BBV: Stack Overflow

  • Example:

void foo(char *bar) {
char c[12];
strcpy(c, bar);
}

int main(int argc, char **argv) {
foo(argv[1]);
}

BBV: Stack Overflow

Before the stack overflow

before.png

BBV: Stack Overflow

After the stack overflow

after.png

BBV: Heap Overflow

  • Dynamic memory
    • malloc()
    • calloc()
    • HeapAlloc()
    • mmap()
  • Not on the stack segment!
  • Most memory allocators use a linked list or binary tree

BBV: Off-by-One

The array index starts at 0 not at 1

  • char array[1024];
  • array[0] = first element!
  • array[1023] = last element!
  • array[1024] = 1 element outside the array!

BBV: Array Indexing Issues

  • Always ensure that the index is within the bounds of the array
  • Never use signed integers as index
    • buf[strlen(buf) - 1] = '\0';
    • strlen() could return 0; resulting in a negative index!

BBV: Array Indexing Issues

  • Always ensure that the index is within the bounds of the array
  • Never use signed integers as index
    • buf[strlen(buf) - 1] = '\0';
    • strlen() could return 0; resulting in a negative index!

BBV: Prevention

  • Check the bounds of your arrays
  • Use a safe and well-designed API
  • When using integers as array indexes, use caution

Format String Bugs

  • Example:

int main(int argc, char *argv[]) {
if (argc > 1) printf(argv[1]);
}

Format String Bugs: Prevention

  • Easy to fix: Always use a format string specifier:

int main(int argc, char *argv[]) {
if (argc > 1) printf("%s", argv[1]);
}

Double Free

  • Example:

void* ptr = malloc(1024);
if (error) {
free(ptr);
}
free(ptr);

Double Free: Prevention

  • Set a pointer to NULL when it’s freed
  • Valgrind or malloc debugging can help detect those bugs

Use After Free

  • Accessing data after a free() or delete can lead to undefined behavior
  • Some debug tools might be able catch some cases

Un-initialized Data

  • Example:

int main() {
char *uninitialized_ptr;
printf("0x%08x\r\n", uninitialized_ptr);
return 0;
}

$ ./test
0x8fe0103

Un-initialized Data: Prevention

Initialize your variables!

Memory Leaks

  • Example:

void *p;
size_t new_size;

p = realloc(p, new_size);
if (p == NULL) {
/* handle error */
}

Memory Leaks: Prevention

Tools like Valgrind can help detect memory leaks

Writing Secure Code: Object Management

Reference Counting Issues

  • Real-life example (bug 440230)

void AddRef() {
++mRefCnt;
NS_LOG_ADDREF(this, mRefCnt, "nsCSSValue::Array", sizeof(*this));
}

void Release() {
--mRefCnt;
NS_LOG_RELEASE(this, mRefCnt, "nsCSSValue::Array");
if (mRefCnt == 0)
delete this
;
}

Reference Counting Issues: Prevention

  • Use the largest data type available on your platform for your reference counter
  • Use a hard limit

Constructor/Destructor Issues

  • If a constructor fails the destructor never gets called
  • This can lead to memory leaks

Constructor/Destructor Issues

  • Example

class foo {
private:
char *ptr;
public:
foo() {}
~foo() {
if (ptr)
free(ptr);
}
};

Constructor/Destructor Issues: Prevention

Initialize the data members of an object in the constructor

Writing Secure Code: Miscellaneous

File I/O

  • A lot can go wrong because a lot can be done with file input and output
    • Filenames
    • Permissions
    • File handles and descriptors

File I/O: Filename

  • Divided in directories, subdirectories, and the file itself
  • ‘/’ is separator; in Windows ‘\’ would work too
    int openfile(char *file) {
    HANDLE fh;
    if (strstr(file, “\”))
    return -1;
    fh = CreateFileA(file, ...);
    WriteFile(fh, data, sizeofdata, NULL, NULL);
    }
  • Could be a normal file, directory, device, or link
  • Directory traversal (../../../../)

File I/O: File Permissions

  • Should be set correctly
  • Be sure not to make world-writable files
  • Sensitive files shouldn’t be world readable

File I/O: File Descriptors and Handles

  • Could be a race if instances of fh are shared between threads
  • Fh inheritence: default in Unix, needs to be set in Windows int main(int argc, char **argv, char **envp) {
    int fd = open("/etc/shadow", O_RDWR);
    setreuid(getuid(), getuid());
    excve("/bin/sh", argv, envp);
    }
  • suid root applications

File I/O: File Descriptors and Handles

  • Potential overflows when using select
  • fd_set struct, static length, holds a bitmask of fds
  • Manipulated with FD_SET, FD_ISSET, FD_CLR and FD_ZERO macros
  • fd_set’s size depends on the operating system
  • If the OS allows opening more fds, then fd_set can hold
  • Could overflow fd_set

File I/O: File Descriptors and Handles

Good solution: dynamically allocate fd_set structs

int main(void) {
int i, fd;
fd_set fdset;
for( i = 0; i < 2000; i++) {
fd = open("/DEV/NULL", O_RDWR);
}
FD_SET(fd, &fdset);
}

File I/O: Race Conditions

  • Operating on files can often lead to race conditions since the file system is shared with other processes
  • You check the state of a file at one point in time and immediately after the state might have changed
  • Most file name operations suffer from these race conditions, but not when performed on file descriptors

File I/O: Race Conditions

  • Consider the following example

int main(int argc, char **argv) {
char *file = argv[1];
int fd; struct stat statbuf;
stat(file, &statbuf);
if (S_ISLINK(statbuf.st_mode)) {
bailout(“symbolic link”);
}
else if (statbuf.st_uid != getuid) {
bailout(“you don’t own the file”);
}
fd = open(file, O_RDWR);
write(fd, argv[2], strlen(argv[2]));
}

File I/O: Race Conditions

  • Previous example contains a race condition
  • The file may change between the call top stat() and open()
  • This opens the possibility of writing arbitrary content to any file

Race Conditions

  • Occur when two separate execution flows share a resource and its access is not synchronized properly
  • Race condition types include
    • File (previously covered)
    • Thread (two threads share a resource but don’t lock it)
    • Signal

Race Conditions

  • Example

char *ptr;
void sighandler() {
if (ptr)
free(ptr);
_exit(0);
}

int main() {
signal(SIGINT, sighandler);
ptr = malloc(1000);
if (!ptr)
exit(0);
... do stuff ...
free(ptr);
ptr = NULL;
}

Race Conditions

  • Previous example is vulnerable to a signal race condition
  • What would happen if the application received a signal in the middle of free(ptr)?
    • It would lead to a double free

Race Conditions: Prevention

  • Be very careful when working with threads, the file system, or signals
  • Track down shared resources and lock them accordingly
  • For signal handlers
    • Never use non-atomic operations
    • longjmp() is a sign of badness
    • Even exit() could cause problems, but _exit() is okay

Deadlocks and Locking Issues

  • Locks are used when
    • Dealing with threads
    • Acquiring more than one lock to perform an action
  • If a second thread acquires the same locks but in a different order, it causes denial of service since both threads will be waiting forever

Deadlocks and Locking Issues

  • Example

func_a() {
lock(lockA);
lock(lockB);
... do something ...
unlock(lockB);
unlock(lockA);
}

func_b() {
lock(lockB);
lock(lockA);
... do something ...
unlock(lockA);

unlock(lockB);
}

Writing Secure Code: Good Coding Practices

Banned API List

  • Badly designed APIs can often lead to vulnerabilities
  • It’s too easy to use the API inappropriately
  • For example, consider the libc string handling APIs
    • Strcpy() performs no bounds checking
    • Strncpy() doesn’t always 0-terminate
    • Strncat() length parameter is very confusing

Banned API List

  • Examples of incorrect strncat usage
    • Buffer overflow
      strncat(buffer, string, sizeof(buffer));
    • Off-by-one
      strncat(buffer, string, sizeof(buffer) – strlen(string));
  • Correct usage
    • strncat(buffer, string, sizeof(buffer) – strlen(string) – 1));

Banned API List: Recommendations

  • Create wrappers or replacements for standard functions with a bad design
Libc function name Reason to ban it
strcpy, strcat, gets, sprintf, vsprintf No bounds checking.
strncpy Does not guarantee \0 termination.
strncat Confusing size argument; can write 1 byte beyond the size.
snprintf, vsnprintf Return value differs on various platforms.
alloca Never use this. Calling alloca() with a user-controlled size == game over. Use malloc() instead.

Using the Mozilla API

  • Use C++ strings so C strings are possible
  • Mozilla C safe string handling APIs
  • Similar to libc str*cpy and str*cat
  • Some are potentially dangerous
Function name Comments
PL_strcpy, PL_strcat These functions don't verify whether the destination buffer is large enough.
PL_strncpy This function doesn't null terminate.
PL_strncat This function is confusing because the size argument is the maximum number of bytes being appended, not the size of the buffer. This can lead to security bugs.
PL_strncpyz This function copies a string and guarantees null termination.
PL_strcatn The size argument for this function is the size of the buffer and it guarantees zero termination.

Using the Mozilla API

  • Mozilla C++ style strings are less prone to buffer overflows and 0-termination issues
  • Every string derived from the abstract base class nsAString
  • Common read-only methods
    • Length()
    • isEmpty()
    • Equals()
  • Common methods for modifying the string
    • Assign()
    • Append()
    • Insert()
    • Truncate()

Checking Return Values

  • Often causes problems
  • Return value not handled
  • Certain cases not handled or interpreted incorrectly
  • Double meaning
    • malloc() can return a pointer or NULL, but NULL by itself is a valid address

Checking Return Values

int main() {
int fds[2];
pipe(fds);
write(fds[0], "data", 4);
}

  • The pipe() return value is not checked
  • If pipe() fails, fds is not initialized
  • Write to un-initialized file descriptor

Checking Return Values

  • Check all return values—no matter how unlikely the API failure
  • For example:
    • close() can fail and leak file descriptor
    • setuid() can fail and privileges don’t get dropped
    • snprintf() can fail and result in return value -1
    • tmp = realloc(tmp, size) — realloc could fail and leak tmp

Writing Secure Code: Exception Handling

Double Freeing Pointers

  • Double frees can occur in exception handling
  • They free data in try block and then free it again in the catch block
  • This is a common issue

Double Freeing Pointers

char *ptr1 = NULL, *ptr2 = NULL;
try {
ptr1 = new char[1024];
do_something();
delete ptr1;
do_something_else();
ptr2 = new char[-1] // OOM
} catch (...) {
delete ptr1;
delete ptr2;
}

  • New will throw an exception — ptr1 is already freed in the try block then freed again in the catch block

Freeing Un-initialized Data

  • Free data in the catch block
  • Assumption is that it’s initialized in the try block
  • If the try block throws before the variable is initialized, the catch block will operate on un-intialized data

Freeing Un-initialized Data

  • Example:

int main(){
char *ptr;
try {
ptr = new char[-1]; // OOM
} catch(...) {
delete ptr;
}
}

Freeing Un-initialized Data: Prevention

  • Be careful when freeing data in catch blocks
  • Make sure the try block can’t throw before data is initialized
  • Initialize variables when they’re declared; for example, set pointers to NULL

Memory Leaks

  • Usually occur when memory is allocated in a try block
  • After the allocation, something throws in the try block before memory is freed in the try block
  • The catch block does not foresee this and doesn't free the memory

Freeing Un-initialized Data

  • Example:

int main(){
char *p;
try {
p = new char [1000];
... code that might throw an exception ...
delete p;
} catch (...) {
...
}
}

Memory Leaks: Prevention

  • Any acquired resource in a try block should be freed in a catch block (if the try block throws before the resource is freed)
  • Might be helpful to initialize variables
    • NULL for pointers
    • -1 for file descriptors