CHAPTER 7
SymbianOS Security

image

SymbianOS is an operating system designed specifically as a foundation for developing smart devices. It is a direct descendeant of the EPOC family of PDA operating systems developed by Psion. In 1998, Symbian Ltd. was established as a joint venture between Psion, Nokia, Motorola, Ericsson, and NTT DoCoMo in order to manage the future development of SymbianOS. Since then, it has become the dominant smartphone operating system, accounting for 46.6 percent of all smartphones shipped worldwide in the third quarter of 2008. Phones are manufactured by a wide variety of manufacturers, including LG, Nokia, NTT DoCoMo, Motorola, Samsung, and Sony Ericsson.

Introduction to the Platform

SymbianOS currently provides most of the functionality for a mobile operating system, but lacks essential features such as a GUI toolkit. This is generally provided by one of three related but incompatible framework libraries: S60, UIQ, or MOAPS. This means that a developer must develop against each platform separately. Though used by other manufacturers, S60 is largely associated with Nokia, UIQ with Motorola and Sony Ericsson, and MOAPS with NTT DoCoMo. Although important to recognize, the inherent differences among these frameworks do not impact secure development practices on the SymbianOS platform.

The most current version of SymbianOS is 9.4 and is the foundation for the fifth edition of S60. This is available on the Nokia 5800 XpressMusic. It is both API and ABI compatible with previous releases in the 9.x series, and software developed for previous versions should be able to run without modification. With that in mind, one should develop against the oldest software development kit (SDK) available for the 9.x series that provides the required features. This allows for applications to achieve the widest adoption possible. Current SDKs and documentation are freely available to developers at the SymbianOS developer site.

Smartphones relying upon the SymbianOS platform include:

image    Nokia 5800 XpressMusic (S60 5.0/SymbianOS 9.4)

image    Samsung SGH-L870 (S60 3.2/SymbianOS 9.3)

image    Nokia N96 (S60 3.2/SymbianOS 9.3)

image    Nokia E71 (S60 3.1/SymbianOS 9.2)

image    LG KS10 (S60 3.1/SymbianOS 9.2)

image    Motorola MOTO Z10 (UIQ 3.2/SymbianOS 9.2)

image    Sony Ericsson W950 (UIQ 3.0/SymbianOS 9.1)

image    Sony Ericsson P1 (UIQ 3.0/SymbianOS 9.1)

In June 2008, Nokia announced its intention to establish the Symbian Foundation as an autonomous entity to unify and guide future SymbianOS development. To that end, Nokia acquired all outstanding shares of Symbian Ltd. In addition, S60, UIQ, and MOAPS were all contributed by their stakeholders to the Symbian Foundation to advance the goal of unification. The S60 framework has been selected, with key components from UIQ and MOAPS, as the standard for future SymbianOS development. The foundation has also committed to open-sourcing SymbianOS, with the intent of using the OSI-approved Eclipse Public License. The first Symbian release arising from this enterprise will be known as Symbian ^ 2, based on S60 5.1; in the meantime, the Symbian Foundation has re-released the fifth-edition S60 SDK as Symbian ^ 1. The software should be available during the second half of 2009, with shipping hardware to arrive on the market at the beginning of 2010.

Device Architecture

In order to provide flexibility in hardware support, SymbianOS implements a layered design where the majority of software components are provided by SymbianOS. Device original equipment manufacturers (OEMs) provide the concrete implementations of necessary interfaces in order to interact with their product. Figure 7-1 shows a simplified representation of a SymbianOS device.

image


Figure 7-1 SymbianOS layered architecture

Hardware Layer

The hardware layer represents the physical hardware of the device and is wholly the responsibility of the manufacturer developing the device. SymbianOS devices can currently be based on the ARMv5, ARMv6, or ARMv7 architecture, although the layered design allows for a relatively straightforward porting process to other architectures. Whatever hardware SymbianOS is operating on, it is only able to take advantage of the facilities exposed through the Hardware Abstraction Layer (HAL).

Hardware Abstraction Layer (HAL)

The HAL sits between the hardware and the kernel to significantly reduce the development effort required to support new platforms. Hardware-specific interaction is factored into a Board Support Package (BSP) consisting of concrete implementations of several abstract interfaces consumed by the kernel. In this fashion, SymbianOS can run on a wide variety of hardware platforms. SymbianOS also provides a set of reference/test BSPs that OEMs can adapt to assist in the rapid development of device-specific code.

Kernel Services

The kernel services layer is responsible for managing system resource allocation. In addition to EKA2, the actual microkernel, several other critical services run within this layer. For example, although the file server and assorted file systems do not execute within the kernel memory context, they are included here because devices would fail to function properly in their absence.

OS Services

The OS services layer includes a set of frameworks and libraries that are not critical for the functioning of the device, but without which the device could not do anything useful without dramatic effort. This includes libraries and services providing access to telephony, networking, windowing, multimedia, and so on. These components execute as processes within the user memory context and are accessed through approved kernel-mediated interprocess communication (IPC) mechanisms.

User Applications and Frameworks

The top layer consists of various user applications and frameworks, including both those preinstalled by an OEM and aftermarket applications installed by the end user. Applications at this layer access device-specific functionality exclusively through OS-provided services. As such, applications written against a specific SDK can be compiled for and run on a wide variety of supported phones. The end user generally associates these components with the device because they perform useful and visible tasks.

Device Storage

Storage on SymbianOS-based phones can be broken down into three logical components: ROM, fixed internal storage, and removable storage. All SymbianOS phones include ROM and some form of fixed storage; most also include additional removable storage. Access to all storage is performed through a common interface provided by file system drivers, concealing any physical differences among the devices.

ROM

Built-in system code and applications are placed in a read-only segment, visible to the system as the Z: drive. Although this can be an actual masked ROM or other specialized ROM device, it is commonly implemented using some form of flash memory. This allows the manufacturer to readily upgrade system components throughout the life cycle of a device, including by an end user. Some devices use NOR-based flash memory that is byte addressable and supports eXecute-in-Place (XiP), where code is run without copying into RAM. Others use NAND flash that is only block addressable, requiring that code be copied into RAM before execution. An OEM may choose to use NAND flash because this allows them to use the same physical chip to provide both the ROM and fixed storage functionality.

Fixed Storage

Fixed storage is visible to the operating system as the C: drive and is available for persistently storing user settings, data, and applications. This is generally implemented using NAND flash memory, although this is not the only possibility. The size of this storage can vary widely among manufacturers. Some devices will have additional internal storage made available to the system using another unique drive letter.

Removable Storage

Many SymbianOS-based phones support some form of removable storage medium such as Secure Digital (SD), MultiMedia Card (MMC), or CompactFlash (CF). Removable storage is also a local storage medium and is assigned one of the drive letters reserved for local devices (in practice, this is generally the D: or E: drive). Phone users can store personalized settings and data, or they can install applications onto removable storage just as they would with internal storage. Because the storage is removable, stored data can be readily modified by mounting the storage device on a computer. To maintain the integrity of the installer as the sole gatekeeper for installed applications, a hash of application binaries installed onto removable storage is kept in a private location on internal storage. This prevents tampering with applications installed onto removable storage to gain elevated privileges for the binary.

Development and Security Testing

A plethora of tools can be used in developing applications for SymbianOS-based devices. Developing for SymbianOS is relatively straightforward, although it can at times appear daunting due to the large number of SDKs and runtime environments. Developers can choose between writing code in Symbian C++, Open C, Python, and Java, among others. This chapter focuses on Symbian C++ (because it remains the most popular option) and briefly mentions Open C and Python. Other runtime environments either do not come preinstalled on the device or do not provide access to the newest APIs.

Development Environment

Development for SymbianOS can be performed solely with a text editor and an SDK (providing both libraries and a compiler). However, the use of an integrated development environment (IDE) is encouraged to assist in correct and rapid development. As such, Symbian provides Carbide.c++ as an IDE (see Figure 7-2). It consists of a modified version of Eclipse and the C development toolkit (CDT). The majority of the modifications have been implemented in the form of additional plug-ins to Eclipse, including primarily the customized build system.

The current version of Carbide.c++ comes in three editions: Developer, Professional, and OEM. Developer includes features commonly available in any development environment. It provides wizards for creating new projects and source files, an editor for modifying build system files, debugging support using either the emulator or a physical device, and an integrated mechanism for signing application installers.

Professional includes all of the features that come with Developer, plus support for using development devices, debugging of kernel-context code on device, an application profiler, and basic static analysis. OEM builds further, including all of the features of Developer and Professional, and provides support for stop-mode debugging on the device.

image


Figure 7-2 Carbide.c++

Previous versions of Carbide.c++ were not freely available, and the different editions were sold at several different price points. However, since the release of version 2.0, each of the editions is available free of charge. A single installer is downloaded and the desired edition is selected during the installation process. Because Carbide.c++ is freely available, all developers should install the Professional edition at the very least to make use of the application profiler and static analysis tool.

Carbide can be downloaded as a part of the application development toolkit (ADT) at http://developer.symbian.org/main/tools_and_kits/downloads/view.php?id=2.

Software Development Kits

In order to develop for SymbianOS-based phones, you must obtain an SDK. Previously, there was not a single “SymbianOS” SDK, although this has changed with the release of Symbian ^ 1 and the forthcoming Symbian ^ 2. A separate SDK is required to develop for both S60- and UIQ-based phones.

Each of the platform SDKs is largely self-contained, with everything required to perform application development. This includes a version of the GCC compiler, a phone emulator, header files, and libraries targeting the emulator. The differences among the SDKs arise with the additional header files and libraries that are included. For example, the S60 SDK includes the header files required to compile against the S60 libraries.

Other add-on SDKs provide additional header files and libraries targeting both the emulator and actual devices. These can be used to access additional functionality, as provided by the encryption API, to reduce the programming burden on the developer, as with the core idioms library, or to facilitate development in additional programming languages, such as Python.

The base platform SDK (for Symbian ^ 1 as well as S60) is available at http://developer.symbian.org/main/tools_and_kits/downloads/view.php?id=3.

Additional SDKs and libraries include:

image    Python S60 https://garage.maemo.org/frs/?group_id=854

image    Encryption API http://developer.symbian.com/main/tools_and_sdks/developer_tools/supported/crypto_api/index.jsp

image    Core Idioms (EUserHL) http://developer.symbian.org/wiki/index.php/File:EUserHL.zip

Emulator

A Symbian device emulator for Windows computers is included with the various platform SDKs (see Figure 7-3). Its use increases the pace of the test/development cycle by eliminating the need to load test code onto a physical device until the final stages of development. It should not be used as a complete replacement for testing on a physical device. Although the emulator provides an appropriate environment for most aspects of development, it is not a completely accurate representation of what would be found on a physical device.

The emulator provides for triggering events to observe application behavior, including sending SMS/MMS messages, low battery, attaching the charger, and so on. It also supports bridging COMMS through the host Windows machine in order to allow testing of Internet-based applications.

One key difference between a physical device and the emulator is the memory model exposed. The emulator exists within a Windows process, and memory management is performed using standard Windows APIs. This means that code is also compiled into the standard Windows DLL format. For example, memory allocation is performed using VirtualAlloc(), libraries are loaded using LoadLibrary(), and Symbian threads are created on top of native Windows threads. Because all code on the emulator is running within a single Windows process, there is a single Windows address space. This means that there is no memory protection between emulated Symbian OS processes or even between “user mode” and “kernel mode.” As such, what works within the confines of the emulator might not work on a physical device. The design was implemented in this way to take advantage of preexisting Windows debugging tools and techniques.

image


Figure 7-3 The S60 emulator

Debugging

Debugging of Symbian applications can be performed both through the use of the emulator as well as via a serial connection to a physical device. Debugging within the emulator is a straightforward process. However, due to the differences against debugging using a physical phone, it is also necessary to perform on-device debugging.

Emulator Debugging

Emulator debugging is performed through the debugging view of Carbide.c++. Starting a debugging session for a project will automatically launch the target application within the emulator and attach to the emulator process. The debugging view provides several additional frames to the user interface. The top left lists the threads running within the emulator, and the top right lists the watch variables, current breakpoints, and SymbianOS-specific data.

To create a new breakpoint, double-click on the margin of the line of source code where you would like to explore the program flow control. At this point, variables can be examined to determine whether they contain the expected value. If they do, step over, step out, and step into functionality is supported. This allows the developer the flexibility to only delve into the functions they are interested in.

On-Device Debugging

On-device debugging can be separated into three separate categories, each provided by a different edition of Carbide.c++. Application-level debugging is performed using the AppTRK device agent, where the debugger can attach to user threads and has a generally restricted view of system memory. System-level debugging is performed using the SysTRK device agent, where the debugger can attach to system threads and processes that were loaded from the ROM image. It has a largely unrestricted view system memory. This can only be performed on a development device. Finally, debugging can also be performed through a dedicated JTAG link. This provides the deepest view of code executing on the device. System-level and dedicated JTAG debugging are not available to the average SymbianOS developer.

IDA Pro

IDA Pro from Hex-Rays (www.hex-rays.com) is a ubiquitous tool for binary disassembly and reverse engineering (see Figure 7-4) and has supported SymbianOS 9.x as a first-class target since version 5.3. IDA Pro supports more than just disassembly—it can also connect to the AppTRK debugging agent to perform live debugging. IDA Pro Standard supports loading Executable Linker Format (ELF) files and includes an ARMv4 processor module. IDA can parse SIS installation scripts and list the available binaries for review inside.

image


Figure 7-4 IDA Pro

IDA Pro can also act as a debugger instead of Carbide.c++. In doing so, IDA will communicate with the AppTRK debugging agent. This allows you to take advantage of the advanced visualization tools that IDA provides during debugging.

Code Security

The primary development language for SymbianOS is a modified dialect of C++ known as Symbian C++; however, additional language runtimes are available. Developers may opt to write applications with P.I.P.S (P.I.P.S Is Posix on SymbianOS), Open C (an extension of P.I.P.S.), Python, Java, or other languages. In general, additional runtimes do not come preinstalled, and the runtime installer should be embedded within the installers of relying applications.

Symbian C++

With Symbian C++, code executes directly on the hardware of the phone, without a virtual machine or interpreter, allowing a developer to take full advantage of the available resources. Unfortunately, such native code does not provide protections against many common memory corruption vulnerabilities. This includes stack overflows, heap overflows, and integer overflows. In essence, the programmer is entirely responsible for preventing these vulnerabilities through appropriate secure coding practices. Otherwise, an attacker could potentially cause their malicious code to execute within an exploited process.

Descriptors

Symbian provides technologies to reduce the chances of exploitable buffer overflow conditions, but the programmer must take advantage of them. For example, buffer overflows regularly result from mishandling data when copying, formatting, or concatenating strings and byte arrays. To protect against such errors, Symbian C++ provides the descriptor framework to replace all C-style strings and the related string-handling operations. They are called descriptors because each instance stores its type, length, and data, thus describing everything needed to safely manipulate the stored data. Mutable descriptors (those whose name does not end with the letter C) also include the maximum length. Figure 7-5 presents the relationships among the varied descriptor classes.

Descriptors come in both 8-bit-wide and 16-bit-wide varieties and can be identified by the number appended to the name (for example, TDesC8 and TBuf16). When a descriptor is used that includes neither a 16 nor an 8, the 16-bit version will be used. (Technically a determination is made based on whether the _UNICODE macro is defined, which is the platform default.)

It is important to note that the type and the length are stored within a single 32-bit Tint field. The four most significant bits indicate the type of the descriptor, and the remaining 28 bits represent the size of the referenced data. This has several important consequences. First, descriptors are limited to a maximum size of 256MB (228 bytes). Second, developers must be exceptionally careful deriving custom descriptor subclasses with regard to the type field. The TDesC base class uses this field to determine the memory layout of its subclasses. This means that custom subclasses cannot have an arbitrary memory layout, but must share a type and memory layout of a built-in descriptor. It is not recommended to derive custom descriptor classes.

image


Figure 7-5 Relationships among the Different Descriptor Classes

For example, accessing the descriptor data of each subclass is performed with the non-virtual Ptr() method defined in the TDesC base class. (In fact, there are no virtual methods within the descriptor hierarchy.) This method uses the type data stored in each descriptor instance as the control variable for a switch statement that then returns the proper address referring to the beginning of the data.

By preserving the length (and maximum length) of a descriptor, its method calls are able to perform appropriate validation and prevent the unintentional access to out-of-bounds memory. When a method call would read or write beyond the data boundaries, the process will panic and immediately terminate. This means that with traditional descriptors, the burden of correct memory management falls solely on the shoulders of the developer. They must verify that the descriptor can accommodate the amount of incoming data before calling a method that would modify the contents of the descriptor.

Recognizing these difficulties, Symbian has developed and released a new library. The EUserHL Core Idioms Library provides a pair of descriptors (LString for Unicode-aware text and LData for simple byte buffers) that manage their own memory. These descriptors automatically reallocate themselves to increase their capacity and then release the resources when they go out of scope, similar to the behavior of standard C++ strings. Because these two classes have been derived from their respective TDesC class (TDesC8 and TDesC16), they can be used as parameters directly when accessing the preexisting platform API. LString and LData should be used instead of any of the other descriptor classes.

CAUTION

Because descriptor methods are not virtual, preexisting methods in the class hierarchy will still cause a process panic when the buffer is not large enough to hold the fresh data. For working with LString and LData directly, these methods have been restricted with the private access modifier and public replacements have been introduced. These can be identified by the “L” suffix common to all API functions that may leave (throw an exception). However, LString or LData objects passed to a function taking TDes16 or TDes8 parameters will not have the new methods called. You should ensure that the LString/LData object is large enough to hold any resultant data when calling such a method. The ReserveFreeCapacity method should be called before calling any such method.

Arrays

Memory management issues can also arise when manipulating bare arrays. After all, a C-style string is a null-terminated array of chars. Rather than use a C-style array (int a[10]; int * b = new int[10];), Symbian provides many classes for safe array management. In fact, the number of separate classes available can be daunting.

For the predominant use cases, the RArray and RPointerArray template classes are the recommended choices. These two classes manage their own memory and implement a traditional array interface (that is, indices can be accessed through the [] operator, and objects can be inserted, deleted, and appended). Attempts to access indices outside the bounds of the current size (either negative or greater than the current size) will cause the process to panic.

Using the RArray class has some additional restrictions that RPointerArray is not subject to. Due to an implementation detail, objects stored in an RArray class must be word-aligned on a four-byte boundary and cannot be larger than 640 bytes. Attempts to create an RArray of objects larger than 640 bytes will cause the process to panic. In these cases, consider using an RPointerArray instead. Here’s an example:

LString ex1 (_L (“example1”)), ex2(_L (“example2”));
LCleanedupHandle< RArray< LString > > strArray;
strArray->ReserveL(10);
strArray->AppendL(ex1);
strArray->InsertL(ex2, 0);
strArray->Remove(0);
strArray->Compress();

Integer Overflows

Symbian does not provide for automatic protections against integer overflows. An integer overflow occurs when the memory representation of an integer variable lacks the capacity to hold the result of an arithmetic operation. The behavior when such a condition arises is left as an implementation-specific detail. Integer overflows can cause security vulnerabilities in many instances—for example, when allocating memory or accessing an array. Developers must consciously take strides to eliminate the risk associated with such errors. As such, each arithmetic operation should be checked for overflow conditions.

When a type is available whose representation is twice as large as the type that is to be checked—for example, a 64-bit integer and a 32-bit integer—the overflow tests are straightforward. This is because the larger type can accurately hold the result of any arithmetic operation on variables of the smaller type. Symbian provides both a signed and an unsigned 64-bit integer type that can be used to perform such testing:

TInt32 safe_add(TInt32 a, TInt32 b) {
    TInt64 c = static_cast< TInt64 >(a) + b;
    if((c > INT_MAX) || (c < INT_MIN)) {
        User::Leave(KErrOverflow);
    }
    return static_cast< TInt32 >(c);
}

If the parameters to an arithmetic operation are known at compile time (for example, using literals and constants), then the GCCE compiler will be able to flag the potential integer overflows—GCCE being one of the compilers that targets the physical phone. Each of these should be reviewed to ensure that the overflow does not cause an issue. Note that the four SID and the four VID security policy macros will trigger a false positive integer overflow and can be safely ignored.

Leaves and Traps

SymbianOS has a system known as leaves and traps for handling many error conditions. Calling User::Leave(TInt32 ErrorCode) is directly analogous to the throw statement from standard C++, and the TRAP/TRAPD macros take the place of the try/catch statements. (The TRAP macro requires the developer to explicitly declare an error code variable, whereas the TRAPD macro does this automatically.) A function that could potentially cause such an error is referred to as a function that “may leave.” These can be identified in the standard library through a naming convention—each function whose name ends with a capital L can potentially leave.

In older versions of Symbian, these could be imagined as being implemented using the setjmp and longjump pair of functions. This means that program flow could jump from one function to another without performing appropriate stack cleanup; the destructors for stack objects were not executed. This necessitated implementing a custom system for managing heap allocated memory in order to prevent memory leaks. This system is called the Cleanup Stack. Every time a developer allocates an object on the heap, a reference should be pushed onto the cleanup stack. Whenever an object goes out of scope, it should be popped off the cleanup stack and deallocated. If a function leaves, all objects on the current cleanup stack frame are removed and deallocated. The beginning of a stack frame is marked by the TRAP/TRAPD macros and can be nested.

Another consequence of the abrupt transfer of program flow control was that constructors could not leave. If they did, the partially constructed object would not be properly cleaned up because the destructor would not be called and no reference had been pushed onto the cleanup stack. In order to get around this, many classes implement two-phase construction, where the actual constructor does nothing but return a self-reference and a second method ConstructL initializes the object. These methods are oftentimes private. Instead, classes offer a public method, NewL or NewLC, that creates an object, initializes it, and returns a reference.

In the 9.x series of SymbianOS, the leaves and trap system has been implemented on top of standard C++ exceptions. When a function leaves, several steps are performed:

1. All objects in the current frame of the cleanup stack are removed and deallocated, calling any object destructors.

2. An XLeaveException is allocated and thrown. This exception will be created using preallocated memory if new memory cannot be obtained (for example, during an out-of-memory condition).

3. Through the standard C++ exception procedure, the call stack is unwound and the destructor is called on each stack object.

4. One of the TRAP/TRAPD macros catches the thrown exception and assigns the error code to an integer variable that a developer should test. Any other type of exception will cause the process to panic.

This backward compatibility has several interesting consequences. First, a developer should never mix the use of standard exceptions and SymbianOS leaves. A function that leaves should not call a function that throws exceptions without enclosing it within a try/catch block in order to prevent propagation back to a TRAP macro. Similarly, any function that may leave called from code using only standard exceptions should be wrapped with one of the TRAP/TRAPD macros.

When leaves and traps were mapped onto the standard exception mechanism, the use of two-phase construction remained. With the release of the EUserHL Core Idioms Library, you may return to a more traditional Resource Acquisition Is Initialization (RAII) model. When you define a new class, you should use the CONSTRUCTORS_MAY_LEAVE macro as the first line of code. This macro actually defines how the delete operator will affect the class:

    class CUseful : public CBase {
        public:
            CONSTRUCTORS_MAY_LEAVE
            CUseful ();
            ~CUseful();
    };

Finally, the use of a cleanup stack continued to be useful because there were several classes that did not adequately clean up properly upon destruction. These objects required the developer to explicitly call a cleanup method—generally Close, Release, or Destroy. The EUserHL Core Idioms Library also includes a set of classes to assist the developer with proper resource management in these cases. This includes the LCleanedupXXX and LManagedXXX classes. The LCleanedupXXX group of classes, found in Table 7-1, is designed for use with local function variables, and the LManagedXXX group of classes, found in Table 7-2, is designed for use with class member variables.

These classes implement what can be viewed as a set of “smart pointers.” The developer no longer has to worry about pushing and popping objects from the cleanup stack, deleting memory before a reference goes out of scope, or calling a resource cleanup function. This is all performed appropriately when one of the LManagedXXX/LCleanedupXXX objects goes out of scope. In the following example, the handle to the file server and to an open file are automatically closed and released when the function ends:

image

Table 7-1 Local Variable Automatic Resource Management

image

Table 7-2 Class Member Variable Automatic Resource Management

void readFileL(LString filename, LString data) {
    LCleanedupHandle<RFs> fs;
    LCleanedupHandle<RFile> file;
    TInt size;

    fs->Connect() OR_LEAVE;
    file->Open(*fs, filename, EFileReadOR_LEAVE;
    file->Size(size) OR_LEAVE;

    data.SetLengthL(0);
    data.ReserveFreeCapacityL(size);

    file->Read(data) OR_LEAVE;
}

And in the following example, the CMessageDigest object that is allocated in the constructor of the CUseful class is automatically cleaned up when an instance goes out of scope. The destructor does not need to do anything to prevent memory leaks.

class CUseful : public CBase {
    public:
        CONSTRUCTORS_MAY_LEAVE
        CUseful() : hash(CMessageDigestFactory::NewHMACL (
            CMessageDigest::ESHA1, HMACKey)) { }
        ~CUseful(){ }

    private:
        LManagedPtr< CMessageDigest > hash;
};

The different groups of classes, LCleanedupXXX and LManagedXXX, arise due to interaction with the cleanup stack. Indeed, if the LCleanedupXXX classes were used for class member variables, then objects would be popped and destroyed from the cleanup stack out of order.

Use of these classes is recommended in order to reduce the possibility of manual memory management errors, such as memory leaks, double frees, null pointer dereferences, and so on.

Automatic Protection Mechanisms

SymbianOS does not guarantee the presence of any automatic protection mechanisms to mitigate memory corruption vulnerabilities. Neither address space layout randomization nor stack canaries are available in any form. A nonexecutable stack is only available when run on hardware that supports it—namely, ARMv6 and ARMv7, but not ARMv5.

Although such mechanisms are not perfect in preventing the exploitation, they do increase the level of skill required to craft a successful exploit. Because they are not present, developers must continue to be exceptionally careful to reduce the risk of code execution vulnerabilities.

P.I.P.S and OpenC

P.I.P.S. is a POSIX compatibility layer that aides in the rapid porting of software to SymbianOS-based phones. OpenC is an S60 extension of P.I.P.S. that brings a larger set of ported libraries. These environments are not suitable for GUI code, which must continue to be written in Symbian C++.

P.I.P.S. and OpenC are implemented as shared libraries that are linked into native code applications. This means that applications written for either of these environments suffer from all of the same memory corruption flaws. In fact, it is more likely to have such problems because the environment does not provide safer alternatives—namely, descriptors and array classes. As such, string handling is performed with C-style strings and the associated set of blatantly unsafe functions, such as strcat and strcpy.

P.I.P.S. and OpenC should only be used for POSIX code ported from other platforms, not for newly written code for the SymbianOS platform.

Application Packaging

After developing an application, it must somehow be distributed to end-users in a manner that they can use. After all, you can’t distribute the source code and expect users to know how to compile the source code into the appropriate format for their phone. Further, simply providing compiled binaries is also not enough. Where are they supposed to be located on storage? How do users know that the applications they have received have not been tampered with?

Applications for the Symbian platform are compiled into a modified version of the ELF executable format known as E32Image that supports Symbian specific requirements. Since access to the local file system is restricted via capabilities; installation of applications is managed by a privileged process on the device. Applications are provided to end-users in an archive format that provides instructions to this installation gatekeeper on where to place the final applications.

Executable Image Format

Applications are compiled to the ARM Embedded Application Binary Interface (EABI). Code compiled with GCCE can link against code that is compiled with RVCT because they use the same EABI. Applications are initially compiled and linked into ELF executable format executables and are then transformed into the E32Image format. The most important difference is the addition of a Symbian platform–specific header. This header includes several key fields that are used to make security decisions within executing applications:

image    UID3 A 32-bit value that uniquely identifies an application. It must be locally unique on the device and should be globally unique.

image    SecureID Another application-specific identifier, generally identical to UID3.

image    VendorID A globally unique value that identifies the developer of the image.

image    Capabilities A 64-bit field that identifies the capabilities the image requires.

image

Table 7-3 Protected Range UIDs

UID3/SecureID are allocated from within the same 32-bit range according to their purpose. This range is split in half into protected and unprotected chunks. The lower half (0x00000000–0x7FFFFFFF) is protected and an allocation must be requested from Symbian Signed. This ensures that these values are globally unique. Table 7-3 lists the various protected sub-ranges and their intended purpose. The installation process validates that UID3 and SecureID are only used with executables whose installation scripts were signed and that validate against a trusted root. The installation also validates whether any nonzero VendorIDs fall within the allocated range and that the installation script is signed.

The upper half (0x80000000–0xFFFFFFFF) is unprotected. The installation process does not enforce that installation scripts containing executables with these UIDs be signed. Developers should still obtain an allocation from within this range in an effort to promote global uniqueness of the application identifier. Use UIDs in the 0xAxxxxxxx range for unsigned applications destined for distribution and UIDs in the range 0xExxxxxx for development purposes. Table 7-4 lists the unprotected sub-ranges and their intended purpose.

To obtain a UID/SecureID allocation, follow these steps:

1. Visit www.symbiansigned.com.

2. Register for and/or log into an account.

image

Table 7-4 Unprotected Range UIDs

3. Click on the My Symbian Signed tab along the top.

4. Click on UIDs, and then click Request in the left sidebar.

5. Select either Protected Range or Unprotected Range.

6. Enter the required information and click Submit.

Installation Packages

Symbian applications are distributed in SIS files—an archive-like file format that includes the files for installation as well as a manifest-like file that specifies where they should be placed on the filesystem. These packages support a basic level of flow control to allow developers to create a single installer that contains the binaries for different Symbian versions/platforms.

All SIS files consist of a header and a sequence of word-aligned Type-Length-Value (TLV) SISFields. This allows for a parser to efficiently skip over unnecessary parts of the file without wasting space by loading the entirety into the limited memory available. The basic layout can be seen in Figure 7-6. The header consists of three package UID (pUID) values and a UID checksum. However, these should not be confused with the UID values within the header of an executable image. The first pUID identifies the package as an SIS file and will always be set to 0x10201A7A. The second pUID is reserved for future use and is generally set to 0x00000000. The third pUID uniquely identifies the installation package, allowing the installer to identify package upgrades in the future. Attempting to install another SIS file with the same pUID will fail. The rules regarding the selection of an appropriate pUID are identical to those surrounding the UID3 values found within executables. It is recommended to use the UID3 of the principal application within the installation script as the pUID.

image


Figure 7-6 Basic layout of fields within an SIS file

Directly after the header is an SISContents field that encompasses the rest of the file. This acts a container for SISController and SISData fields (and corresponding checksums). The SISController field includes all of the metadata regarding the package, including the type of installation, the language, and required dependencies. The SISData field is an array of SISDataUnits, each of which is an array of SISFileData fields, the actual files to be copied onto the device. SISData fields consist of multiple SISDataUnits to allow for embedding package dependencies within a single installation file. This can be seen in Figure 7-7. The SISController field from the child package is embedded within the SISController field of the parent. The SISDataUnit field(s) from the child package are added to the SISData field of the parent. A child SIS file may itself have another SIS nested within it. The nesting of these packages is limited to a depth of eight.

Signatures

SymbianOS executable images do not contain signatures. Instead, SIS installation scripts can contain a signature block. When signed, installation scripts generally have an .sisx file extension. This means that signatures are validated at installation time, not at runtime. Were an executable to be modified on the device by a highly capable process after installation, it would remain undetected. Because all executables must be loaded from the restricted install directory, very few applications have the capabilities required to modify installed executables.

image


Figure 7-7 Layout of a parent SIS file with a nested child

An SIS file can have multiple signatures, allowing for applications that require manufacturer approval to be signed by multiple roots. The signature field is nested within the SISController field. The signature covers every field within the SISController field except for the SISDataIndex field at the end. This includes previous signatures. This can be seen in Figure 7-8. The SISDataIndex field is not included because it is modified whenever an SIS file is embedded within another one.

At first glance, this does not appear to preclude modification of the SISData field. After all, the signature only covers the SISController field. However, within the SISController field is the SISInstallBlock field consisting of an array of SISFileDescription fields. These SISFileDescription entries include an SHA-1 hash of the contents of the corresponding files within the SISData block. Attempts to manipulate the files directly will invalidate this hash, and manipulating this hash invalidates the digital signature.

Signatures can be created from arbitrary certificate/private key pairs, including self-signed. However, the capabilities available to an SIS installation package will be limited to the extent that the signatures validate to a set of trusted roots. Different trust roots can certify different capabilities. In this fashion, it may be necessary to have multiple signatures to have access to all of the capabilities requested.

Symbian Signed

Symbian Signed is an umbrella process for properly signing application installers during development and for release. In order to make this work, the Symbian Signed root certificate is preinstalled onto SymbianOS-based devices by the device manufacturer. The device manufacturer will also generally provide its root certificate. The use of Symbian Signed is not required of manufacturers taking advantage of the SymbianOS platform; however, most do in order to take advantage of the ecosystem of signed applications.

image


Figure 7-8 Layout of a signed SIS file

The Symbian Signed program can be developed into four main categories: OpenSigned Online, Open Signed Offline, Express Signed, and Certified Signed. Each of the categories enforces different requirements to obtain a signature and impose different limitations on their use.

Open Signed Online is the first tier of Symbian Signed. Anyone can get their application signed by providing an International Mobile Equipment Identity (IMEI) number, an e-mail address, and the SIS installation package. Open Signed Online can be used to grant any User and System capabilities. This will be signed by a developer certificate. Upon installation, the user is informed that the application was signed by a developer certificate and asked whether they would like to continue. Further, the package that it is being installed on the phone specified will be validated based on the IMEI number provided during signing.

Open Signed Offline provides a developer certificate that can be used to sign multiple SIS installation files that can generally be used on up to 1,000 devices by IMEI number. This certificate can be used to grant any User and System capabilities, although installation will still be confirmed by the end user due to the use of a developer certificate. Obtaining this certificate requires one to have a Symbian Signed account as well as to have purchased a valid Publisher ID. A special Open Signed Offline certificate can be requested that also grants restricted and manufacturer capabilities by sending the request to the device manufacturer. Installers signed by this certificate can only be used on a phone with an IMEI number that matches one of the 1,000 recorded numbers.

Express Signed is the first tier intended for end-user release. Installation packages signed through the Express Signed program are valid for ten years and can be installed onto any device. Express Signed applications can use both User- and System-level capabilities. Applications must meet the Symbian Signed Test Criteria, although independent testing house validation is not required. All Express Signed applications may be audited to ensure that they meet these criteria. In order to participate in the Express Signed process, one needs a Symbian Signed account, a valid Publisher ID, and the purchase of one Content ID per application submission.

Certified Signed is the final tier intended for end-user release. Installation packages signed at this level have full access to User, System, and Restricted capabilities. Applications can petition for access to Manufacturer capabilities. Certified Signed applications must meet the test criteria, as confirmed through independent testing. At this level, one needs a Symbian Signed account, a valid Publisher ID, the purchase of a Content ID, as well as a paid independent validation.

Installation

Application packages can be installed onto SymbianOS devices through several vectors—namely MMS, Bluetooth, HTTP/S, IRdA, and USB tethering. No matter the route, all installation is handled through the SWInstall process. SWInstall acts as the gatekeeper for a SymbianOS device. It has sufficient capabilities to access the necessary file system paths. It validates all installation packages to ensure that they meet the signing requirements for the requested capabilities, that the binaries within the package have not been modified, and that the installer is not overwriting an existing application.

Validation occurs in several steps: To begin, SWInstall identifies the files to be installed (remembering that a single installation package can include binaries for multiple platforms). Then it enumerates the capabilities requested by the listed executable images (DLLs and EXEs). SWInstall proceeds to chain the signatures on the SIS file back to trusted SymbianOS code-signing CAs—generally Symbian Signed or a device manufacturer. Each CA may only certify particular capabilities. If the set of requested capabilities is not a subset of the certifiable capabilities, then one of two things occurs. If the requested capabilities are system level or higher, the installation will fail. If the requested capabilities are user level, the device owner will be asked whether installation should continue. SWInstall also ensures that the SID is unique on the phone, that installers with protected range SIDs are appropriately signed, and that a Vendor ID is only specified with signed applications.

After validation, SWInstall copies the contained resources to the required destinations. Binaries are placed in sysin, resources are written to esource, and the associated private directories are determined from an applications SID. The installer will also write files into the import directory of another executable (private<SID>import) to allow for delivering data to other applications. Installing into this import directory must happen at installation; most applications will not have the necessary capabilities to write to this directory.

When applications are installed to removable media, the SWInstall process will record the SHA-1 hash of the binaries to a private location on internal media. This prevents an individual from installing an application, removing the media, and modifying the binary to increase capabilities. Remember that capabilities are only checked against the signature at install time.

Permissions and User Controls

Symbian is a single-user operating system. This makes sense considering the use case of a mobile phone. A small embedded device that a single user carries with them on their person. Because there is only one user, permissions must be enforced elsewhere. There is no concept of a “root” user or an unprivileged user. Rather, each process has an immutable set of capabilities indicating what actions the process is allowed to perform.

Capabilities Overview

Twenty capabilities are defined within the Symbian platform. These are maintained within a 64-bit-wide field in the executable image header, thus allowing for future expansion. These capabilities can be divided into four categories: User, System, Restricted, and Manufacturer.

User capabilities are directly meaningful to the user of the mobile phone. A mobile phone user is expected to be able to make reasonable decisions regarding these capabilities. Users can grant untrusted applications (unsigned, self-signed, or not chained to a trust root) the ability to make use of these capabilities. These are listed in Table 7-5.

System capabilities may be of use to a wide variety of potential distributed applications. However, they are not directly meaningful to end users. What is the difference between SurroundingsDD and Location or UserEnvironment? As such, these capabilities cannot be granted by a user to untrusted applications. An installer must be signed through the Symbian Signed program. Any of the available signing options will suffice: Open Signed, Express Signed, or Certified Signed. Table 7-6 provides a listing of the System capabilities.

image

Table 7-5 User Capabilities

image

Table 7-6 System Capabilities

The Restricted capabilities found in Table 7-7 may still be of use to a wide variety of potential applications. Due to the potential disruptive impact to the operating environment were they to be abused, applications requiring these capabilities must undergo more scrutiny. As such, an installer cannot be Express Signed. Open Signed Offline is allowed because it is intended only for development purposes.

Manufacturer capabilities provide the ultimate degree of access to the mobile phone. For example, with the TCB capability a process could modify what capabilities another process is created with by adjusting them within the executable image. Very few applications require such privileges. Those that do (for example, whole disk encryption or data backup) must request permission from individual device manufacturers in addition to the scrutiny placed on applications that request Restricted capabilities. The three Manufacturer capabilities are listed in Table 7-8.

image

Table 7-7 Restricted Capabilities

image

Table 7-8 Manufacturer Capabilities

Executable Image Capabilities

The capabilities for an executable are stored within the image file header. This defines the capabilities that a successfully loaded process will possess. It is the responsibility of the software install process to validate that the capabilities an install package’s executables possess do not exceed those allowed based on the assigned trust level.

The capabilities for a dynamic library are also defined within the file header. However, they do not have a direct impact on the capabilities of the resultant process. They indicate what capabilities the library is allowed to execute with, not necessarily those that it will execute with. In order to be successfully loaded into a live process, the dynamic link library (DLL) must have a set of capabilities comprising a superset of the capabilities required by the executable image. As a result, most general-use shared libraries are defined with an almost complete set of capabilities—all except for manufacturer capabilities.

Process Capabilities

Capabilities are an immutable property of running processes on the Symbian platform—neither able to increase nor decrease privileges. When a process is created, the loader first reads the set of capabilities requested by the executable. It then validates that this is a subset of the capabilities each linked DLL is allowed to execute with, as can be seen in Figure 7-9. Otherwise, the process will fail to start with the message “Unable to execute file for security reasons.” This is performed for all dynamically linked libraries throughout the dependency tree.

image


Figure 7-9 Example of capability checking when a process loads

Capabilities Between Processes

Processes are the basic unit of trust on the Symbian platform. Threads executing within the same process will all have the same capabilities. As such, capabilities have no meaning within a single process, only across processes. Any API call that requires a particular capability in fact communicates with another process through the client/server mechanism mediated by the operating system kernel.

The capabilities, Secure ID, and Vendor ID of a client are provided to the server with each message to allow for appropriate security decision making. A simple server may require that a client have a certain capability to establish a session, one of intermediate complexity may require different capabilities for each function, and a complex server could use dynamic criteria to enforce an appropriate policy.

This provides a layered approach to defining and creating APIs, and explains why a process does not need the CommDD capability when accessing network functionality. For example, a client makes an API call to open a network socket. The ESOCK component validates that the client has the NetworkServices capability and calls the requisite device access functions on behalf of the client. The kernel validates that ESOCK has the CommDD capability and performs the appropriate action. Put another way, capabilities protect APIs and not resources.

When implementing a custom server component, the onus falls on the programmer to perform appropriate validation of connecting clients. This can include verifying the Vendor ID, Secure ID, and the set of capabilities. It is critical that custom components do not expose an unprivileged API that acts as a simple proxy for other privileged APIs.

Interprocess Communication

SymbianOS supports kernel-mediated communication between running processes. Thanks to the protected memory model, a process cannot directly modify the memory space of another running process. Interprocess communication is still a desirable activity—for example, dividing a single executable into two executables that communicate to provide a separation of concerns.

Client/Server Sessions

A client/server IPC model is used pervasively throughout SymbianOS. A number of the OS-provided services are implemented in two components: a server process and a client DLL that creates a session with the server. The server is an independent, long-running process with its own SecureID. The client DLL is loaded by client processes and maintains a mapping between function names and ordinal function numbers. Note that the client DLL is not strictly necessary, but the developer must interact with the server in the same fashion.

Part of implementing a client/server interface includes the ability to enforce a particular security policy as to which clients can connect to a server and which servers a client will connect to. A security policy is defined using one of fifteen _LIT_SECURITY_POLICY macros. These macros can be grouped in three categories: Capability enforcement, SecureID enforcement, and VendorID enforcement. A listing of these macros can be seen in Table 7-9. A server can and should validate the capabilities of clients that connect to it in order to prevent capability leakage where a privileged server performs sensitive actions on behalf of an unprivileged client. A server can also enforce that only clients with a particular SecureID can connect to it. This can be useful when implementing an application model where sensitive actions are performed in a small second process. Finally, a server can enforce that only clients from a particular vendor can connect to it when the vendor factors out sensitive actions into a common process that each of its applications should be able to access.

Each of these macros is used in the same general way. The first parameter specifies the name of a new policy object. For SecureID macros, the second parameter specifies the targeted SecureID. For VendorID macros, the second parameter specifies the targeted VendorID. The rest of the parameters are one of the enumerated capabilities, the number of which is specified in the macro name.

_LIT_SECURITY_POLICY_S0(KCustomServerSID, 0xE0000001);
_LIT_SECURITY_POLICY_V1(KClientVIDOneCap, 0xE0000001, ECapabilityDiskAdmin);
_LIT_SECURITY_POLICY_C2(KEnforceTwoCaps, ECapabilityReadUserData, ECapabilityWriteUserData);

image


Table 7-9 Security Policy Macros

Client sessions are created via an RSessionBase object and a call to the CreateSession() method. Most client DLLs will derive their own subclass that calls this method with the appropriate parameters. During the 9.x series, another overload of CreateSession() was added that takes a pointer to a TSecurityPolicy object. This allows a client to validate the SecureID or VendorID of the named server. Messages to the server are delivered and responses obtained through the SendReceive() method. Most client DLLs will provide wrapper functions for calls to SendReceive() to provide a more natural interface. The code below shows the basic pattern behind writing a client proxy object. This class will reside within a client DLL and hide the interprocess communication details.

_LIT(KCustomServerName, “com_isecpartners_custom”);

enum TCustomServerMessages {
    EDoStuff
};

class RCustomSession : public RSessionBase {
    public:
        IMPORT_C TInt Connect();
        IMPORT_C TInt DoStuff(const LString& str);
};

EXPORT_C TInt RCustomSession::Connect() {
    return CreateSession(KCustomServerName,
                         TVersion(),
                         KServerDefaultMessageSlots,
                         EIpcSession_Unsharable,
                         &KCustomServerSID());
}

EXPORT_C TInt RCustomSession::DoStuff(const LString& str) {
    return SendReceive(EDoStuff, TIpcArgs(&str));
}

Servers are created by deriving two of three classes: CSession2 and either CServer2 or CPolicyServer. CServer2 works well in many cases—namely, where the security policy to be enforced is straightforward (for example, restricting potential clients to those with a particular SecureID upon session connection or requiring a particular capability to call a certain method). In order to enforce a policy with a CSession2 or CServer2 class, call the CheckPolicy() method of a SecurityPolicy object with RMessage& as the first parameter. The result of this method is a boolean indicating whether the message was delivered by a process that conforms to the policy. Wrapping calls to this method in an if statement allows for corrective action to be taken. In the following example, the corrective action is to “leave” with a permission-denied error:

class CCustomSession : public CSession2 {
    private:
        void ServiceL(const RMessage2& msg);
        TInt doStuff(const LString& str);
        TInt getStuff (LString& str);
};

void CCustomSession::ServiceL(const RMessage2& msg) {
    TInt status = KErrNotSupported;
    switch (aMessage.Function()) {
        case EDoStuff: {
            LString param(msg.GetDesLengthL(0));
            msg.ReadL(0, param);
            status = doStuff(param);
            break;
        }
        case EGetStuff :{
            if(!KEnforceTwoCaps().CheckPolicy (msg,
                   __PLATSEC_DIAGNOSTIC_STRING(“CCustomSession::ServiceL”))) {
                 User::Leave(KErrPermissionDenied);
            }

            LString result;
            status = getStuff(result);

            __ASSERT_ALWAYS(result.Length() <= msg.GetDesMaxLengthL(0),
                             User::Leave(KErrBadDescriptor));
            msg.WriteL(0, result);
            break;
        }
        default: {
            _LIT(KErrMsg, “Unknown function call!”);
            msg.Panic(KErrMsg, KErrNotSupported);
        }
    }
    aMessage.Complete(status);
}


class CCustomServer : public CServer2 {
    public:
        CCustomServer(TInt priority = EPriorityNormal);

    private:
        CSession2* NewSessionL(const TVersion& version,
                               const RMessage2& msg) const;
};

CCustomServer::CCustomServer(TInt priority) : CServer2(priority,
                                                       ESharableSessions) {
    Start(KCustomServerName);
}
CSession2* CCustomServer::NewSessionL(const TVersion&,
                                      const RMessage2&) const {
    if(!KClientVIDOneCap().CheckPolicy(msg,
                __PLATSEC_DIAGNOSTIC_STRING(“CCustomServer::NewSessionL”))) {
        User::Leave(KErrPermissionDenied);
    }
    return new (ELeaveCCustomSession();
}

CPolicyServer should be chosen when the policy to be enforced is very complex. Although substantially more complex in simple cases, it can be much simpler in complex cases. The framework automatically handles checking a prospective client’s policy conformance upon session initiation and for each message. The first step is to create an array of message numbers in sorted increasing order. Each number need not be represented, just the lower bound of a range that shares the same policy. That is, if functions 0 through 3 share a policy and function 4 has its own policy, then the array should have two elements: 0 and 4. Next, a second array is created that must be the same size as the previous one to contain indices into a third array. This third array contains the separate policy-enforcement objects. When a message is sent to the server, its function number (or the closest number less than it) is found in the first array and the index is noted. This index is used to reference into the second array in order to obtain the index into the third array, where the actual policy object is found. Conceptually this can be imagined as a dictionary that maps a function ordinal to the policy to be applied, where the first array holds the dictionary key and the second array holds a reference to the policy. The code below demonstrates the basics behind using the CPolicyServer class to reduce the developer effort required to enforce complex security policies.

const TUInt rangesCount = 3;

const TInt msgNumRanges[rangesCount] = {
    0, // EDoStuff
    1, // EGetStuff
    2 // Non-existent functions
};

const TUInt8 msgPolicyIndices[] = {
    CPolicyServer::EAlwaysPass,         // EDoStuff
    1,                                  // EGetStuff
    CPolicyServer::EBadMessageNumber    // Non-existent functions
};
const CPolicyServer::TPolicyElement msgPolicies[] = {
    {
        _INIT_SECURITY_POLICY_V1(0xE0000001,  ECapabilityDiskAdmin),
        CPolicyServer::EFailClient
},
    {
        _INIT_SECURITY_POLICY_C2(ECapabilityReadUserData,
                                 ECapabilityWriteUserData),
         CPolicyServer::EFailClient
}
};

const CPolicyServer::TPolicy customPolicy = {
    0,
    rangesCount,
    msgNumberRanges,
    msgPolicyIndices,
    msgPolicies
};

class CCustomPolicyServer : public CPolicyServer {
    public:
        CCustomPolicyServer(TInt priority = EPriorityNormal);

    private:
        CSession2* NewSessionL(const TVersion& version,
                               const RMessage2& msg) const;
};

CCustomPolicyServer::CCustomServer(TInt priority) :
    CPolicyServer(priority, CustomPolicy, ESharableSessions) {
    Start(KCustomServerName);
}

CSession2* CCustomPolicyServer::NewSessionL(const TVersion&,
                                            const RMessage2&) const {
    return new (ELeaveCCustomSession();
}

Shared Sessions

Sessions can be created as unsharable, sharable between threads within the same process, or sharable between processes. This is defined on both the client and server sides. When CreateSession() is called on the client, one of the parameters specifies the desired session sharing. The CServer2() constructor specifies the type of sessions that can be created.

Use caution when allowing globally sharable sessions and implementing a policy where clients are checked at session initialization. A client that does not meet the policy could obtain a session handle from a process that did. This is especially true when implementing subsessions. Subsessions maintain a reference to the parent session (which maintains a list of open subsessions). When sharing a subsession, be aware that all of the other active subsessions are also exposed.

Shared Handles

Another form of IPC available to SymbianOS processes involves sharing handles to kernel-side objects. A number of these objects share a common interface because they derive from the same base class: RHandleBase. Examples of these objects include RChunk, RSemaphore, RMutex, and RSessionBase.

RChunk references a chunk of memory and is useful for transferring large amounts of data between processes. In previous versions of SymbianOS, this could not be used securely. An RChunk object could either be created locally, accessible only to the current process, or created as a named global object, accessible to any process that knew the name. With the introduction of 9.x, anonymous global chunks were introduced. These objects were accessible between processes, but the creating process had to pass the handle to the consuming process through the client/server interface. This greatly limited the possibility of unintentionally leaking data through a global handle; however, one still needs to take care because a shared handle can be shared further to other processes.

This same interface is exposed for the other RHandleBase-derived classes. They can be created locally, named globally, or anonymous globally. This allows a process to carefully control access to these kernel-side handles. In fact, it is this interface exposed by the RSessionBase that allows for separate processes to share file handles. A session handle to the file server is shared between two processes.

Persistent Data Storage

SymbianOS supports several methods for maintaining persistent data storage, residing on both fixed internal and removable storage devices. At the bottom resides a traditional file system model, accessed through the file server process. Built on top of this resides an optional DBMS layer than can operate in two modes—integrated within a process as a library, accessing data stored within the process’s storage space, or as a server process that can store and police access within a separate storage location.

File Storage

Writable storage devices used with Symbian devices are formatted with the VFAT file system—a very simple format that is widely supported throughout the computer industry. In fact, this near universal support is one of the key reasons for its selection. Removable storage had to be capable of being read by other devices, such as general-purpose computers.

Unfortunately, VFAT does not support several features that assist with appropriate file access controls, recording neither the creator/owner nor access permissions. Instead, these are enforced by the file server process through which file access operations are performed. The file server dynamically evaluates whether a process is allowed to access a particular file based on the file path, the file operation, and four simple rules. Access to the sys directory is restricted to processes that contain the AllFiles or TCB capabilities. Write access to esource is restricted to processes that have TCB. Directories under private are restricted based on the SecureID of the requesting process. If the directory name is the process SecureID, no capabilities are required; otherwise, AllFiles is required. Finally, all other files have no access restrictions. These rules are summarized in Table 7-10. These rules apply equally across all mounted file systems—Z: (ROM), C: (internal), and so on.

Data Caging

Any data that generally should not be accessible on the device should be placed within the executable’s private data storage. Note that two process instances loaded from the same executable will share the same private directory because it is keyed off the SecureID. The file server API provides several methods for easily accessing the private directory for a given executable, including CreatePrivatePath(TInt TargetDrive) and SetSessionToPrivate(TInt TargetDrive).

image

Table 7-10 Directory Access Restrictions

File Handle Sharing

It may be desirable to provide limited access to files stored within a private directory. The file server provides support for sharing open file handles between processes, similar in function to other platforms. The sharing process will create a session with the file server and mark the session as sharable by calling RFs::ShareProtected(). It will then open the files to be shared; access controls are performed once at this time. It will then call RFile::TransferToClient() on the target file. The client will than call RFile::AdoptFromServer() on the received file handle. The client now has access to the file in whatever mode it was opened with. There are three separate transfer and adopt functions, depending on the relationship between sharing processes. Files can be shared from a server to a client, from a client to a server, and from a parent to a spawned child.

In actuality, it is the file server session that is shared with the target process. RFile objects are actually subsessions associated with a specific session to the file server and maintain an internal reference to the session. One important consequence of this is that any open files within a file server session are accessible to a client that a file has been shared with. Whenever a file is to be shared with a target process, a new file server session should be established and only the target files should be opened. This prevents a malicious executable from viewing or modifying other open files.

Structured Storage

In addition to private file storage within an application’s private directory, there is also a system-wide service for providing SQL database storage. Access to this service is handled through the RSqlDatabase class, similar to how file access is mediated through the RFs class. Databases can be created in two ways: by specifying a filename and by specifying a database name and a security policy. When a new database is created via filename, any other application that can access that file location can read the database. When a new database is created via a specified name and policy, only applications that conform to the policy can access the database (that is, it is shared in a secure fashion). In this case, the database is stored within the SQL database private directory. The format of the name for a shared secure database is <drive>:[<SID>]dbname. To create a database, the SID must match that of the current process.

Use the RSqlSecurityPolicy class to define the policy for access to the secure shared database upon creation. Once the policy is set, it cannot be changed for the lifetime of the database. A separate policy can be set for the database schema, global database read, global database write, table read, and table write. This means you can create a database where some tables can be read by any process, but others are restricted based on capabilities, SecureID, or VendorID.

When you’re using the RSqlDatabase object, the same concern about mixing data and statements applies as would apply to any SQL database. Namely, be aware of SQL injection when integrating untrusted input into database queries. Use prepared statements, rather than raw string concatenation, in order to safely make database queries. This is done by calling RSqlStatment::Prepare with a descriptor containing the parameterized query. Parameters are identified with a colon (:) prepended to them. Then the appropriate Bind method is called for the type of data that is to replace the parameter.

Encrypted Storage

SymbianOS does not provide a standard encrypted storage feature. In addition, the cryptography APIs must be downloaded and installed separately from the Symbian ^ 1 SDK. This support is changing at a rapid pace and may be offered within the base SDK in a future release. Unfortunately, such support may provide a wholly different API from what’s described here.

These APIs allow a developer to access the same cryptographic algorithms that are already installed on the device to support, such as certificate validation and secure communications protocols. They provide a wide-ranging set of cryptographic-related functionality, including key exchange, asymmetric and symmetric encryption, integrity through digital signatures and HMACs, and message digests.

Data that should be kept private regardless of where it is stored should therefore be explicitly encrypted. This is readily apparent for files placed on removable storage, but it is also important for files stored on internal media. A determined attacker could disassemble the device to gain access to such files. Performing encryption correctly and securely can be a confusing proposition. The Symbian cryptographic API attempts to mitigate this by presenting a nested and chained structure.

Proper encryption requires the use of sufficiently random numbers derived from a pool of entropy. Otherwise, an attacker who knows the approximate random seed (for example, timestamp) could guess generated keys with relative ease. SymbianOS provides the CSystemRandom class in order to obtain secure random numbers. After instantiating an instance of the class, you should provide a data descriptor of the desired length to the GenerateBytesL method to obtain the required random values. Here’s an example:

LData AESKey128, HMACKey, IV;
TRandom rng;

AESKey128.SetLengthL(16);
HMACKey.SetLengthL(20);
IV.SetLengthL(16);

rng.RandomL(AESKey128);
rng.RandomL(HMACKey);
rng.RandomL(IV);

Once a random key has been generated, it can be used to perform encryption on a binary blob of data. A symmetric block encryption algorithm only defines a block transformation. Given a key and a block, an apparently random block is returned, although the same block and the same key will always return the result. A complete system also includes padding, an initialization vector, and a method of chaining. This is done to obscure any structure between blocks from analysis. The SymbianOS cryptographic libraries provide AES and 3DES, PKCS #7 padding, and CBC mode chaining, as demonstrated here:

LCleanedupPtr< CBufferedEncryptor > encryptor(CBufferedEncryptor::NewL(
    CModeCBCEncryptor::NewL(CAESEncryptor::NewL(AESKey128), IV),
    CPaddingPKCS7::NewL(16)));

ctxt.ReserveFreeCapacityL(encryptor->MaxFinalOutputLength(ptxt.Size()));
encryptor->ProcessFinalL(ptxtctxt);

It is often assumed that encrypted data will decrypt to garbage data when tampered with, but this is not always the case. An encrypted blob should be integrity-protected in order to detect when tampering has occurred. Integrity protection can be easily provided with an HMAC, which takes a new key and a message digest algorithm. Be sure to include the initialization vector when calculating the HMAC verifier! This can be seen in the brief snippet shown next.

LCleanedupPtr< CMessageDigest >
hmac(CMessageDigestFactory::NewHMACL(CMessageDigest::ESHA1, HMACKey));
LData verifier;

hmac->Update(IV);
hmac->Update(ctxt);
verifier = hmac->Final();

In order to recover encrypted data or validate a verifier, the associated key needs to be maintained. The data-caging mechanism previously described generally provides sufficient protection, as long as keys are stored solely on internal storage. For more sensitive requirements, consider encapsulating keys by encrypting them with another key derived from a user-supplied password. In this fashion, sensitive data can be stored with a strong random key, and the encryption key (a much smaller piece of data) can be protected with a password-derived key. Be sure to generate an HMAC as well. This is demonstrated in the brief code snippet shown next:

TPBPassword password(_L(“password”));
LData derivedKey, derivedKeySalt;
TRandom rng;

derivedKey.SetLengthL(16);
derivedKeySalt.SetLengthL(8);

rng.RandomL(DerivedKeySalt);

TPKCS5KDF::DeriveKeyL(derivedKey, password.Password(),
derivedKeySalt, KDefaultIterations);

The cryptographic libraries also include support for asymmetric operations—encrypting, decrypting, signing, and verification. The RSA implementation can be used for all four operations, whereas the included DSA implementation can only be used for signing and verification. Performing each of these actions can be seen in the following code:

LCleanedupPtr< CRSAKeyPair > kp(CRSAKeyPair::NewL(2048));
LCleanedupPtr< CRSASignature > signature;

LCleanedupPtr< CRSAPKCS1v15Encryptor > RSAencryptor(
    CRSAPKCS1v15Encryptor::NewL(kp->PublicKey()));
RSAencryptor->EncryptL(pKey, cKey);

LCleanedupPtr< CRSAPKCS1v15Decryptor > RSAdecryptor(
    CRSAPKCS1v15Decryptor::NewL(kp->PrivateKey()));
RSAdecryptor->DecryptL(cKey, pKey);

LCleanedupPtr< CRSAPKCS1v15Signer > RSAsigner(
    CRSAPKCS1v15Signer::NewL(kp->PrivateKey()));
signature = RSAsigner->SignL(sha256hash);

LCleanedupPtr< CRSAPKCS1v15Verifier > RSAverifier(
    CRSAPKCS1v15Verifier::NewL(kp->PublicKey()));
RSAverifier->VerifyL(sha256hash, *signature);

Conclusion

Secure development for the SymbianOS platform does not need to be painful or confusing. Despite providing a native development environment, with all of the associated pitfalls, the included libraries reinforce secure choices and practices. The following guidelines also encourage the use of secure practices.

image    Use the LString and LData descriptors. Rather than use unsafe C-style strings, use the descriptor framework to prevent memory corruption flaws from string handling.

image    Use the RArray or RPointerArray class. Do not use C-style arrays, another potential source for buffer overflows and other memory corruption flaws.

image    Check arithmetic operations including untrusted data for integer overflow. Use the next larger integer type to hold the result from integer operations in order to check that it is able to be represented in the target type.

image    Wrap all local variables with the appropriate LCleanedup class. This will ensure that objects are appropriately deleted and resources released when they go out of scope. This includes when a function leaves.

image    Wrap all class variables with the appropriate LManaged class. This ensures that objects are correctly managed when they go out of scope. Be sure to use the LManaged classes for class variables to prevent out-of-order cleanup if an exception is thrown.

image    Only use OpenC (P.I.P.S.) for ported code. These runtimes rely on C-style strings and arrays. They also support known dangerous functions such as strcpy() and strcat().

image    Review the capabilities that your application is requesting. Do not request unnecessary capabilities to perform the tasks at hand. If an attacker were to provide arbitrary code, it would be limited as to what it could do.

image    Sign all installation packages meant for general release. Not only is the user experience more straightforward, but signed packages can possess a VendorID. This allows for VendorID-specific policy enforcement.

image    Consider separating tasks requiring elevated capabilities into a server process. Develop a client/server model where the client process receives and processes untrusted data and the server process performs very specific and limited tasks as requested. An arbitrary code execution vulnerability in the client is unable to directly take advantage of the capabilities maintained by the server.

image    Enforce conformance to an appropriate security policy for clients. Enforce a security policy for connecting clients that prevents capability leakage.

image    Mark kernel handles (including sessions) as sharable only when necessary.

image    Close private subsessions before sharing a session. Subsessions (for example, RFile handles) maintain a reference to their parent session. A shared subsession can access any other active subsession of the same parent.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.36.174