Difference between revisions of "Symbian OS Platform Security/05. How to Write Secure Servers"

From Franklin Heath Ltd Wiki
Jump to: navigation, search
m
m (removed category, added copyright template)
 
(22 intermediate revisions by 5 users not shown)
Line 1: Line 1:
Reproduced by kind permission of John Wiley & Sons.
+
{| width="100%"
 +
|-
 +
|'''by Jonathan Dixon'''||align="center"|Reproduced by kind permission of John Wiley & Sons.||align="right"|'''[[Symbian OS Platform Security/04. How to Write Secure Applications|Prev.]]   [[Symbian OS Platform Security|Contents]]   [[Symbian OS Platform Security/06. How to Write Secure Plug-ins|Next]]'''
 +
|}
  
 
=What Is a Secure Server?=
 
=What Is a Secure Server?=
Line 23: Line 26:
 
a robust inter-process communication (IPC) mechanism modeled around
 
a robust inter-process communication (IPC) mechanism modeled around
 
a client–server architecture.
 
a client–server architecture.
 +
 
The primary purpose of a server is to mediate and arbitrate for multiple
 
The primary purpose of a server is to mediate and arbitrate for multiple
 
clients who share access to a resource. The server code executes within
 
clients who share access to a resource. The server code executes within
Line 38: Line 42:
 
client and server, and so must be mutually and universally trusted by
 
client and server, and so must be mutually and universally trusted by
 
these processes to carry out this role in the secure environment.
 
these processes to carry out this role in the secure environment.
 +
 
There are, however, other places where the process and trust boundaries
 
There are, however, other places where the process and trust boundaries
 
can be crossed. Examples range from the use of shared memory
 
can be crossed. Examples range from the use of shared memory
Line 57: Line 62:
  
 
Writing Symbian OS servers was once considered something of a
 
Writing Symbian OS servers was once considered something of a
‘black art’, however, recently several books, such as Symbian OS C++
+
‘black art’, however, recently several books, such as ''Symbian OS C++ for Mobile Phones'' [Harrison 2003]
forMobile Phones [Harrison 2003] and Symbian OS Explained [Stichbury
+
and ''Symbian OS Explained'' [Stichbury
 
2005], have gone a long way to demystify this process. Rather than repeat
 
2005], have gone a long way to demystify this process. Rather than repeat
 
what is covered in those volumes we will build upon them, highlighting
 
what is covered in those volumes we will build upon them, highlighting
Line 76: Line 81:
 
acts as a proxy to accessing it on behalf of each hungry pupil standing in
 
acts as a proxy to accessing it on behalf of each hungry pupil standing in
 
line!
 
line!
 +
 
To keep life simple for server developers, whilst also being frugal with
 
To keep life simple for server developers, whilst also being frugal with
 
system resources, servers are typically implemented within a single thread
 
system resources, servers are typically implemented within a single thread
Line 93: Line 99:
 
of a client-side DLL:
 
of a client-side DLL:
  
class RSimpleServer : public RSessionBase
+
<code>
{
+
class RSimpleServer : public RSessionBase
public:
+
  {
IMPORT_C TInt Connect();
+
public:
IMPORT_C TInt GetInformation() const;
+
  IMPORT_C TInt Connect();
};
+
  IMPORT_C TInt GetInformation() const;
_LIT(KSimpleServerName, "com_symbian_press_testserver1");
+
  };
static _LIT_SECURITY_POLICY_S0(KSimpleServerPolicy,
+
0xE1234567); // a test UID used as the server’s SID
+
_LIT(KSimpleServerName, "com_symbian_press_testserver1");
EXPORT_C TInt RSimpleServer::Connect()
+
        static _LIT_SECURITY_POLICY_S0(KSimpleServerPolicy,
{
+
        0xE1234567); // a test UID used as the server’s SID
return CreateSession(KSimpleServerName, TVersion(), -1,
+
EIpcSession_Unsharable, &KSimpleServerPolicy());
+
EXPORT_C TInt RSimpleServer::Connect()
}
+
  {
EXPORT_C TInt RSimpleServer::GetUserInformation(TInt aInfoRequired,
+
  return CreateSession(KSimpleServerName, TVersion(), -1,
TDes8& aResult) const
+
          EIpcSession_Unsharable, &KSimpleServerPolicy());  
{
+
  }
return SendReceive(ESimpleServFnGetUserInfo, TIpcArgs(aRequest,
+
&aResult));
+
EXPORT_C TInt RSimpleServer::GetUserInformation(TInt aInfoRequired,
}
+
                                                TDes8& aResult) const
 +
  {
 +
  return SendReceive(ESimpleServFnGetUserInfo, TIpcArgs(aRequest,
 +
                                                        &aResult));
 +
  }</code>
 +
 
 
If you are familiar with client–server development for previous versions
 
If you are familiar with client–server development for previous versions
 
of Symbian OS, there should be little surprise here.
 
of Symbian OS, there should be little surprise here.
 +
 
The base class for the client side of a client–server connection is
 
The base class for the client side of a client–server connection is
RSessionBase; creating a connection to a server is achieved through
+
{{Icode|RSessionBase}}; creating a connection to a server is achieved through
the call RSessionBase::CreateSession(). The version used here
+
the call {{Icode|RSessionBase::CreateSession()}}. The version used here
 
is the new overload added in Symbian OS 9.x, and is of the form:
 
is the new overload added in Symbian OS 9.x, and is of the form:
IMPORT_C TInt CreateSession(const TDesC& aServer, const TVersion& aVersion,
+
 
const TInt aAsyncMessageSlots, TIpcSessionType aType,
+
<code>
const TSecurityPolicy* aPolicy=0, TRequestStatus* aStatus=0);
+
IMPORT_C TInt CreateSession(const TDesC& aServer, const TVersion& aVersion,
 +
                      const TInt aAsyncMessageSlots, TIpcSessionType aType,
 +
              const TSecurityPolicy* aPolicy=0, TRequestStatus* aStatus=0);</code>
 +
 
 
The parameter we are most interested in is the pointer to an object of type
 
The parameter we are most interested in is the pointer to an object of type
TSecurityPolicy. This allows the client code to stipulate criteria for
+
{{Icode|TSecurityPolicy}}. This allows the client code to stipulate criteria for
 
the server to which it will connect. In the example, we state that the server
 
the server to which it will connect. In the example, we state that the server
must be running in a process with a SID of 0xE1234567 (a test UID, see
+
must be running in a process with a SID of {{Icode|0xE1234567}} (a test UID, see
 
Chapter 3 for more information), to guard against server spoofing. You
 
Chapter 3 for more information), to guard against server spoofing. You
 
will see other uses of these policy objects throughout this book. Other
 
will see other uses of these policy objects throughout this book. Other
 
than this, establishing the session is essentially the same as before.
 
than this, establishing the session is essentially the same as before.
 +
 
Having established the session, requests can be sent to the server using
 
Having established the session, requests can be sent to the server using
RSessionBase::SendReceive(). In the example the GetUserInformation
+
{{Icode|RSessionBase::SendReceive()}}. In the example the {{Icode|GetUserInformation}}
 
method does exactly this. This has not changed significantly,
 
method does exactly this. This has not changed significantly,
 
although small improvements have been made to allow more robust marshalling
 
although small improvements have been made to allow more robust marshalling
of arguments. Specifically, you can see the TIpcArgs class
+
of arguments. Specifically, you can see the {{Icode|TIpcArgs}} class
 
being used to provide a typed container for the arguments to the message;
 
being used to provide a typed container for the arguments to the message;
TIpcArgs carries flags to allow the kernel to differentiate the
+
{{Icode|TIpcArgs}} carries flags to allow the kernel to differentiate the
 
various argument types, and thereby ensure that the server respects them
 
various argument types, and thereby ensure that the server respects them
correctly. This mechanism is described in more detail in Symbian OS
+
correctly. This mechanism is described in more detail in ''Symbian OS Internals'' [Sales 2005].
Internals [Sales 2005].
+
  
 
Now let’s see how this message is handled on the server side:
 
Now let’s see how this message is handled on the server side:
  
 +
<code>
 
simpleserver.mmp:
 
simpleserver.mmp:
TARGET simpleserver.exe
+
TARGET simpleserver.exe
TARGETTYPE exe
+
TARGETTYPE exe
UID 0 0xE1234567 // test UID as UID3 / SID
+
UID 0 0xE1234567   // test UID as UID3 / SID
SOURCE simpleserver.cpp
+
SOURCE simpleserver.cpp</code>
 +
 
 +
<code>
 
simpleserver.cpp:
 
simpleserver.cpp:
class CSimpleServer : public CServer2
+
class CSimpleServer : public CServer2
{
+
  {
protected:
+
protected:
CSession2* NewSessionL(const TVersion& aVersion,
+
  CSession2* NewSessionL(const TVersion& aVersion,
const RMessage2& aMessage);
+
                        const RMessage2& aMessage);
//. . .
+
  //...
};
+
  };
static _LIT_SECURITY_POLICY_C1(KSimpleServerConnectPolicy,
+
ECapabilityReadUserData);
+
static _LIT_SECURITY_POLICY_C1(KSimpleServerConnectPolicy,
CSession2* CSimpleServer::NewSessionL(const TVersion&
+
                                  ECapabilityReadUserData);
aVersion,const RMessage2& aMessage)
+
{
+
CSession2* CSimpleServer::NewSessionL(const TVersion&
if(!KSimpleServerConnectPolicy().CheckPolicy(aMessage,
+
                    aVersion,const RMessage2& aMessage)
__PLATSEC_DIAGNOSTIC(“CSimpleServer::NewSessionL
+
  {
KSimpleServerConnectPolicy”))
+
  if(!KSimpleServerConnectPolicy().CheckPolicy(aMessage,
User::Leave(KErrPermissionDenied);
+
        __PLATSEC_DIAGNOSTIC(“CSimpleServer::NewSessionL
// proceed with handling the connect request
+
                            KSimpleServerConnectPolicy”))
}
+
    User::Leave(KErrPermissionDenied);
 +
 +
  // proceed with handling the connect request
 +
  }</code>
  
 
Here you see a server-side implementation of a security policy test. A
 
Here you see a server-side implementation of a security policy test. A
Line 171: Line 192:
 
sessions with the server. After a connect message has been processed by
 
sessions with the server. After a connect message has been processed by
 
the kernel, it reappears in user space in the server process, and is handled
 
the kernel, it reappears in user space in the server process, and is handled
by the framework code – specifically the CServer2 base class. This then
+
by the framework code – specifically the {{Icode|CServer2}} base class. This then
calls the NewSessionL() method that is implemented by the derived
+
calls the {{Icode|NewSessionL()}} method that is implemented by the derived
concrete class, in our example, CSimpleServer. Here we perform this
+
concrete class, in our example, {{Icode|CSimpleServer}}. Here we perform this
 
simple policy check: a security policy object is defined that requires the
 
simple policy check: a security policy object is defined that requires the
client to hold the ReadUserData capability. The connect message, as
+
client to hold the {{Icode|ReadUserData}} capability. The connect message, as
indicated by the RMessage2 parameter to NewSessionL(), is tested
+
indicated by the {{Icode|RMessage2}} parameter to {{Icode|NewSessionL()}}, is tested
 
against this policy, and if it fails, the method throws a leave exception,
 
against this policy, and if it fails, the method throws a leave exception,
with the new error code KErrPermissionDenied.
+
with the new error code {{Icode|KErrPermissionDenied}}.
  
 
If the server has a more complex security policy, it may not be
 
If the server has a more complex security policy, it may not be
Line 184: Line 205:
 
established, but instead individual IPC operations might be restricted.
 
established, but instead individual IPC operations might be restricted.
  
class CSimpleSession : public CServer2
+
<code>
{
+
class CSimpleSession : public CServer2
protected:
+
  {
void ServiceL(const RMessage2 &aMessage);
+
protected:
void GetUserInfoL(TInt aInfoRequired, TDes8& aResult);
+
  void ServiceL(const RMessage2 &aMessage);
//. . .
+
  void GetUserInfoL(TInt aInfoRequired, TDes8& aResult);
};
+
static _LIT_SECURITY_POLICY_C1(KSimpleServerUserInfoPolicy,
+
  //...
ECapabilityReadUserData);
+
  };
CSession2* CSimpleSession::ServiceL (const RMessage2& aMessage)
+
{
+
static _LIT_SECURITY_POLICY_C1(KSimpleServerUserInfoPolicy,
switch(aMessage.Function())
+
                                  ECapabilityReadUserData);
{
+
case ESimpleServFn1:
+
CSession2* CSimpleSession::ServiceL (const RMessage2& aMessage)
// . . . handle function
+
  {
break;
+
  switch(aMessage.Function())
case ESimpleServFnGetUserInfo:
+
    {
if(!KSimpleServerUserInfoPolicy().CheckPolicy(aMessage,
+
  case ESimpleServFn1:
__PLATSEC_DIAGNOSTIC(“CSimpleSession::ServiceL
+
    // ... handle function
KSimpleServerUserInfoPolicy”))
+
    break;
User::Leave(KErrPermissionDenied);
+
// Process the request as normal
+
  case ESimpleServFnGetUserInfo:
RBuf8 result;
+
    if(!KSimpleServerUserInfoPolicy().CheckPolicy(aMessage,
result.CreateLC(aMessage.GetDesLengthL(1));
+
              __PLATSEC_DIAGNOSTIC(“CSimpleSession::ServiceL
result.CleanupClosePushL();
+
                              KSimpleServerUserInfoPolicy”))
GetUserInfoL(aMessage.Int0(), result);
+
      User::Leave(KErrPermissionDenied);
aMessage.WriteL(1, result);
+
    // Process the request as normal
CleanupStack::PopAndDestroy();
+
    RBuf8 result;
break;
+
    result.CreateLC(aMessage.GetDesLengthL(1));
default:
+
    result.CleanupClosePushL();
User::Leave(KErrNotSupported);
+
    GetUserInfoL(aMessage.Int0(), result);
}
+
    aMessage.WriteL(1, result);
aMessage.Complete(KErrNone);
+
    CleanupStack::PopAndDestroy();
}
+
    break;
 +
 +
  default:
 +
    User::Leave(KErrNotSupported);
 +
    }
 +
  aMessage.Complete(KErrNone);
 +
  }</code>
  
 
Here we see that the server session code performs a very similar
 
Here we see that the server session code performs a very similar
 
security policy check to the last example, but this time it is conditional
 
security policy check to the last example, but this time it is conditional
on the value of aMessage.Function(). Here we are just
+
on the value of {{Icode|aMessage.Function()}}. Here we are just
picking out IPC messages that have a function value of ESimple-
+
picking out IPC messages that have a function value of {{Icode|ESimpleServFnGetUserInfo}}, which corresponds to the call to {{Icode|RSimpleServer::GetUserInformation()}} in the client library that we started
ServFnGetUserInfo, which corresponds to the call to RSimple-
+
Server::GetUserInformation()in the client library that we started
+
 
off with. We could also take this down to the next level, the security
 
off with. We could also take this down to the next level, the security
policy might depend on the value of aInfoRequired that was passed
+
policy might depend on the value of {{Icode|aInfoRequired}} that was passed
into the client call. This is represented on the server side in the aMessage.
+
into the client call. This is represented on the server side in the {{Icode|aMessage.Int0()}} message parameter. An additional level of switch would
Int0() message parameter. An additional level of switch would
+
 
be required in order to achieve this.
 
be required in order to achieve this.
  
 
From this quite simple example, we can pick out some important
 
From this quite simple example, we can pick out some important
 
points which we will explore further in the remainder of this chapter:
 
points which we will explore further in the remainder of this chapter:
Both the client and the server can make use of the platform security
+
*Both the client and the server can make use of the platform security architecture to protect their IPC boundaries.
architecture to protect their IPC boundaries.
+
*The client will typically have a simple security policy, and the changes to client-side code are minimal.
The client will typically have a simple security policy, and the changes
+
*The server can have as simple or as complex a policy as its IPC protocol demands, thus the changes to the server code may be simple or complex.
to client-side code are minimal.
+
*A complex server security policy could result in a great deal of repetitive security check code in a standard form. {{Icode|CPolicyServer}}, described later, is a framework provided to help manage this complexity and minimize copy and paste bugs.
The server can have as simple or as complex a policy as its IPC
+
*Security policies are tested at the process boundary: the server does not rely on code in its client library to protect its interface.
protocol demands, thus the changes to the server code may be simple
+
*At any point where a trust boundary may be crossed, a check may be required.
or complex.
+
A complex server security policy could result in a great deal of
+
repetitive security check code in a standard form. CPolicyServer,
+
described in Sections 5.3.2 and 5.4.2, is a framework provided to help
+
manage this complexity and minimize copy and paste bugs.
+
Security policies are tested at the process boundary: the server does
+
not rely on code in its client library to protect its interface.
+
At any point where a trust boundary may be crossed, a check may be
+
required.
+
  
 
=Server Threat Modeling=
 
=Server Threat Modeling=
Line 281: Line 296:
  
 
To summarize, the assets for a typical server will consist of:
 
To summarize, the assets for a typical server will consist of:
the access to any ‘physical’ resources it owns (including its memory,
+
*the access to any ‘physical’ resources it owns (including its memory, data-caged files, and even its server name)
data-caged files, and even its server name)
+
*any services it provides derived from or built on top of these resources
any services it provides derived from or built on top of these resources
+
*the client sessions, which are assets to be protected from any other sessions.
the client sessions, which are assets to be protected from any other
+
 
sessions.
+
 
Note that the order here is intended only to aid logical and comprehensive
 
Note that the order here is intended only to aid logical and comprehensive
 
analysis; it is not intended to indicate any prioritization of risk levels.
 
analysis; it is not intended to indicate any prioritization of risk levels.
Line 294: Line 308:
 
access to higher layer (application) clients. The most obvious interfaces a
 
access to higher layer (application) clients. The most obvious interfaces a
 
server exposes are:
 
server exposes are:
1. The IPC interface it opens to its client, via the client–server framework.
+
#The IPC interface it opens to its client, via the client–server framework.
2. The interface to underlying services or resources it needs in order to
+
#The interface to underlying services or resources it needs in order to function.
function.
+
#Any other interfaces into the server’s process.
3. Any other interfaces into the server’s process.
+
 
 
A server runs in a normal OS process – there may be occasions where one
 
A server runs in a normal OS process – there may be occasions where one
 
process would expose several server interfaces (such as ESOCK and ETEL,
 
process would expose several server interfaces (such as ESOCK and ETEL,
Line 312: Line 326:
 
servers and processes, and this can help simplify one’s reasoning. Note
 
servers and processes, and this can help simplify one’s reasoning. Note
 
that it is a practical impossibility for one Symbian OS server (that is, the
 
that it is a practical impossibility for one Symbian OS server (that is, the
RServer or RServer2 instance) to span multiple processes. However,
+
{{Icode|RServer}} or {{Icode|RServer2}} instance) to span multiple processes. However,
 
when analyzing possible interfaces to a server, be aware that other code
 
when analyzing possible interfaces to a server, be aware that other code
 
might execute within the same process as that server. Any interface
 
might execute within the same process as that server. Any interface
 
opened by that code is also a potential interface to your server, so all
 
opened by that code is also a potential interface to your server, so all
interfaces into the process must be analyzed. It follows that all server
+
interfaces into the ''process'' must be analyzed. It follows that all server
 
interfaces into the process in question should be considered as a whole.
 
interfaces into the process in question should be considered as a whole.
 +
 
It may well be that you perform threat model analysis across a number
 
It may well be that you perform threat model analysis across a number
 
of servers that co-operate to perform some common goal. Here you must
 
of servers that co-operate to perform some common goal. Here you must
Line 349: Line 364:
 
incentive for attack, and require a greater duty of care in identifying and
 
incentive for attack, and require a greater duty of care in identifying and
 
addressing such threats. Threats can be brainstormed on a per-capability
 
addressing such threats. Threats can be brainstormed on a per-capability
basis. For example, a server possessing the ReadUserData capability
+
basis. For example, a server possessing the {{Icode|ReadUserData}} capability
 
would need to consider if there is any way it could be manipulated or
 
would need to consider if there is any way it could be manipulated or
 
tricked into unknowingly revealing the user’s private data or leaking confidential
 
tricked into unknowingly revealing the user’s private data or leaking confidential
Line 368: Line 383:
  
 
The countermeasures are split into three broad types:
 
The countermeasures are split into three broad types:
platform security architecture – features provided ‘for free’ by the OS
+
*platform security architecture – features provided ‘for free’ by the OS architecture
architecture
+
*server design and implementation – aspects of good server development that can work to mitigate security threats
server design and implementation – aspects of good server development
+
*platform security mechanism – security mechanisms provided by the platform for use by the code, but which the code must be designed to utilize.
that can work to mitigate security threats
+
platform security mechanism – security mechanisms provided by the
+
platform for use by the code, but which the code must be designed to
+
utilize.
+
  
 
==Platform Security Architecture==
 
==Platform Security Architecture==
Loader Rules (prevent untrusted code execution in a trusted
+
'''''Loader Rules (prevent untrusted code execution in a trusted environment)'''''
environment)
+
  
 
The platform security loader’s rules provide a strong level of protection
 
The platform security loader’s rules provide a strong level of protection
Line 384: Line 394:
 
access to server-owned resources. However, this is no protection against
 
access to server-owned resources. However, this is no protection against
 
well-meaning but bug-infested code within your server!
 
well-meaning but bug-infested code within your server!
Process Isolation (prevents tampering with server execution
+
 
environment)
+
'''''Process Isolation (prevents tampering with server execution environment)'''''
  
 
From its inception, Symbian OS was designed to support a strong model
 
From its inception, Symbian OS was designed to support a strong model
Line 397: Line 407:
 
One example of this is the IPC v1 client–server APIs – hence the reason
 
One example of this is the IPC v1 client–server APIs – hence the reason
 
these are now superseded by the strongly-typed IPC v2 framework.
 
these are now superseded by the strongly-typed IPC v2 framework.
 +
 
The above are generic features, afforded to all processes under the
 
The above are generic features, afforded to all processes under the
 
platform security architecture. However, they are worthy of repetition in
 
platform security architecture. However, they are worthy of repetition in
Line 406: Line 417:
 
way that this could happen is by calling virtual functions of C++
 
way that this could happen is by calling virtual functions of C++
 
objects supplied by the client. If the client were able to tamper with the
 
objects supplied by the client. If the client were able to tamper with the
object, it could change pointers in the vtable to cause arbitrary code
+
object, it could change pointers in the {{Icode|vtable}} to cause arbitrary code
 
to be executed. C++ objects, which may have virtual functions, must
 
to be executed. C++ objects, which may have virtual functions, must
 
not be byte-copied from an IPC message – instead, objects should be
 
not be byte-copied from an IPC message – instead, objects should be
Line 412: Line 423:
  
 
==Platform Security Mechanisms==
 
==Platform Security Mechanisms==
Session Connect Policy Check (detects server name spoofing)
+
'''''Session Connect Policy Check (detects server name spoofing)'''''
 +
 
 
As we illustrated in the example earlier in this chapter, when the client
 
As we illustrated in the example earlier in this chapter, when the client
 
library code connects to a server it can specify a security policy, which
 
library code connects to a server it can specify a security policy, which
Line 419: Line 431:
 
but does provide a means through which the client library can detect this.
 
but does provide a means through which the client library can detect this.
  
ProtServ for System Servers
+
'''''{{Icode|ProtServ}} for System Servers'''''
  
 
In order to stop spoof servers from taking the names of critical system
 
In order to stop spoof servers from taking the names of critical system
Line 426: Line 438:
 
the ‘!’ character. Registering server objects that have names beginning
 
the ‘!’ character. Registering server objects that have names beginning
 
with this character with the kernel is only permitted for processes possessing
 
with this character with the kernel is only permitted for processes possessing
the ProtServ capability, all others will receive an error return code
+
the {{Icode|ProtServ}} capability, all others will receive an error return code
from their call to CServer2::Start(). In this way, only processes
+
from their call to {{Icode|CServer2::Start()}}. In this way, only processes
 
trusted with this capability are permitted to provide system services. (As
 
trusted with this capability are permitted to provide system services. (As
 
a rule of thumb, a system service can be considered to be one that
 
a rule of thumb, a system service can be considered to be one that
Line 436: Line 448:
 
to hold at least one capability – and a system capability at that – meaning
 
to hold at least one capability – and a system capability at that – meaning
 
that the loader rules prevent such system servers from loading any lesser
 
that the loader rules prevent such system servers from loading any lesser
trusted DLLs (i.e. any DLL lacking the ProtServ capability).
+
trusted DLLs (i.e. any DLL lacking the {{Icode|ProtServ}} capability).
  
IPC Security Policy Check
+
'''''IPC Security Policy Check'''''
  
 
This is the most significant new security measure available to servers
 
This is the most significant new security measure available to servers
Line 453: Line 465:
 
framework, built upon these building blocks, should it be necessary.
 
framework, built upon these building blocks, should it be necessary.
  
CPolicyServer Framework
+
'''''{{Icode|CPolicyServer}} Framework'''''
  
 
As we touched upon in Section 5.1.2, correctly coding, verifying and
 
As we touched upon in Section 5.1.2, correctly coding, verifying and
Line 459: Line 471:
 
a server is a potentially error-prone activity. For this reason, the user
 
a server is a potentially error-prone activity. For this reason, the user
 
library provides base-classes to make life easier for the server writer; this
 
library provides base-classes to make life easier for the server writer; this
framework is called CPolicyServer, and allows for the definition of
+
framework is called {{Icode|CPolicyServer}}, and allows for the definition of
 
a static policy table based on the opcode number of the function being
 
a static policy table based on the opcode number of the function being
 
invoked over IPC. Once mastered, this can reduce the repetition involved
 
invoked over IPC. Once mastered, this can reduce the repetition involved
Line 465: Line 477:
 
and we will cover it in more detail in Section 5.4.2.
 
and we will cover it in more detail in Section 5.4.2.
  
Data Caging
+
'''''Data Caging'''''
  
 
A server has the use of the private data-cage owned by the process
 
A server has the use of the private data-cage owned by the process
 
it is running in. Once again assuming a one-to-one process–server
 
it is running in. Once again assuming a one-to-one process–server
 
relationship, this implies one private data-cage per server.
 
relationship, this implies one private data-cage per server.
 +
 
This is one mechanism through which the server can store non-volatile
 
This is one mechanism through which the server can store non-volatile
 
information it needs for its operation. Storing and sharing data is discussed
 
information it needs for its operation. Storing and sharing data is discussed
Line 479: Line 492:
 
its data-caged area.
 
its data-caged area.
  
Anonymous Objects and Secure Handle Transfer
+
'''''Anonymous Objects and Secure Handle Transfer'''''
  
 
The EKA2 kernel provides a powerful mechanism through which handles
 
The EKA2 kernel provides a powerful mechanism through which handles
 
to kernel objects can be securely passed between processes, to allow
 
to kernel objects can be securely passed between processes, to allow
 
secure sharing of the underlying resource. For example, a handle to an
 
secure sharing of the underlying resource. For example, a handle to an
RMsgQueue may be passed from a producer process to a consumer
+
{{Icode|RMsgQueue}} may be passed from a producer process to a consumer
 
process, and no other process will be permitted access to the kernel
 
process, and no other process will be permitted access to the kernel
 
queue object.
 
queue object.
Line 496: Line 509:
 
accessible to any process, through an open by name operation. For this
 
accessible to any process, through an open by name operation. For this
 
reason, unnamed (or anonymous) global objects have been introduced.
 
reason, unnamed (or anonymous) global objects have been introduced.
To create an anonymous global object, KNullDesC should be passed as
+
To create an anonymous global object, {{Icode|KNullDesC}} should be passed as
 
the name parameter in the appropriate Create() method.
 
the name parameter in the appropriate Create() method.
  
Line 504: Line 517:
 
server.
 
server.
  
Constrain Server Responsibilities and Dependencies
+
'''''Constrain Server Responsibilities and Dependencies'''''
  
 
A server that performs many different roles is harder to develop securely,
 
A server that performs many different roles is harder to develop securely,
Line 510: Line 523:
 
server that has run-time dependencies on many other parts of the system,
 
server that has run-time dependencies on many other parts of the system,
 
will be more fragile than one that has a constrained set of dependencies.
 
will be more fragile than one that has a constrained set of dependencies.
 +
 
Architecturally, it is far simpler to consider and validate the behavior
 
Architecturally, it is far simpler to consider and validate the behavior
 
of a server that has clearly identified responsibilities, and constrained
 
of a server that has clearly identified responsibilities, and constrained
Line 521: Line 535:
 
given to this issue.
 
given to this issue.
  
Parameter Validation
+
'''''Parameter Validation'''''
  
 
Whenever a client passes data to a server, the server must carefully
 
Whenever a client passes data to a server, the server must carefully
Line 546: Line 560:
 
may modify the parameter’s memory contents or allocation state while
 
may modify the parameter’s memory contents or allocation state while
 
the client thread is blocked, even on a synchronous request.
 
the client thread is blocked, even on a synchronous request.
 +
 
To combat this, the server must expect to handle errors arising
 
To combat this, the server must expect to handle errors arising
during client memory access, i.e. the Read(), Write() and Get-
+
during client memory access, i.e. the {{Icode|Read()}}, {{Icode|Write()}} and {{Icode|GetDesLength()}} members of {{Icode|RMessagePtr2}}; wherever possible, use
DesLength() members of RMessagePtr2; wherever possible, use
+
 
the leaving overloads. Useful additions to particularly note are the new
 
the leaving overloads. Useful additions to particularly note are the new
RMessagePtr2::GetDesLengthL() leaving overloads – these allow
+
{{Icode|RMessagePtr2::GetDesLengthL()}} leaving overloads – these allow
 
the server to safely discover the length of a client buffer, and have the
 
the server to safely discover the length of a client buffer, and have the
 
standard exception framework take the burden on handling a descriptor
 
standard exception framework take the burden on handling a descriptor
 
error, without having to remember to manually check for negative error
 
error, without having to remember to manually check for negative error
 
results, as is the case with the older non-leaving version.
 
results, as is the case with the older non-leaving version.
 +
 
Also, a server should never accept or use a pointer received over
 
Also, a server should never accept or use a pointer received over
 
IPC. We have already noted that you must never call code via a pointer
 
IPC. We have already noted that you must never call code via a pointer
Line 563: Line 578:
 
except in the most bandwidth-critical applications.
 
except in the most bandwidth-critical applications.
  
Robust Error-handling Framework
+
'''''Robust Error-handling Framework'''''
  
 
Continuing from the previous point, the server-side error-handling framework
 
Continuing from the previous point, the server-side error-handling framework
 
should not be overlooked, as it is a very significant part of the security
 
should not be overlooked, as it is a very significant part of the security
 
design of the server interface. The server error framework is built upon
 
design of the server interface. The server error framework is built upon
the standard CActive implementation of the leave/trap Symbian OS
+
the standard {{Icode|CActive}} implementation of the leave/trap Symbian OS
primitives. Unlike the IPC v1 CServer class, both CServer2 and CPolicyServer
+
primitives. Unlike the IPC v1 {{Icode|CServer}} class, both {{Icode|CServer2}} and {{Icode|CPolicyServer}}
provide an implementation of the CActive::RunError()
+
provide an implementation of the {{Icode|CActive::RunError()}}
 
interface, and, if relevant, they pass the trapped error onto the specific
 
interface, and, if relevant, they pass the trapped error onto the specific
CSession2 instance that was handling execution at the point the leave
+
{{Icode|CSession2}} instance that was handling execution at the point the leave
 
was encountered, providing it with the message that was being processed
 
was encountered, providing it with the message that was being processed
at that time via the CSession2::ServiceError() method.
+
at that time via the {{Icode|CSession2::ServiceError()}} method.
This means that concrete implementations of CSession2 are able and
+
 
 +
This means that concrete implementations of {{Icode|CSession2}} are able and
 
encouraged to make maximum use of the leave framework. If required,
 
encouraged to make maximum use of the leave framework. If required,
the concrete session class can override ServiceError() with a custom
+
the concrete session class can override {{Icode|ServiceError()}} with a custom
 
implementation, although the default implementation, which simply
 
implementation, although the default implementation, which simply
 
completes the message with the leave code that was thrown, will suffice
 
completes the message with the leave code that was thrown, will suffice
 
in many instances.
 
in many instances.
  
Thismeans that if, during the initial (synchronous) processing of a client
+
This means that if, during the initial (synchronous) processing of a client
 
request, any error occurs – for example, low memory or disk resources,
 
request, any error occurs – for example, low memory or disk resources,
 
an invalid parameter in a deeply nested structure provided by the client,
 
an invalid parameter in a deeply nested structure provided by the client,
Line 589: Line 605:
 
the error condition back to the client.
 
the error condition back to the client.
  
Robust API Design
+
'''''Robust API Design'''''
  
 
Getting the API design right can greatly ease the design and implementation
 
Getting the API design right can greatly ease the design and implementation
 
of the security policy for that API. Here are a few points to consider:
 
of the security policy for that API. Here are a few points to consider:
Functions with a specific purpose are easier to provide a policy
+
*Functions with a specific purpose are easier to provide a policy for than multipurpose, generic or ambiguous methods, where the context must be taken into consideration in order to decide on the appropriate policy. For example, contrast {{Icode|RDisk::Format()}} with {{Icode|RDisk::PerformAdministrativeOperation(TOperation)}} or {{Icode|RDisk::Extension(TExtId}}).
for than multipurpose, generic or ambiguous methods, where the
+
*Having a specific purpose also helps the user of the API to create secure code, as it is clearer what the consequences of calling the method might be.
context must be taken into consideration in order to decide on the
+
*The primary outcome of a security policy failure in an API is an error code result, for example, {{Icode|KErrPermissionDenied}}. The error modes of APIs should be considered in general.
appropriate policy. For example, contrast RDisk::Format() with
+
*Error codes should be returned to the client application at every point where an error can legitimately arise, but not in a place where the client would not be able to handle the error (such as when canceling or closing down). Generally the error should be indicated at the point at which failure has become inevitable, but no sooner.
RDisk::PerformAdministrativeOperation(TOperation)
+
or RDisk::Extension(TExtId).
+
Having a specific purpose also helps the user of the API to create
+
secure code, as it is clearer what the consequences of calling the
+
method might be.
+
The primary outcome of a security policy failure in an API is an
+
error code result, for example, KErrPermissionDenied. The error
+
modes of APIs should be considered in general.
+
Error codes should be returned to the client application at every point
+
where an error can legitimately arise, but not in a place where the
+
client would not be able to handle the error (such as when canceling
+
or closing down). Generally the error should be indicated at the point
+
at which failure has become inevitable, but no sooner.
+
  
Server Name
+
'''''Server Name'''''
  
 
We saw how a client can use a security policy to ensure they only
 
We saw how a client can use a security policy to ensure they only
Line 622: Line 625:
 
It is also worth pointing out that the server name is quite distinct from
 
It is also worth pointing out that the server name is quite distinct from
 
the process name. Both are stored by the kernel, the server name in a
 
the process name. Both are stored by the kernel, the server name in a
DServer object, the process name in a DProcess object. As already
+
{{Icode|DServer}} object, the process name in a {{Icode|DProcess}} object. As already
 
mentioned, one process can have many servers running in it. By default
 
mentioned, one process can have many servers running in it. By default
 
the process name is equal to the name of the EXE that was used to launch
 
the process name is equal to the name of the EXE that was used to launch
Line 640: Line 643:
 
the answer:
 
the answer:
  
Does it need protection at all?
+
'''''Does it need protection at all?'''''
  
 
Use threat analysis to drive this.
 
Use threat analysis to drive this.
  
Is this the right point to make a policy check?
+
'''''Is this the right point to make a policy check?'''''
  
 
The aim is to make the security policy test at the point where the client
 
The aim is to make the security policy test at the point where the client
Line 657: Line 660:
 
case, the policy check can be made once at session establishment, and
 
case, the policy check can be made once at session establishment, and
 
need not be repeated on each subsequent operation in that session.
 
need not be repeated on each subsequent operation in that session.
 +
 
Another point to remember is that security policy checks against a
 
Another point to remember is that security policy checks against a
 
client must only be made within the context of the server, and not within
 
client must only be made within the context of the server, and not within
Line 663: Line 667:
 
the trust boundary.
 
the trust boundary.
  
Can the API be made secure without restricting it with a capability or
+
'''''Can the API be made secure without restricting it with a capability or caller identity?'''''
caller identity?
+
  
 
Careful API design can often reduce the threat presented by an API. For
 
Careful API design can often reduce the threat presented by an API. For
Line 674: Line 677:
 
device, sound output can be left unrestricted.
 
device, sound output can be left unrestricted.
  
What aspects of the API need restricting?
+
'''''What aspects of the API need restricting?'''''
  
Arrange the API so that sensitive operations are separated from nonsensitive
+
Arrange the API so that sensitive operations are separated from non-sensitive
 
ones – for example, under different methods and server opcodes
 
ones – for example, under different methods and server opcodes
 
– where possible.
 
– where possible.
  
Do you know the identity of a single process that is, architecturally, the
+
'''''Do you know the identity of a single process that is, architecturally, the only client of this method?'''''
only client of this method?
+
  
 
If so, its SID may be checked instead of a capability.
 
If so, its SID may be checked instead of a capability.
  
Is it acceptable to use a list of known clients as the policy?
+
'''''Is it acceptable to use a list of known clients as the policy?'''''
  
 
Generally this is undesirable – the capability model was created specifically
 
Generally this is undesirable – the capability model was created specifically
Line 691: Line 693:
 
application domains it may be acceptable.
 
application domains it may be acceptable.
  
What is the asset and what is the threat to it, which you are protecting
+
'''''What is the asset and what is the threat to it, which you are protecting against?'''''
against?
+
  
 
Check if there is an existing system API that is sufficiently similar to have
 
Check if there is an existing system API that is sufficiently similar to have
 
set a precedent for how this asset is to be protected.
 
set a precedent for how this asset is to be protected.
  
Is there an external or industry policy or requirement about how this
+
'''''Is there an external or industry policy or requirement about how this asset must be protected?'''''
asset must be protected?
+
  
 
There may be some regulatory or commercial circumstances that require
 
There may be some regulatory or commercial circumstances that require
Line 704: Line 704:
 
authenticity or permissibility of the requester or the request.
 
authenticity or permissibility of the requester or the request.
  
Does this method layer over some other API?
+
'''''Does this method layer over some other API?'''''
  
 
Consider whether the new API fully exposes the lower-level API (and if
 
Consider whether the new API fully exposes the lower-level API (and if
Line 710: Line 710:
 
whether the policy on the lower-level API is appropriate for this higher
 
whether the policy on the lower-level API is appropriate for this higher
 
level method, and whether the threat is reduced by using this method. For
 
level method, and whether the threat is reduced by using this method. For
example, the Symbian OS Bluetooth stack enforces LocalServices at
+
example, the Symbian OS Bluetooth stack enforces {{Icode|LocalServices}} at
 
its client API, even though it is revealing functionality that is implemented
 
its client API, even though it is revealing functionality that is implemented
over APIs protected with the CommDD capability. On the other hand, a
+
over APIs protected with the {{Icode|CommDD}} capability. On the other hand, a
 
CSY offering direct serial port access to the Bluetooth hardware would
 
CSY offering direct serial port access to the Bluetooth hardware would
duplicate the device driver policy of requiring CommDD. One particular
+
duplicate the device driver policy of requiring {{Icode|CommDD}}. One particular
 
aspect to consider here is whether your server might be ‘leaking’ access
 
aspect to consider here is whether your server might be ‘leaking’ access
 
to the underlying sensitive API. All processes holding capabilities have a
 
to the underlying sensitive API. All processes holding capabilities have a
Line 720: Line 720:
 
leak access in this way.
 
leak access in this way.
  
What would be the impact of not protecting this method?
+
'''''What would be the impact of not protecting this method?'''''
  
 
Possible consequences could be loss of users’ confidential data, unauthorized
 
Possible consequences could be loss of users’ confidential data, unauthorized
 
access to the network, unauthorized phone reconfiguration, and so
 
access to the network, unauthorized phone reconfiguration, and so
 
on. This can give an initial pointer as to which capability should apply.
 
on. This can give an initial pointer as to which capability should apply.
Will the client application be able to get the necessary capabilities?
+
 
 +
'''''Will the client application be able to get the necessary capabilities?'''''
 +
 
 
Once you have identified a proposed capability (or set of capabilities)
 
Once you have identified a proposed capability (or set of capabilities)
 
under which the API could be protected, you should carefully consider
 
under which the API could be protected, you should carefully consider
Line 741: Line 743:
 
When you reject a client request, it is generally recommended that you
 
When you reject a client request, it is generally recommended that you
 
do so by completing the relevant message with an appropriate error code.
 
do so by completing the relevant message with an appropriate error code.
KErrPermissionDenied should only be used in the case where a
+
{{Icode|KErrPermissionDenied}} should only be used in the case where a
 
policy check has failed, as it gives a clear indication to the application
 
policy check has failed, as it gives a clear indication to the application
 
developer or user that it is a security policy failure, rather than simply an
 
developer or user that it is a security policy failure, rather than simply an
Line 761: Line 763:
 
protocol and expose the API as a set of exported methods. There are two
 
protocol and expose the API as a set of exported methods. There are two
 
significant things to bear in mind, if you use this architecture:
 
significant things to bear in mind, if you use this architecture:
1. As this code is running within the client process, it is futile to perform
+
#As this code is running within the client process, it is futile to perform any security checks as they can easily be defeated.
any security checks as they can easily be defeated.
+
#As this code is running within the client process, it must be sufficiently trusted to be loaded by that process.
2. As this code is running within the client process, it must be sufficiently
+
 
trusted to be loaded by that process.
+
 
Statement 1 is a re-iteration of what we’ve seen in the previous sections –
 
Statement 1 is a re-iteration of what we’ve seen in the previous sections –
 
security policies should only be checked at the point where a process
 
security policies should only be checked at the point where a process
 
boundary is crossed. Within the client library, no boundary has been
 
boundary is crossed. Within the client library, no boundary has been
 
crossed, so a security check is unnecessary and ineffective.
 
crossed, so a security check is unnecessary and ineffective.
 +
 
We need to consider statement 2 a little further. This asserts that the
 
We need to consider statement 2 a little further. This asserts that the
 
client needs to trust your code in order to use it. This is to avoid the client
 
client needs to trust your code in order to use it. This is to avoid the client
 
process being tricked into doing something unintentional through the use
 
process being tricked into doing something unintentional through the use
 
of untrusted code.
 
of untrusted code.
 +
 
If the client library is distributed as a binary DLL, as most are, then
 
If the client library is distributed as a binary DLL, as most are, then
 
the loader’s capability rules, as described in Chapter 2, will enforce
 
the loader’s capability rules, as described in Chapter 2, will enforce
Line 780: Line 783:
 
of clients, this means that the client library must have a wide set of
 
of clients, this means that the client library must have a wide set of
 
capabilities. For this reason, the client libraries on most Symbian provided
 
capabilities. For this reason, the client libraries on most Symbian provided
servers are assigned all capabilities except Tcb. This is accepted
+
servers are assigned all capabilities except {{Icode|Tcb}}. This is accepted
 
as a trade-off between maximizing the utility of the server and constraining
 
as a trade-off between maximizing the utility of the server and constraining
 
the code base trusted to run within the most sensitive part of
 
the code base trusted to run within the most sensitive part of
Line 813: Line 816:
 
The other important considerations for the client library were illustrated
 
The other important considerations for the client library were illustrated
 
in the opening example. To recap:
 
in the opening example. To recap:
The RSessionBase::SendReceive() methods should be recoded
+
*The {{Icode|RSessionBase::SendReceive()}} methods should be recoded in IPC v2 format – that is, using {{Icode|TIpcArgs}} in place of {{Icode|TAny*}} parameters.
in IPC v2 format – that is, using TIpcArgs in place of TAny*
+
*When creating a session to the server, it is wise to add a policy check that ensures that the server is running with the expected SID.
parameters.
+
*For servers that operate in the {{Icode|ProtServ}} domain, the name will need to be changed to start with ‘!’. Only servers which are system critical – without which the system cannot operate – need to implement this, which is, by definition, rare for an after-market application.
When creating a session to the server, it is wise to add a policy check
+
 
that ensures that the server is running with the expected SID.
+
For servers that operate in the ProtServ domain, the name will need
+
to be changed to start with ‘!’. Only servers which are system critical
+
– without which the system cannot operate – need to implement
+
this, which is, by definition, rare for an after-market application.
+
 
A final point to consider is the documentation for the client interface – this
 
A final point to consider is the documentation for the client interface – this
 
is most importance where the client interface is to be shared with others.
 
is most importance where the client interface is to be shared with others.
Line 830: Line 828:
 
==Server Considerations==
 
==Server Considerations==
 
The recommended way of adding security policy checks into a server is to
 
The recommended way of adding security policy checks into a server is to
use the CPolicyServer framework. This involves deriving the server’s
+
use the {{Icode|CPolicyServer}} framework. This involves deriving the server’s
main class from the CPolicyServer base class, instead of CServer or
+
main class from the {{Icode|CPolicyServer}} base class, instead of {{Icode|CServer}} or
CServer2.
+
{{Icode|CServer2}}.
  
 
If you are migrating a server to this framework, you will see that the
 
If you are migrating a server to this framework, you will see that the
 
following changes need to be made:
 
following changes need to be made:
On construction the CPolicyServer requires a parameter of type
+
*On construction the {{Icode|CPolicyServer}} requires a parameter of type {{Icode|CPolicyServer::TPolicy}} to be supplied to it.
CPolicyServer::TPolicy to be supplied to it.
+
*Two virtual methods, {{Icode|CPolicyServer::CustomSecurityCheckL()}} and {{Icode|CPolicyServer::SecurityCheckFailedL()}} may need to be overridden, if referenced by the {{Icode|TPolicy}} table provided in the constructor.
Two virtual methods, CPolicyServer::CustomSecurity-
+
CheckL() and CPolicyServer::SecurityCheckFailedL()
+
may need to be overridden, if referenced by the TPolicy table
+
provided in the constructor.
+
  
Policy Tables
+
===Policy Tables===
  
 
The policy table is designed to allow a very compact representation of
 
The policy table is designed to allow a very compact representation of
Line 856: Line 850:
 
table works.
 
table works.
  
TPolicy
+
[[File:Figure 5.1 platsec.jpg]]
iOnConnect = 3
+
{|align="center"
iRangeCount= 8
+
|-
iSpare = 0
+
|'''Figure 5.1''' TPolicy Structure
0-1
+
|}
2-7
+
8
+
9
+
10-11
+
12-41
+
42-44
+
45-
+
0
+
2
+
8
+
9
+
10
+
12
+
42
+
45
+
EAlwaysPass
+
0
+
1
+
2
+
ENotSupported
+
2
+
ECustomCheck
+
ENotSupported
+
C1
+
C2
+
S1
+
S2
+
-1
+
EPanicClient
+
EFailClient
+
EFailClient
+
iElementsIndex
+
iRanges
+
IPC
+
message
+
number
+
Array of TInt
+
first in range
+
range index
+
Array of TUint8
+
policy index
+
Array of TPolicyElement
+
{iPolicy, iAction}
+
iElements
+
  
Figure 5.1 TPolicy Structure
+
Here we see an example {{Icode|TPolicy}} instance, with the three array members – {{Icode|iRanges}}, {{Icode|iElementsIndex}}, and {{Icode|iElements}} – expanded.
  
Here we see an example TPolicy instance, with the three array members
+
The elements of the first array, {{Icode|iRanges}}, correspond to IPC function
– iRanges, iElementsIndex, and iElements – expanded.
+
 
+
The elements of the first array, iRanges, correspond to IPC function
+
 
numbers. When a message is received in a session the value of
 
numbers. When a message is received in a session the value of
RMessage2::Function() is searched for in this array, in order to
+
{{Icode|RMessage2::Function()}} is searched for in this array, in order to
 
determine what action the policy server framework will apply to the
 
determine what action the policy server framework will apply to the
 
message – based on the index of the IPC function in the array. This is
 
message – based on the index of the IPC function in the array. This is
called the range index. For example, an IPC message with function value
+
called the ''range index''. For example, an IPC message with function value
 
8 will cause the value 8 to be searched for in this first array. Here the
 
8 will cause the value 8 to be searched for in this first array. Here the
 
3rd index contains the value 8, but as indexes are counted starting at 0
 
3rd index contains the value 8, but as indexes are counted starting at 0
Line 928: Line 875:
 
reserved for use by the server framework itself).
 
reserved for use by the server framework itself).
  
The range index is used as an index into the second array, iElementsIndex,
+
The range index is used as an index into the second array, {{Icode|iElementsIndex}},
to determine the policy index. So IPC function 9 corresponds
+
to determine the ''policy index''. So IPC function 9 corresponds
 
to range index 3, which corresponds to a policy index of 2.
 
to range index 3, which corresponds to a policy index of 2.
  
 
Following the same pattern, the policy index is then used as the index
 
Following the same pattern, the policy index is then used as the index
into third and final array, iElements. This yields the TPolicyElement
+
into third and final array, {{Icode|iElements}}. This yields the {{Icode|TPolicyElement}}
 
that should be applied to this particular IPC function. Each object of this
 
that should be applied to this particular IPC function. Each object of this
type contains both the security policy that should be applied (encoded
+
type contains both the ''security policy'' that should be applied (encoded
in the iPolicy member as a TSecurityPolicy, seen in our opening
+
in the {{Icode|iPolicy}} member as a {{Icode|TSecurityPolicy}}, seen in our opening
example) and the failure action to take in the case where the policy check
+
example) and the ''failure action'' to take in the case where the policy check
fails (encoded in the iAction member).
+
fails (encoded in the {{Icode|iAction}} member).
  
 
Using the example of IPC function 8, with policy index of 1, we
 
Using the example of IPC function 8, with policy index of 1, we
Line 944: Line 891:
 
an instance of a security policy demanding that the client hold two
 
an instance of a security policy demanding that the client hold two
 
specific capabilities. In our example code this is initialized with the
 
specific capabilities. In our example code this is initialized with the
_INIT_SECURITY_POLICY_C2 macro. If a client invoking this IPC
+
{{Icode|_INIT_SECURITY_POLICY_C2}} macro. If a client invoking this IPC
 
function does not hold both specified capabilities, then the failure
 
function does not hold both specified capabilities, then the failure
action EFailClient (from the CPolicyServer::TFailureAction
+
action {{Icode|EFailClient}} (from the {{Icode|CPolicyServer::TFailureAction}}
 
enumeration) indicates that the message must be completed with the
 
enumeration) indicates that the message must be completed with the
KErrPermissionDenied error code.
+
{{Icode|KErrPermissionDenied}} error code.
  
 
This policy table is described in code as follows:
 
This policy table is described in code as follows:
  
const TUint myRangeCount = 8;
+
<code>
const TInt myRanges[myRangeCount] =
+
const TUint myRangeCount = 8;
{
+
const TInt myRanges[myRangeCount] =
0, //range is 0-1 inclusive
+
  {
2, //range is 2-7 inclusive
+
  0, //range is 0-1 inclusive
8, //range is 8 only
+
  2, //range is 2-7 inclusive
9, //range is 9 only
+
  8, //range is 8 only
10, //range is 10-11 inclusive
+
  9, //range is 9 only
12, //range is 12-41 inclusive
+
  10, //range is 10-11 inclusive
42, //range is 42-44 inclusive
+
  12, //range is 12-41 inclusive
45, //range is 45-KMaxTInt inclusive
+
  42, //range is 42-44 inclusive
};
+
  45, //range is 45-KMaxTInt inclusive
const TUint8 myElementsIndex[myRangeCount] =
+
  };
{
+
CPolicyServer::EAlwaysPass, //IPC 0 -
+
const TUint8 myElementsIndex[myRangeCount] =
0, //IPC 2 -
+
  {
1, //IPC 8 -
+
  CPolicyServer::EAlwaysPass,   //IPC 0 -
2, //IPC 9 -
+
  0,                           //IPC 2 -
104 HOW TO WRITE SECURE SERVERS
+
  1,                           //IPC 8 -
CPolicyServer::ENotSupported, //IPC 10 -
+
  2,                           //IPC 9 -
2, //IPC 12 -
+
  CPolicyServer::ENotSupported, //IPC 10 -
CPolicyServer::ECustomCheck, //IPC 42 -
+
  2,                           //IPC 12 -
CPolicyServer::ENotSupported, //IPC 45 - KMaxTInt
+
  CPolicyServer::ECustomCheck, //IPC 42 -
};
+
  CPolicyServer::ENotSupported, //IPC 45 - KMaxTInt
const CPolicyServer::TPolicyElement myElements[] =
+
  };
{
+
{_INIT_SECURITY_POLICY_C1(KMyCap1), -1}, //IPC 2 - 7
+
const CPolicyServer::TPolicyElement myElements[] =
{_INIT_SECURITY_POLICY_C2(KMyCap2A, KMyCap2B),
+
  {
CPolicyServer::EPanicClient}, //IPC 8
+
  {_INIT_SECURITY_POLICY_C1(KMyCap1), -1},   //IPC 2 - 7
{_INIT_SECURITY_POLICY_S1(KMySID, KMyCap3),
+
  {_INIT_SECURITY_POLICY_C2(KMyCap2A, KMyCap2B),
CPolicyServer::EFailClient}, //IPC 9, 12-41
+
                CPolicyServer::EFailClient}, //IPC 8
{_INIT_SECURITY_POLICY_C1(KMyConnectCap),
+
  {_INIT_SECURITY_POLICY_S1(KMySID, KMyCap3),
CPolicyServer::EFailClient}, //Connect
+
                CPolicyServer::EFailClient}, //IPC 9, 12-41
}
+
  {_INIT_SECURITY_POLICY_C1(KMyConnectCap),
const CPolicySErver::TPolicy myPolicy =
+
                CPolicyServer::EPanicClient}, //Connect
{
+
  }
3, // Connect messages use policy index 3
+
myRangeCount,
+
const CPolicyServer::TPolicy myPolicy =
myRanges,
+
  {
myElementsIndex,
+
  3,           // Connect messages use policy index 3
myElements,
+
  myRangeCount,
}
+
  myRanges,
 +
  myElementsIndex,
 +
  myElements,
 +
  }</code>
  
If you get confused following these steps, it is useful to remember
+
The process followed to look up the appropriate {{Icode|TPolicyElement}} could be confusing so, to summarize:
that the IPC number goes through a reverse lookup in iRanges, the
+
The IPC number is first used to do a binary search in {{Icode|iRanges}}; the index of the result (the ''range index'') is then used as an index into {{Icode|iElementsIndex}}, and the result of that (the ''policy index'') is used as an index into {{Icode|iElements}}.
result of which (the range index) goes through a forward lookup in
+
iElementsIndex, and the result of that (the policy index) goes through
+
a forward lookup in iElements.
+
  
When a policy check fails, i.e. whenever the CPolicyServer::
+
When a policy check fails, i.e. whenever the {{Icode|CPolicyServer::EFailClient}} result is encountered, diagnostics can be generated
EFailClient result is encountered, diagnostics can be generated
+
 
in the emulator to aid debugging, as already mentioned in Chapter 3.
 
in the emulator to aid debugging, as already mentioned in Chapter 3.
 
 
Here’s an example:
 
Here’s an example:
 +
 +
<code>
 +
*PlatSec* ERROR - Capability check failed - A Message (function number=0x000000cf) from Thread
 +
helloworld[10008ace]0001::HelloWorld, sent to Server !CntLockServer, was checked by Thread
 +
CNTSRV.EXE[10003a73]0001::!CntLockServer and was found to be missing the capabilities: WriteUserData .
 +
Additional diagnostic message: Checked by CPolicyServer::RunL</code>
  
*PlatSec* ERROR - Capability check failed - A Message
 
(function number=0x000000cf) from Thread
 
helloworld[10008ace]0001::HelloWorld, sent to Server
 
!CntLockServer, was checked by Thread
 
CNTSRV.EXE[10003a73]0001::!CntLockServer and was found to
 
be missing the capabilities: WriteUserData . Additional
 
diagnostic message: Checked by CPolicyServer::RunL
 
 
In this example diagnostic you can see:
 
In this example diagnostic you can see:
the message function number: 0x000000cf
+
*the message function number: {{Icode|0x000000cf}}
the client process and thread that sent the request: helloworld
+
*the client process and thread that sent the request: {{Icode|helloworld [10008ace]0001::HelloWorld}}
[10008ace]0001::HelloWorld
+
*the name of the server that received the request: {{Icode|!CntLockServer}}
the name of the server that received the request: !CntLockServer
+
*the name of the process and thread in which the server is hosted:{{Icode| CNTSRV.EXE[10003a73]0001::!CntLockServer}} (note the exclamation mark on the thread name here is coincidental, and not enforced by the {{Icode|ProtServ}} capability)
SERVER IMPLEMENTATION CONSIDERATIONS 105
+
*the reason that the {{Icode|TSecurityPolicy}} check failed: a lack of {{Icode|WriteUserData}}
the name of the process and thread in which the server is hosted:
+
*the additional diagnostic information, in this case indicating that the security policy check was made from within the {{Icode|CPolicyServer::RunL}} framework function.
CNTSRV.EXE[10003a73]0001::!CntLockServer (note the exclamation
+
mark on the thread name here is coincidental, and not
+
enforced by the ProtServ capability)
+
the reason that the TSecurityPolicy check failed: a lack of
+
WriteUserData
+
the additional diagnostic information, in this case indicating that
+
the security policy check was made from within the CPolicy-
+
Server::RunL framework function.
+
  
Special Cases
+
===Special Cases===
  
 
There are some special cases to consider in the framework. Firstly,
 
There are some special cases to consider in the framework. Firstly,
 
connect messages do not go through the first two array lookups, as
 
connect messages do not go through the first two array lookups, as
 
connect messages do not have normal IPC function numbers. Instead, the
 
connect messages do not have normal IPC function numbers. Instead, the
policy index for a connect request is taken directly from the iOnConnect
+
policy index for a connect request is taken directly from the {{Icode|iOnConnect}}
member of TPolicy itself. This is then looked up in the third array as
+
member of {{Icode|TPolicy}} itself. This is then looked up in the third array as
 
normal.
 
normal.
  
Secondly, if any policy index found by lookup in the iElementsIndex
+
Secondly, if any policy index found by lookup in the {{Icode|iElementsIndex}}
array or from iOnConnect is a value from the CPolicyServer::
+
array or from {{Icode|iOnConnect}} is a value from the {{Icode|CPolicyServer::TSpecialCase}} enumeration, then no policy element lookup
TSpecialCase enumeration, then no policy element lookup
+
 
occurs, but instead the policy is inferred as follows:
 
occurs, but instead the policy is inferred as follows:
CPolicyServer::EAlwaysPass – the IPC function is allowed to
+
*{{Icode|CPolicyServer::EAlwaysPass}} – the IPC function is allowed to go ahead with no specific policy check against the client; that is, any client that can establish a session may call this method
go ahead with no specific policy check against the client; that is, any
+
*{{Icode|CPolicyServer::ENotSupported}} – the IPC message processing is completed immediately with {{Icode|KErrNotSupported}}. This should be used as the final element in {{Icode|iElementsIndex}}, and also to fill any other ‘holes’ in the IPC function space – for example, where deprecated functions have been removed, or gaps left for compatibility reasons. If any of these opcodes are used by new API methods in the future, the policy table must be considered and updated to allow the policy check to pass – for this reason having a default position of {{Icode|CPolicyServer::ENotSupported}} is much safer than {{Icode|CPolicyServer::EAlwaysPass}}. Note there is no {{Icode|CPolicyServer::EAlwaysFail}} enumeration – you should instead use {{Icode|CPolicyServer::ENotSupported}} for any opcode that must always fail.
client that can establish a session may call this method
+
*{{Icode|CPolicyServer::ECustomCheck}} – the security policy is not based on IPC function alone; run-time consideration is required to determine the policy to apply. A call to {{Icode|CPolicyServer::CustomSecurityCheckL()}}, discussed below, will be made in response to this.
CPolicyServer::ENotSupported – the IPC message processing
+
is completed immediately with KErrNotSupported. This should
+
be used as the final element in iElementsIndex, and also to fill
+
any other ‘holes’ in the IPC function space – for example, where
+
deprecated functions have been removed, or gaps left for compatibility
+
reasons. If any of these opcodes are used by new API
+
methods in the future, the policy table must be considered and
+
updated to allow the policy check to pass – for this reason having
+
a default position of CPolicyServer::ENotSupported is
+
much safer than CPolicyServer::EAlwaysPass. Note there
+
is no CPolicyServer::EAlwaysFail enumeration – you should
+
instead use CPolicyServer::ENotSupported for any opcode
+
that must always fail.
+
  
• CPolicyServer::ECustomCheck – the security policy is not
+
Finally, if the failure action – identified by the {{Icode|iAction}} member of
based on IPC function alone; run-time consideration is required to
+
{{Icode|CPolicyServer::TPolicyElement}} – is negative then it means special
determine the policy to apply. A call to CPolicyServer::Custom-
+
SecurityCheckL(), discussed below, will be made in response
+
to this.
+
 
+
Finally, if the failure action – identified by the iAction member of
+
CPolicyServer::TPolicyElement – is negative then it means special
+
 
failure processing should be performed instead of just returning a
 
failure processing should be performed instead of just returning a
 
simple error code to the client (the recommended approach) or a client
 
simple error code to the client (the recommended approach) or a client
panic. A call to CPolicyServer::CustomFailureActionL() will
+
panic. A call to {{Icode|CPolicyServer::CustomFailureActionL()}} will
 
be made to allow this to occur.
 
be made to allow this to occur.
  
Custom Checks and Failure Actions
+
===Custom Checks and Failure Actions===
  
If the policy index equals CPolicyServer::ECustomCheck then
+
If the policy index equals {{Icode|CPolicyServer::ECustomCheck}} then
CustomSecurityCheckL() is called. If a failure action is negative,
+
{{Icode|CustomSecurityCheckL()}} is called. If a failure action is negative,
the CustomFailureActionL() is called. Potentially one IPC message
+
the {{Icode|CustomFailureActionL()}} is called. Potentially one IPC message
 
could result in calls to both these methods.
 
could result in calls to both these methods.
  
TCustomResult CustomSecurityCheckL(const RMessage2 &aMsg,
+
<code>
TInt &aFailureAction, TSecurityInfo &aMissing);
+
TCustomResult CustomSecurityCheckL(const RMessage2 &aMsg,
 +
          TInt &aFailureAction, TSecurityInfo &aMissing);</code>
  
You must override this method if ECustomCheck appears in your policy
+
You must override this method if {{Icode|ECustomCheck}} appears in your policy
 
table, as otherwise the base class method will be called which will result
 
table, as otherwise the base class method will be called which will result
 
in a server panic.
 
in a server panic.
  
In this method, you can inspect the contents of the RMessage2
+
In this method, you can inspect the contents of the {{Icode|RMessage2}}
 
received – passed as the first parameter – in order to determine the correct
 
received – passed as the first parameter – in order to determine the correct
 
policy to apply. Use this whenever the policy is based on something other
 
policy to apply. Use this whenever the policy is based on something other
Line 1,094: Line 1,011:
 
you are free to form this code as you wish, we recommend you structure
 
you are free to form this code as you wish, we recommend you structure
 
this as two distinct stages:
 
this as two distinct stages:
Determine the TSecurityPolicy object to apply to this request,
+
*Determine the {{Icode|TSecurityPolicy}} object to apply to this request, based on the state of the server, session, or subsession, and the parameters passed in {{Icode|RMessage2}}, and
based on the state of the server, session, or subsession, and the
+
*Test the message against the policy so determined.
parameters passed in RMessage2, and
+
Test the message against the policy so determined.
+
  
 
A generalized implementation might look something like this:
 
A generalized implementation might look something like this:
  
CPolicyServer::TCustomResult
+
<code>
CMyPolicyServer::CustomSecurityCheckL (const RMessage2 & aMsg,
+
CPolicyServer::TCustomResult
TInt & aFailureAction, TSecurityInfo & aMissing)
+
CMyPolicyServer::CustomSecurityCheckL (const RMessage2 & aMsg,
{
+
              TInt & aFailureAction, TSecurityInfo & aMissing)
TSecurityPolicy policy;
+
{
DeterminePolicyL(aMsg, policy);
+
TSecurityPolicy policy;
if(policy.CheckPolicy(aMsg, aMissing,
+
<b>DeterminePolicyL</b>(aMsg, policy);
__PLATSEC_DIAGNOSTIC(“example custom check”))
+
if(policy.CheckPolicy(aMsg, aMissing,
return EPass;
+
  __PLATSEC_DIAGNOSTIC(“example custom check”))
SERVER IMPLEMENTATION CONSIDERATIONS 107
+
  return EPass;
else
+
else
return EFail;
+
  return EFail;
}
+
}</code>
 +
 
 +
{{Icode|DeterminePolicyL()}} can be as simple or complex as required.
  
DeterminePolicyL() can be as simple or complex as required.
 
 
Using this structure encourages a more rigorous approach to determining
 
Using this structure encourages a more rigorous approach to determining
 
the policy, rather than ad-hoc layers of logic and counter-logic being
 
the policy, rather than ad-hoc layers of logic and counter-logic being
 
tested against the message. It also aids debugging, as there is a single
 
tested against the message. It also aids debugging, as there is a single
 
place to inspect the policy being applied, in order to determine why it
 
place to inspect the policy being applied, in order to determine why it
is failing or passing when it shouldn’t. Note that using the TSecurity-
+
is failing or passing when it shouldn’t. Note that using the {{Icode|TSecurityPolicy}} class applied against the {{Icode|RMessage}} object received as shown
Policy class applied against the RMessage object received as shown
+
 
here is strongly recommended as it maximizes the amount of diagnostics
 
here is strongly recommended as it maximizes the amount of diagnostics
 
automatically generated by the server framework.
 
automatically generated by the server framework.
  
The aFailureAction and aMissing members passed into this
+
The {{Icode|aFailureAction}} and {{Icode|aMissing}} members passed into this
 
method are primarily of use if you are implementing custom failure
 
method are primarily of use if you are implementing custom failure
 
actions:
 
actions:
  
TCustomResult CustomFailureActionL(const RMessage2 &aMsg,
+
<code>
TInt aFailureAction, const TSecurityInfo &aMissing);
+
TCustomResult CustomFailureActionL(const RMessage2 &aMsg,
The CustomFailureActionL method is called by the framework
+
        TInt aFailureAction, const TSecurityInfo &aMissing);</code>
 +
 
 +
The {{Icode|CustomFailureActionL}} method is called by the framework
 
whenever a negative failure action is encountered in processing a security
 
whenever a negative failure action is encountered in processing a security
check – either in the iAction of the resolved TPolicyElement
+
check – either in the {{Icode|iAction}} of the resolved {{Icode|TPolicyElement}}
for the message, or returned in the second parameter to CustomSecurityCheckL().
+
for the message, or returned in the second parameter to {{Icode|CustomSecurityCheckL()}}.
 +
 
 
It is primarily intended to allow servers to handle security policy failures
 
It is primarily intended to allow servers to handle security policy failures
 
in a central place. This handling may be for debug logging or tracing, or
 
in a central place. This handling may be for debug logging or tracing, or
Line 1,145: Line 1,063:
 
run time.
 
run time.
  
The actual implementation of CustomFailureActionL() follows
+
The actual implementation of {{Icode|CustomFailureActionL()}} follows
much the same pattern as CustomSecurityCheckL() above, so much
+
much the same pattern as {{Icode|CustomSecurityCheckL()}} above, so much
 
of the advice expressed there holds here too.
 
of the advice expressed there holds here too.
  
 
The result of both of these custom methods is indicated in the same form:
 
The result of both of these custom methods is indicated in the same form:
they should either return one of the three enumerations from CPolicy-
+
they should either return one of the three enumerations from {{Icode|CPolicyServer::TCustomResult}}, or leave with an appropriate error code. A
Server::TCustomResult, or leave with an appropriate error code. A
+
 
leave will be propagated back to the client in the form of a standard error
 
leave will be propagated back to the client in the form of a standard error
 
completion code. The custom result codes are used as follows:
 
completion code. The custom result codes are used as follows:
CPolicyServer::EPass – The message is processed as normal;
+
*{{Icode|CPolicyServer::EPass}} – The message is processed as normal; either by passing it to the {{Icode|ServiceL()}} method of a session or, in the case of a connection message, by creating a new session.
either by passing it to the ServiceL() method of a session or, in the
+
*{{Icode|CPolicyServer::EFail}} – The message is considered to have failed its policy check; the {{Icode|aFailureAction}} parameter is used to determine what to do next (on entry this parameter is initialized to {{Icode|EFailClient}}, the common case).
case of a connection message, by creating a new session.
+
*{{Icode|CPolicyServer::EAsync}} – The derived class is responsible for further processing of the message; the policy server framework will do nothing more with it.
CPolicyServer::EFail – The message is considered to have
+
failed its policy check; the aFailureAction parameter is used
+
to determine what to do next (on entry this parameter is initialized to
+
EFailClient, the common case).
+
CPolicyServer::EAsync – The derived class is responsible for
+
further processing of the message; the policy server framework will
+
do nothing more with it.
+
  
This last case, EASync, deserves a little more discussion. If either of
+
This last case, {{Icode|EASync}}, deserves a little more discussion. If either of
 
the custom methods returns this value, it effectively removes the corresponding
 
the custom methods returns this value, it effectively removes the corresponding
 
message object from the policy server framework’s control.
 
message object from the policy server framework’s control.
 
 
It is then up to the specific server implementation to handle the message
 
It is then up to the specific server implementation to handle the message
 
as and when it sees fit, in the same way as any other message
 
as and when it sees fit, in the same way as any other message
within the ‘normal’ ServiceL() processing path of the server. That
+
within the ‘normal’ {{Icode|ServiceL()}} processing path of the server. That
 
is, the same error-handling rules apply here as to any other message
 
is, the same error-handling rules apply here as to any other message
 
that might be completed asynchronously within the server. Most often
 
that might be completed asynchronously within the server. Most often
Line 1,179: Line 1,088:
 
whatever. Once this has been done, you can insert the message back
 
whatever. Once this has been done, you can insert the message back
 
into the policy server to continue processing. This is achieved by calling
 
into the policy server to continue processing. This is achieved by calling
CPolicyServer::ProcessL() in the case of a policy pass, or
+
{{Icode|CPolicyServer::ProcessL()}} in the case of a policy pass, or
CPolicyServer::CheckFailedL() in the case of a policy check
+
{{Icode|CPolicyServer::CheckFailedL()}} in the case of a policy check
 
fail. You can also complete the message directly, for example, through
 
fail. You can also complete the message directly, for example, through
RMessagePtr2::Complete(KErrPermissionDenied), however
+
{{Icode|RMessagePtr2::Complete(KErrPermissionDenied)}}, however
 
this will skip the opportunity for any further custom failure action to
 
this will skip the opportunity for any further custom failure action to
 
be performed on the message.
 
be performed on the message.
Line 1,192: Line 1,101:
 
Symbian OS server, as you set about protecting it. It also demonstrated
 
Symbian OS server, as you set about protecting it. It also demonstrated
 
some of the fundamental principles of the security architecture:
 
some of the fundamental principles of the security architecture:
Security checks should only be made when a process boundary (that
+
*Security checks should only be made when a process boundary (that is, a trust boundary) is crossed.
is, a trust boundary) is crossed.
+
*Where servers abstract complex underlying resources they may also require a complex security policy for access to those resources, whilst clients typically have a simple connection policy.
Where servers abstract complex underlying resources they may also
+
*The kernel is the trusted intermediary between client and server, and both client and server rely on it for the implementation of their security policies.
require a complex security policy for access to those resources, whilst
+
clients typically have a simple connection policy.
+
SUMMARY 109
+
The kernel is the trusted intermediary between client and server, and
+
both client and server rely on it for the implementation of their security
+
policies.
+
  
 
The rest of the chapter then concentrated on the ways in which you
 
The rest of the chapter then concentrated on the ways in which you
Line 1,211: Line 1,114:
 
servers, looking in particular detail at the policy server framework and
 
servers, looking in particular detail at the policy server framework and
 
how to use it.
 
how to use it.
 +
 +
{{SymbianOSPlatformSecurity_Copyright}}

Latest revision as of 12:51, 17 January 2011

by Jonathan Dixon Reproduced by kind permission of John Wiley & Sons. Prev.   Contents   Next

What Is a Secure Server?

In this chapter we look in detail at writing server software. Servers demand special attention as they are, in many ways, a cornerstone of the whole platform security architecture, as we described in Chapter 2. A typical server will have one or more capabilities to enable it to perform its functions, and, in the same way as any other application software, it has a duty to moderate and protect its use of these capabilities, so that it is not unduly influenced in its usage of them by other, less trusted processes. However, servers seem to have a dichotomy here, as the very purpose of a server is to provide services to other processes, which will often have no particular trust level. Resolving this apparent dichotomy is a key part of good server design.

Symbian OS Servers

Before diving into the details of secure servers, we should recap the basics of what we mean by a ‘server’.

Symbian OS is built around a micro kernel, with system services such as the file server and communication protocol stack implemented in ‘user space’ processes. Application processes gain access to these services via a robust inter-process communication (IPC) mechanism modeled around a client–server architecture.

The primary purpose of a server is to mediate and arbitrate for multiple clients who share access to a resource. The server code executes within one process, but clients of its services may execute in multiple processes in the system.

As described in Chapter 2, the process is the unit of trust in the platform security architecture. This means that the process boundary is also the trust boundary. As client–server is by far the most commonly used generic IPC mechanism within Symbian OS, it is the most natural point at which to enforce policies regarding this crossing of the trust boundary. The fact that the client–server IPC is designed for robustness, as are the servers themselves to a large extent, is an additional benefit that helps in this task. The kernel is responsible for mediating this communication between client and server, and so must be mutually and universally trusted by these processes to carry out this role in the secure environment.

There are, however, other places where the process and trust boundaries can be crossed. Examples range from the use of shared memory chunks or global semaphores, through to message queues, properties, settings or even the file system itself. Some of these are discussed here or in subsequent chapters, however, client–server is the primary mechanism and we recommend this is what you use as your primary interface to such services. It is what we concentrate on in this chapter.

One exception to the use of client–server is device drivers. The kernel provides the means for user code to communicate with hardware device drivers via channels into the kernel executive. However, both conceptually and in practical terms there are many similarities and equivalencies between a channel to a logical device driver (LDD), and a client–server connection to a server. For this reason, a great many of the principles and procedures presented here are quite relevant to producers of LDDs. (In this context a physical device driver (PDD) is analogous to a plug-in to a server. See Chapter 6 for more on this.)

Writing Symbian OS servers was once considered something of a ‘black art’, however, recently several books, such as Symbian OS C++ for Mobile Phones [Harrison 2003] and Symbian OS Explained [Stichbury 2005], have gone a long way to demystify this process. Rather than repeat what is covered in those volumes we will build upon them, highlighting points where interface methods or development methodology may differ when working in the platform security world.

An Example Secure Server

Before getting started on the process of designing security measures for your servers, it is useful to have a look at how the key concepts work in practice. For this reason, we will walk you through a simple example of securing your first server.

As already mentioned, the role of a server is to mediate access to a shared resource, to enable one or more clients to access that resource, sequentially or concurrently – much in the same way that the server at the front of a school dinner queue mediates access to the pot of stew, and acts as a proxy to accessing it on behalf of each hungry pupil standing in line!

To keep life simple for server developers, whilst also being frugal with system resources, servers are typically implemented within a single thread of execution in a dedicated process, ensuring the server’s exclusive access to the underlying resource being shared.

Multiple clients can then access the server, via the kernel’s IPC mechanism, by establishing a session with the server. The clients can then pass messages to the server to request that certain actions be performed. The messages are delivered to the server in order, and processed by the server as appropriate. The command protocol used in these messages is defined by the server. For this reason, virtually all servers supply a dedicated client-side library (DLL) that encapsulates this protocol, and generally serves to make the application developer’s life easier.

Let’s see what this all means in practice, with this example implementation of a client-side DLL:

class RSimpleServer : public RSessionBase
  {
public:
  IMPORT_C TInt Connect();
  IMPORT_C TInt GetInformation() const;
  };

_LIT(KSimpleServerName, "com_symbian_press_testserver1");
        static _LIT_SECURITY_POLICY_S0(KSimpleServerPolicy,
        0xE1234567); // a test UID used as the server’s SID

EXPORT_C TInt RSimpleServer::Connect()
  {
  return CreateSession(KSimpleServerName, TVersion(), -1,
         EIpcSession_Unsharable, &KSimpleServerPolicy()); 
 }

EXPORT_C TInt RSimpleServer::GetUserInformation(TInt aInfoRequired,
                                                TDes8& aResult) const
  {
  return SendReceive(ESimpleServFnGetUserInfo, TIpcArgs(aRequest,
                                                        &aResult));
  }

If you are familiar with client–server development for previous versions of Symbian OS, there should be little surprise here.

The base class for the client side of a client–server connection is RSessionBase; creating a connection to a server is achieved through the call RSessionBase::CreateSession(). The version used here is the new overload added in Symbian OS 9.x, and is of the form:

IMPORT_C TInt CreateSession(const TDesC& aServer, const TVersion& aVersion,
                      const TInt aAsyncMessageSlots, TIpcSessionType aType,
              const TSecurityPolicy* aPolicy=0, TRequestStatus* aStatus=0);

The parameter we are most interested in is the pointer to an object of type TSecurityPolicy. This allows the client code to stipulate criteria for the server to which it will connect. In the example, we state that the server must be running in a process with a SID of 0xE1234567 (a test UID, see Chapter 3 for more information), to guard against server spoofing. You will see other uses of these policy objects throughout this book. Other than this, establishing the session is essentially the same as before.

Having established the session, requests can be sent to the server using RSessionBase::SendReceive(). In the example the GetUserInformation method does exactly this. This has not changed significantly, although small improvements have been made to allow more robust marshalling of arguments. Specifically, you can see the TIpcArgs class being used to provide a typed container for the arguments to the message; TIpcArgs carries flags to allow the kernel to differentiate the various argument types, and thereby ensure that the server respects them correctly. This mechanism is described in more detail in Symbian OS Internals [Sales 2005].

Now let’s see how this message is handled on the server side:

simpleserver.mmp:

TARGET simpleserver.exe
TARGETTYPE exe
UID 0 0xE1234567    // test UID as UID3 / SID
SOURCE simpleserver.cpp

simpleserver.cpp:

class CSimpleServer : public CServer2
  {
protected:
  CSession2* NewSessionL(const TVersion& aVersion,
                       const RMessage2& aMessage);
  //...
  };

static _LIT_SECURITY_POLICY_C1(KSimpleServerConnectPolicy,
                                 ECapabilityReadUserData);

CSession2* CSimpleServer::NewSessionL(const TVersion&
                   aVersion,const RMessage2& aMessage)
  {
  if(!KSimpleServerConnectPolicy().CheckPolicy(aMessage,
        __PLATSEC_DIAGNOSTIC(“CSimpleServer::NewSessionL
                           KSimpleServerConnectPolicy”))
    User::Leave(KErrPermissionDenied);

  // proceed with handling the connect request
  }

Here you see a server-side implementation of a security policy test. A simple way to protect an IPC interface is by restricting who can establish sessions with the server. After a connect message has been processed by the kernel, it reappears in user space in the server process, and is handled by the framework code – specifically the CServer2 base class. This then calls the NewSessionL() method that is implemented by the derived concrete class, in our example, CSimpleServer. Here we perform this simple policy check: a security policy object is defined that requires the client to hold the ReadUserData capability. The connect message, as indicated by the RMessage2 parameter to NewSessionL(), is tested against this policy, and if it fails, the method throws a leave exception, with the new error code KErrPermissionDenied.

If the server has a more complex security policy, it may not be appropriate to restrict access to clients at the point that the connection is established, but instead individual IPC operations might be restricted.

class CSimpleSession : public CServer2
  {
protected:
  void ServiceL(const RMessage2 &aMessage);
  void GetUserInfoL(TInt aInfoRequired, TDes8& aResult);

  //...
  };

static _LIT_SECURITY_POLICY_C1(KSimpleServerUserInfoPolicy,
                                  ECapabilityReadUserData);

CSession2* CSimpleSession::ServiceL (const RMessage2& aMessage)
  {
  switch(aMessage.Function())
    {
  case ESimpleServFn1:
    // ... handle function
    break;

  case ESimpleServFnGetUserInfo:
    if(!KSimpleServerUserInfoPolicy().CheckPolicy(aMessage,
             __PLATSEC_DIAGNOSTIC(“CSimpleSession::ServiceL
                             KSimpleServerUserInfoPolicy”))
      User::Leave(KErrPermissionDenied);
    // Process the request as normal
    RBuf8 result;
    result.CreateLC(aMessage.GetDesLengthL(1));
    result.CleanupClosePushL();
    GetUserInfoL(aMessage.Int0(), result);
    aMessage.WriteL(1, result);
    CleanupStack::PopAndDestroy();
    break;

  default:
    User::Leave(KErrNotSupported);
    }
  aMessage.Complete(KErrNone);
  }

Here we see that the server session code performs a very similar security policy check to the last example, but this time it is conditional on the value of aMessage.Function(). Here we are just picking out IPC messages that have a function value of ESimpleServFnGetUserInfo, which corresponds to the call to RSimpleServer::GetUserInformation() in the client library that we started off with. We could also take this down to the next level, the security policy might depend on the value of aInfoRequired that was passed into the client call. This is represented on the server side in the aMessage.Int0() message parameter. An additional level of switch would be required in order to achieve this.

From this quite simple example, we can pick out some important points which we will explore further in the remainder of this chapter:

  • Both the client and the server can make use of the platform security architecture to protect their IPC boundaries.
  • The client will typically have a simple security policy, and the changes to client-side code are minimal.
  • The server can have as simple or as complex a policy as its IPC protocol demands, thus the changes to the server code may be simple or complex.
  • A complex server security policy could result in a great deal of repetitive security check code in a standard form. CPolicyServer, described later, is a framework provided to help manage this complexity and minimize copy and paste bugs.
  • Security policies are tested at the process boundary: the server does not rely on code in its client library to protect its interface.
  • At any point where a trust boundary may be crossed, a check may be required.

Server Threat Modeling

Having now seen a simple example of how a server can protect its interface in practice, let’s take a step back and look at how to design security into a server. As the old adage goes, always design security in, rather than bolt it on as an after-thought.

As we recommend throughout this book, a good place to start is through a threat model.

Step 1 – Identify the Assets you Wish to Protect

In most cases, a server provides a layer of abstraction over a lower level resource, and provides access to this resource for its clients. For example, the file server provides a file system layer over the raw disk device driver interface to the hardware, and arbitrates access to this resource on behalf of all file-using clients.

The primary assets to be protected are the underlying resources and the means by which the server allows direct or indirect access to those resources. The file server must protect the raw disk driver interface in order to avoid physical disk corruption, but it must also protect assets that are derived from this underlying physical resource, the individual files themselves. The file server and the file-system drivers it hosts must work together to ensure that, whatever a client does to one file, the contents of none of the other files are altered. Additionally, in the new platform security data caging architecture (see Chapter 2), clients must only be able to read or write files to which they are permitted access. Finally, the server must ensure it does not compromise one client through the actions of another. For example, a malicious client should not be able to use the file server to write data into another client’s memory space – a duty of care, if you like.

To summarize, the assets for a typical server will consist of:

  • the access to any ‘physical’ resources it owns (including its memory, data-caged files, and even its server name)
  • any services it provides derived from or built on top of these resources
  • the client sessions, which are assets to be protected from any other sessions.

Note that the order here is intended only to aid logical and comprehensive analysis; it is not intended to indicate any prioritization of risk levels.

Step 2 – Identify the Architecture and its Interfaces

As already stated, a server provides a layer of abstraction in a generic system model. It sits on top of lower layer (physical) services, and provides access to higher layer (application) clients. The most obvious interfaces a server exposes are:

  1. The IPC interface it opens to its client, via the client–server framework.
  2. The interface to underlying services or resources it needs in order to function.
  3. Any other interfaces into the server’s process.

A server runs in a normal OS process – there may be occasions where one process would expose several server interfaces (such as ESOCK and ETEL, for instance) but often a server interface will have a dedicated process behind it. All the means of interfacing to a simple application process, as described in Chapter 4, should also be considered here – item numbers 2 and 3 in the above list are really just a reiteration of that. In this sense, a server process is the same as any other process, but with an increased attack surface due to its server interface.

Bear in mind the principle from Chapter 2, that the unit of trust in the platform security architecture is actually the operating system process, not the server. In many cases there is a one-to-one relationship between servers and processes, and this can help simplify one’s reasoning. Note that it is a practical impossibility for one Symbian OS server (that is, the RServer or RServer2 instance) to span multiple processes. However, when analyzing possible interfaces to a server, be aware that other code might execute within the same process as that server. Any interface opened by that code is also a potential interface to your server, so all interfaces into the process must be analyzed. It follows that all server interfaces into the process in question should be considered as a whole.

It may well be that you perform threat model analysis across a number of servers that co-operate to perform some common goal. Here you must look at both the external interfaces to the functional subsystem, and also analyze each internal interface, to ensure no ‘back doors’ into the subsystem are overlooked.

As with all processes, files used by the process should be identified because they form an interface with the process. In particular, do not overlook temporary files and log files, if they might be generated in a production (non-debug) build of your server.

As well as identifying the attack surface, this architectural analysis achieves an additional goal. By identifying the responsibilities of the server in question, and the interfaces that it depends on, the capability set of the server can be determined (if this is not already known).

Step 3 – Identify Threats to the Server

Using the information obtained from the previous steps, specific threats to the server can be identified and documented.

Detailed knowledge of the functionality provided by the server, and the way in which it is presented through its client API, is, naturally, very useful in driving this analysis. If the API is already well established, then working through it item by item, brainstorming on each of them, and on all other identified interfaces, is a methodical approach.

This is a good point to consider the capabilities held by the server. In many cases these might well be more numerous or more sensitive than those held by a typical application process. As such they provide a greater incentive for attack, and require a greater duty of care in identifying and addressing such threats. Threats can be brainstormed on a per-capability basis. For example, a server possessing the ReadUserData capability would need to consider if there is any way it could be manipulated or tricked into unknowingly revealing the user’s private data or leaking confidential information. Processes that do not possess this capability would have fewer threats to consider here. There is a strong practical argument for striving to limit the set of capabilities held by any one server!

Designing Server Security Measures

We will now review how a server can implement countermeasures in order to minimize the risk posed by the threats identified. Measures are targeted to address specific threats. If you find yourself getting involved in developing a complex security policy or mechanism for your server, it is often worth checking what the threats are that the mechanism is addressing, whether the threat justifies the cost, and whether there isn’t a more appropriate simpler measure. Don’t forget hidden costs, such as maintaining the solution and reduced utility to the intended audience if the security policy is impractical.

The countermeasures are split into three broad types:

  • platform security architecture – features provided ‘for free’ by the OS architecture
  • server design and implementation – aspects of good server development that can work to mitigate security threats
  • platform security mechanism – security mechanisms provided by the platform for use by the code, but which the code must be designed to utilize.

Platform Security Architecture

Loader Rules (prevent untrusted code execution in a trusted environment)

The platform security loader’s rules provide a strong level of protection against rogue or malicious code executing within servers and gaining access to server-owned resources. However, this is no protection against well-meaning but bug-infested code within your server!

Process Isolation (prevents tampering with server execution environment)

From its inception, Symbian OS was designed to support a strong model of process isolation, using the processor’s MMU to segregate physical memory address space. Indeed, the client–server architecture itself is a key part of this design, providing a robust means for processes to interact while minimizing the coupling between these processes. However, a number of the original APIs did provide means by which one process could interfere with another. These APIs had mainly been provided for the efficient implementation of specific use-cases on now outdated hardware. One example of this is the IPC v1 client–server APIs – hence the reason these are now superseded by the strongly-typed IPC v2 framework.

The above are generic features, afforded to all processes under the platform security architecture. However, they are worthy of repetition in this chapter on servers, as the server has an implicit responsibility not to undermine these mechanisms, if it wishes to receive the benefit of them! For instance, a server should be particularly careful not to accidentally execute untrusted code, which could be reached via a pointer passed by its client (or indeed any other process). One particularly insidious way that this could happen is by calling virtual functions of C++ objects supplied by the client. If the client were able to tamper with the object, it could change pointers in the vtable to cause arbitrary code to be executed. C++ objects, which may have virtual functions, must not be byte-copied from an IPC message – instead, objects should be externalized and internalized with rigorous bounds checking.

Platform Security Mechanisms

Session Connect Policy Check (detects server name spoofing)

As we illustrated in the example earlier in this chapter, when the client library code connects to a server it can specify a security policy, which the server must satisfy before the connection will be allowed to go ahead. This does not prevent a spoof server from taking the name of your server, but does provide a means through which the client library can detect this.

ProtServ for System Servers

In order to stop spoof servers from taking the names of critical system servers, the name space is partitioned into ‘normal’ and ‘protected’ parts. The protected name space is defined by all server names beginning with the ‘!’ character. Registering server objects that have names beginning with this character with the kernel is only permitted for processes possessing the ProtServ capability, all others will receive an error return code from their call to CServer2::Start(). In this way, only processes trusted with this capability are permitted to provide system services. (As a rule of thumb, a system service can be considered to be one that would cause mobile phone instability if it were to have a fatal error and terminate abnormally.)

A convenient side-effect of this is that all such system servers are going to hold at least one capability – and a system capability at that – meaning that the loader rules prevent such system servers from loading any lesser trusted DLLs (i.e. any DLL lacking the ProtServ capability).

IPC Security Policy Check

This is the most significant new security measure available to servers within the platform security architecture. Servers can specify security policies defining which clients are allowed access to which parts of their IPC interfaces.

As we saw in the opening example, servers can make policy checks against clients at any time that it is appropriate, in order to determine whether a client is authorized to perform a given operation. This check can be against the identity of the client – i.e. against its SID – or, more generally, against the capability set held by the client, or a combination of both. In addition, it is possible for servers to derive their own security framework, built upon these building blocks, should it be necessary.

CPolicyServer Framework

As we touched upon in Section 5.1.2, correctly coding, verifying and maintaining a large or complex set of security policy checks within a server is a potentially error-prone activity. For this reason, the user library provides base-classes to make life easier for the server writer; this framework is called CPolicyServer, and allows for the definition of a static policy table based on the opcode number of the function being invoked over IPC. Once mastered, this can reduce the repetition involved in setting up a server’s security policy. This is an important framework, and we will cover it in more detail in Section 5.4.2.

Data Caging

A server has the use of the private data-cage owned by the process it is running in. Once again assuming a one-to-one process–server relationship, this implies one private data-cage per server.

This is one mechanism through which the server can store non-volatile information it needs for its operation. Storing and sharing data is discussed further in Chapter 7.

There are numerous examples of servers making use of their datacaged area for storing private or confidential files – for example, the messaging server holds messages such as emails and text messages within its data-caged area.

Anonymous Objects and Secure Handle Transfer

The EKA2 kernel provides a powerful mechanism through which handles to kernel objects can be securely passed between processes, to allow secure sharing of the underlying resource. For example, a handle to an RMsgQueue may be passed from a producer process to a consumer process, and no other process will be permitted access to the kernel queue object.

There are two primary means for transferring such handles: at process start up in so-called process environment slots, and over the client–server interface.

Only handles to global objects can be transferred between processes using this mechanism. However, traditionally global objects were always accessible to any process, through an open by name operation. For this reason, unnamed (or anonymous) global objects have been introduced. To create an anonymous global object, KNullDesC should be passed as the name parameter in the appropriate Create() method.

Secure Server Design and Implementation

The following are design and implementation best practices, which, if followed, can also be considered as security measures employed by the server.

Constrain Server Responsibilities and Dependencies

A server that performs many different roles is harder to develop securely, and more of a liability should there be a security vulnerability within it. A server that has run-time dependencies on many other parts of the system, will be more fragile than one that has a constrained set of dependencies.

Architecturally, it is far simpler to consider and validate the behavior of a server that has clearly identified responsibilities, and constrained and identified dependencies, than one that performs many roles with dependency on many disparate subsystems. The simpler the design and validation of a server the better, as there will be fewer opportunities for introducing errors that could result in security vulnerabilities. Conversely, the more services a given server depends on, the more variables there are, and so the more complex the validation of its behavior will be. And the more security sensitive a server is, the more consideration should be given to this issue.

Parameter Validation

Whenever a client passes data to a server, the server must carefully validate the data before acting upon it – just as it must for any data coming in to the mobile phone, via a file or communication socket, for instance. This does not just apply to parameters supplied by the client application to the client library, but to all parameters received via the IPC message. So, even if a particular parameter is only ever provided by the client library itself in normal operation, it must still be validated in the server in case of an inept or malicious application creating its own messages to send to the server.

Particular problems are seen with asynchronous methods, as there is increased potential for the client data to go out of scope (for example, if a function with locally-scoped data terminates) between it being referenced in the message and the message being processed by the server. This is true even if the server runs at a higher priority than the client, as it may be blocked on some other operation at the moment the client thread issues the request. If a careless client releases and reuses the parameter’s memory space before the server has processed the request, then it’s probable that the server will receive an error in its attempt to read, write, or validate the data. A multi-threaded client, or one using shared memory, may exhibit similar bugs on synchronous calls too. One thread may modify the parameter’s memory contents or allocation state while the client thread is blocked, even on a synchronous request.

To combat this, the server must expect to handle errors arising during client memory access, i.e. the Read(), Write() and GetDesLength() members of RMessagePtr2; wherever possible, use the leaving overloads. Useful additions to particularly note are the new RMessagePtr2::GetDesLengthL() leaving overloads – these allow the server to safely discover the length of a client buffer, and have the standard exception framework take the burden on handling a descriptor error, without having to remember to manually check for negative error results, as is the case with the older non-leaving version.

Also, a server should never accept or use a pointer received over IPC. We have already noted that you must never call code via a pointer received in such a way, but security problems can also result from data that is pointed to being changed in unexpected ways or at unexpected times. In particular watch out for global or shared chunks – using them in a useful yet secure fashion is tricky enough not to be worth the effort except in the most bandwidth-critical applications.

Robust Error-handling Framework

Continuing from the previous point, the server-side error-handling framework should not be overlooked, as it is a very significant part of the security design of the server interface. The server error framework is built upon the standard CActive implementation of the leave/trap Symbian OS primitives. Unlike the IPC v1 CServer class, both CServer2 and CPolicyServer provide an implementation of the CActive::RunError() interface, and, if relevant, they pass the trapped error onto the specific CSession2 instance that was handling execution at the point the leave was encountered, providing it with the message that was being processed at that time via the CSession2::ServiceError() method.

This means that concrete implementations of CSession2 are able and encouraged to make maximum use of the leave framework. If required, the concrete session class can override ServiceError() with a custom implementation, although the default implementation, which simply completes the message with the leave code that was thrown, will suffice in many instances.

This means that if, during the initial (synchronous) processing of a client request, any error occurs – for example, low memory or disk resources, an invalid parameter in a deeply nested structure provided by the client, or a security policy failure – the same error handling framework will tidy up any partially allocated resources and complete the request, signaling the error condition back to the client.

Robust API Design

Getting the API design right can greatly ease the design and implementation of the security policy for that API. Here are a few points to consider:

  • Functions with a specific purpose are easier to provide a policy for than multipurpose, generic or ambiguous methods, where the context must be taken into consideration in order to decide on the appropriate policy. For example, contrast RDisk::Format() with RDisk::PerformAdministrativeOperation(TOperation) or RDisk::Extension(TExtId).
  • Having a specific purpose also helps the user of the API to create secure code, as it is clearer what the consequences of calling the method might be.
  • The primary outcome of a security policy failure in an API is an error code result, for example, KErrPermissionDenied. The error modes of APIs should be considered in general.
  • Error codes should be returned to the client application at every point where an error can legitimately arise, but not in a place where the client would not be able to handle the error (such as when canceling or closing down). Generally the error should be indicated at the point at which failure has become inevitable, but no sooner.

Server Name

We saw how a client can use a security policy to ensure they only connect to the intended server. However, it is useful to ensure that the server chooses a sensible name in the first place. Longer names reduce the chance of conflict, and, as you can see in the opening example of this chapter, using the unique part of a DNS name owned by yourself can further improve matters.

It is also worth pointing out that the server name is quite distinct from the process name. Both are stored by the kernel, the server name in a DServer object, the process name in a DProcess object. As already mentioned, one process can have many servers running in it. By default the process name is equal to the name of the EXE that was used to launch it, however, a process can rename itself to any name it wishes, as long as the new name is unique at that time. The only real value in a process name is for debugging and diagnostics. Specifically, no security check should be made based on a process’s name – use the SID instead – and it should not be displayed to the user, as it is meaningless at best and potentially downright misleading at worst.

Determining the Security Policy

We now turn to one of the most common questions in the design of secured APIs: what capability should I use to protect access to my sensitive resources?

Here are the questions you should ask yourself in order to determine the answer:

Does it need protection at all?

Use threat analysis to drive this.

Is this the right point to make a policy check?

The aim is to make the security policy test at the point where the client has requested a security sensitive operation, and repeat the check on every such operation. Making a policy check early, and caching the results for future use, can lead to so-called ‘TOCTOU’ (time of check; time of use) errors in the code. Do not rely on the client following a preferred order of function calls to the server to implement the correct policy – every security-sensitive action should consider its own policy check requirements. The one exception to this rule is where every operation on a server has the same security policy requirements. In this case, the policy check can be made once at session establishment, and need not be repeated on each subsequent operation in that session.

Another point to remember is that security policy checks against a client must only be made within the context of the server, and not within the client-side code. You cannot trust the client not to skip over or corrupt such a client-side check – this is why we refer to the process boundary as the trust boundary.

Can the API be made secure without restricting it with a capability or caller identity?

Careful API design can often reduce the threat presented by an API. For example, by designing fair brokering of access to a shared resource such as display screen or speaker, a server might let all clients have some degree of access regardless of capability. When detection and response are possible this may be preferable to prevention – for example, if the user can detect undesired sound being output, and can respond by muting the device, sound output can be left unrestricted.

What aspects of the API need restricting?

Arrange the API so that sensitive operations are separated from non-sensitive ones – for example, under different methods and server opcodes – where possible.

Do you know the identity of a single process that is, architecturally, the only client of this method?

If so, its SID may be checked instead of a capability.

Is it acceptable to use a list of known clients as the policy?

Generally this is undesirable – the capability model was created specifically to avoid the need to manage large access control lists – but in some application domains it may be acceptable.

What is the asset and what is the threat to it, which you are protecting against?

Check if there is an existing system API that is sufficiently similar to have set a precedent for how this asset is to be protected.

Is there an external or industry policy or requirement about how this asset must be protected?

There may be some regulatory or commercial circumstances that require separate cryptographic or other mechanisms to be employed to verify the authenticity or permissibility of the requester or the request.

Does this method layer over some other API?

Consider whether the new API fully exposes the lower-level API (and if so, why this duplication?) or if it does so in a limited form. Consider also whether the policy on the lower-level API is appropriate for this higher level method, and whether the threat is reduced by using this method. For example, the Symbian OS Bluetooth stack enforces LocalServices at its client API, even though it is revealing functionality that is implemented over APIs protected with the CommDD capability. On the other hand, a CSY offering direct serial port access to the Bluetooth hardware would duplicate the device driver policy of requiring CommDD. One particular aspect to consider here is whether your server might be ‘leaking’ access to the underlying sensitive API. All processes holding capabilities have a duty to mediate their use of them on behalf of other processes, and not leak access in this way.

What would be the impact of not protecting this method?

Possible consequences could be loss of users’ confidential data, unauthorized access to the network, unauthorized phone reconfiguration, and so on. This can give an initial pointer as to which capability should apply.

Will the client application be able to get the necessary capabilities?

Once you have identified a proposed capability (or set of capabilities) under which the API could be protected, you should carefully consider whether this capability is realistically going to be available to the target audience of the API. As a rough indicator, the division of user and system capabilities can give some idea, however more detailed information can be sought from the signing authority, responsible for allocating capabilities for applications on the mobile phone – for Symbian Signed the common criteria are covered in Chapter 9. If the capability is not available, then rethinking of the API might be required, or you might have hit upon an intractable incompatibility between the desired access to a resource and the risk in providing that access.

What to Do on Security Policy Failures

When you reject a client request, it is generally recommended that you do so by completing the relevant message with an appropriate error code. KErrPermissionDenied should only be used in the case where a policy check has failed, as it gives a clear indication to the application developer or user that it is a security policy failure, rather than simply an invalid argument or resource error.

Another option is to modify the behavior of the method in a minor way, but let the request proceed. This might be useful for maintaining compatibility, but it can introduce problems for the application developer in the future. If, at some future point, the circumstances change and the request is now able to pass that policy check (for example, the client acquires additional capability for an unrelated reason), then suddenly the debugged and working client will experience the alternative behavior having unintentional and potentially insecure results.

Server Implementation Considerations

Client-side Considerations

Many servers provide a client-side library to encapsulate the client–server protocol and expose the API as a set of exported methods. There are two significant things to bear in mind, if you use this architecture:

  1. As this code is running within the client process, it is futile to perform any security checks as they can easily be defeated.
  2. As this code is running within the client process, it must be sufficiently trusted to be loaded by that process.

Statement 1 is a re-iteration of what we’ve seen in the previous sections – security policies should only be checked at the point where a process boundary is crossed. Within the client library, no boundary has been crossed, so a security check is unnecessary and ineffective.

We need to consider statement 2 a little further. This asserts that the client needs to trust your code in order to use it. This is to avoid the client process being tricked into doing something unintentional through the use of untrusted code.

If the client library is distributed as a binary DLL, as most are, then the loader’s capability rules, as described in Chapter 2, will enforce this trust dependency – the client library must have at least the capability set of each client trying to load it, in order to be loaded by that client. For a general-purpose server, intended for use by a wide range of clients, this means that the client library must have a wide set of capabilities. For this reason, the client libraries on most Symbian provided servers are assigned all capabilities except Tcb. This is accepted as a trade-off between maximizing the utility of the server and constraining the code base trusted to run within the most sensitive part of the system. It is envisaged that any general-purpose third-party server would follow a similar approach, but choosing a set of capabilities for the client library in accordance with the capability set the application is targeted at.

What if the client library cannot be provided with the full set of capabilities that clients might possess? This is, in effect, an architectural ‘early warning’ indicator for the developer of the application wishing to use the less-trusted library. It means that the application is dependent on a service that is less trusted, and perhaps lower quality, than itself.

However, in some circumstances it may still be necessary to keep to this architecture. In this case, the server’s client library could be supplied in either source or static library (LIB) format to the application developer, to be included directly within their own binary. This moves the burden of responsibility for the code to the application developers, who must satisfy themselves that they trust the source and intent of the code at application build time (as opposed to security being enforced at run-time), just as they must be responsible for all instructions in their compiled binary.

Remember that if the client code is distributed in this way, then the IPC protocol is not encapsulated within a binary interface but instead is a public interface. This has consequences for compatibility reasons – for example, any changes to the IPC function numbers or server name might break compatibility with existing clients.

The other important considerations for the client library were illustrated in the opening example. To recap:

  • The RSessionBase::SendReceive() methods should be recoded in IPC v2 format – that is, using TIpcArgs in place of TAny* parameters.
  • When creating a session to the server, it is wise to add a policy check that ensures that the server is running with the expected SID.
  • For servers that operate in the ProtServ domain, the name will need to be changed to start with ‘!’. Only servers which are system critical – without which the system cannot operate – need to implement this, which is, by definition, rare for an after-market application.

A final point to consider is the documentation for the client interface – this is most importance where the client interface is to be shared with others. As a rule security objectives are met when observed behavior meets expected behavior. Clear client interface documentation is an important means through which those expectations can be established.

Server Considerations

The recommended way of adding security policy checks into a server is to use the CPolicyServer framework. This involves deriving the server’s main class from the CPolicyServer base class, instead of CServer or CServer2.

If you are migrating a server to this framework, you will see that the following changes need to be made:

  • On construction the CPolicyServer requires a parameter of type CPolicyServer::TPolicy to be supplied to it.
  • Two virtual methods, CPolicyServer::CustomSecurityCheckL() and CPolicyServer::SecurityCheckFailedL() may need to be overridden, if referenced by the TPolicy table provided in the constructor.

Policy Tables

The policy table is designed to allow a very compact representation of large or complex server policies, and to allow fast lookups on that data. In addition, it is designed to allow the table to reside in constant static data, which not only saves on construction time, but also makes it far less likely to be tampered with, as the memory page holding it will usually be marked read-only.

Alas, all these good features come with a slight penalty of legibility. A diagram (see Figure 5.1) is useful for understanding how the TPolicy table works.

Figure 5.1 platsec.jpg

Figure 5.1 TPolicy Structure

Here we see an example TPolicy instance, with the three array members – iRanges, iElementsIndex, and iElements – expanded.

The elements of the first array, iRanges, correspond to IPC function numbers. When a message is received in a session the value of RMessage2::Function() is searched for in this array, in order to determine what action the policy server framework will apply to the message – based on the index of the IPC function in the array. This is called the range index. For example, an IPC message with function value 8 will cause the value 8 to be searched for in this first array. Here the 3rd index contains the value 8, but as indexes are counted starting at 0 this yields a range index of 2. If the exact value is not in the array, then the closest value below it is taken. So if IPC function 15 arrives at this table, then a range index of 5 is chosen, as this is the index at which the nearest lower value to 15 – i.e. 12 – is held. To support this the array must be held in sorted numerical order and this also conveniently enables a fast search to be performed. Also note that the table must start with a 0 element – negative entries are not allowed (negative IPC functions are reserved for use by the server framework itself).

The range index is used as an index into the second array, iElementsIndex, to determine the policy index. So IPC function 9 corresponds to range index 3, which corresponds to a policy index of 2.

Following the same pattern, the policy index is then used as the index into third and final array, iElements. This yields the TPolicyElement that should be applied to this particular IPC function. Each object of this type contains both the security policy that should be applied (encoded in the iPolicy member as a TSecurityPolicy, seen in our opening example) and the failure action to take in the case where the policy check fails (encoded in the iAction member).

Using the example of IPC function 8, with policy index of 1, we see the policy that will be applied is labeled C2 – this represents an instance of a security policy demanding that the client hold two specific capabilities. In our example code this is initialized with the _INIT_SECURITY_POLICY_C2 macro. If a client invoking this IPC function does not hold both specified capabilities, then the failure action EFailClient (from the CPolicyServer::TFailureAction enumeration) indicates that the message must be completed with the KErrPermissionDenied error code.

This policy table is described in code as follows:

const TUint myRangeCount = 8;
const TInt myRanges[myRangeCount] =
  {
  0,  //range is 0-1 inclusive
  2,  //range is 2-7 inclusive
  8,  //range is 8 only
  9,  //range is 9 only
  10, //range is 10-11 inclusive
  12, //range is 12-41 inclusive
  42, //range is 42-44 inclusive
  45, //range is 45-KMaxTInt inclusive
  };

const TUint8 myElementsIndex[myRangeCount] =
  {
  CPolicyServer::EAlwaysPass,   //IPC 0 -
  0,                            //IPC 2 -
  1,                            //IPC 8 -
  2,                            //IPC 9 -
  CPolicyServer::ENotSupported, //IPC 10 -
  2,                            //IPC 12 -
  CPolicyServer::ECustomCheck,  //IPC 42 -
  CPolicyServer::ENotSupported, //IPC 45 - KMaxTInt
  };

const CPolicyServer::TPolicyElement myElements[] =
  {
  {_INIT_SECURITY_POLICY_C1(KMyCap1), -1},    //IPC 2 - 7
  {_INIT_SECURITY_POLICY_C2(KMyCap2A, KMyCap2B),
                CPolicyServer::EFailClient},  //IPC 8
  {_INIT_SECURITY_POLICY_S1(KMySID, KMyCap3),
                CPolicyServer::EFailClient},  //IPC 9, 12-41
  {_INIT_SECURITY_POLICY_C1(KMyConnectCap),
                CPolicyServer::EPanicClient}, //Connect
  }

const CPolicyServer::TPolicy myPolicy =
  {
  3,           // Connect messages use policy index 3
  myRangeCount,
  myRanges,
  myElementsIndex,
  myElements,
  }

The process followed to look up the appropriate TPolicyElement could be confusing so, to summarize: The IPC number is first used to do a binary search in iRanges; the index of the result (the range index) is then used as an index into iElementsIndex, and the result of that (the policy index) is used as an index into iElements.

When a policy check fails, i.e. whenever the CPolicyServer::EFailClient result is encountered, diagnostics can be generated in the emulator to aid debugging, as already mentioned in Chapter 3. Here’s an example:

*PlatSec* ERROR - Capability check failed - A Message (function number=0x000000cf) from Thread
helloworld[10008ace]0001::HelloWorld, sent to Server !CntLockServer, was checked by Thread
CNTSRV.EXE[10003a73]0001::!CntLockServer and was found to be missing the capabilities: WriteUserData .
Additional diagnostic message: Checked by CPolicyServer::RunL

In this example diagnostic you can see:

  • the message function number: 0x000000cf
  • the client process and thread that sent the request: helloworld [10008ace]0001::HelloWorld
  • the name of the server that received the request: !CntLockServer
  • the name of the process and thread in which the server is hosted: CNTSRV.EXE[10003a73]0001::!CntLockServer (note the exclamation mark on the thread name here is coincidental, and not enforced by the ProtServ capability)
  • the reason that the TSecurityPolicy check failed: a lack of WriteUserData
  • the additional diagnostic information, in this case indicating that the security policy check was made from within the CPolicyServer::RunL framework function.

Special Cases

There are some special cases to consider in the framework. Firstly, connect messages do not go through the first two array lookups, as connect messages do not have normal IPC function numbers. Instead, the policy index for a connect request is taken directly from the iOnConnect member of TPolicy itself. This is then looked up in the third array as normal.

Secondly, if any policy index found by lookup in the iElementsIndex array or from iOnConnect is a value from the CPolicyServer::TSpecialCase enumeration, then no policy element lookup occurs, but instead the policy is inferred as follows:

  • CPolicyServer::EAlwaysPass – the IPC function is allowed to go ahead with no specific policy check against the client; that is, any client that can establish a session may call this method
  • CPolicyServer::ENotSupported – the IPC message processing is completed immediately with KErrNotSupported. This should be used as the final element in iElementsIndex, and also to fill any other ‘holes’ in the IPC function space – for example, where deprecated functions have been removed, or gaps left for compatibility reasons. If any of these opcodes are used by new API methods in the future, the policy table must be considered and updated to allow the policy check to pass – for this reason having a default position of CPolicyServer::ENotSupported is much safer than CPolicyServer::EAlwaysPass. Note there is no CPolicyServer::EAlwaysFail enumeration – you should instead use CPolicyServer::ENotSupported for any opcode that must always fail.
  • CPolicyServer::ECustomCheck – the security policy is not based on IPC function alone; run-time consideration is required to determine the policy to apply. A call to CPolicyServer::CustomSecurityCheckL(), discussed below, will be made in response to this.

Finally, if the failure action – identified by the iAction member of CPolicyServer::TPolicyElement – is negative then it means special failure processing should be performed instead of just returning a simple error code to the client (the recommended approach) or a client panic. A call to CPolicyServer::CustomFailureActionL() will be made to allow this to occur.

Custom Checks and Failure Actions

If the policy index equals CPolicyServer::ECustomCheck then CustomSecurityCheckL() is called. If a failure action is negative, the CustomFailureActionL() is called. Potentially one IPC message could result in calls to both these methods.

TCustomResult CustomSecurityCheckL(const RMessage2 &aMsg,
          TInt &aFailureAction, TSecurityInfo &aMissing);

You must override this method if ECustomCheck appears in your policy table, as otherwise the base class method will be called which will result in a server panic.

In this method, you can inspect the contents of the RMessage2 received – passed as the first parameter – in order to determine the correct policy to apply. Use this whenever the policy is based on something other than the IPC function number alone, such as the parameters being passed into the method or the current state of the session or server objects. While you are free to form this code as you wish, we recommend you structure this as two distinct stages:

  • Determine the TSecurityPolicy object to apply to this request, based on the state of the server, session, or subsession, and the parameters passed in RMessage2, and
  • Test the message against the policy so determined.

A generalized implementation might look something like this:

CPolicyServer::TCustomResult
CMyPolicyServer::CustomSecurityCheckL (const RMessage2 & aMsg,
              TInt & aFailureAction, TSecurityInfo & aMissing)
{
TSecurityPolicy policy;
DeterminePolicyL(aMsg, policy);
if(policy.CheckPolicy(aMsg, aMissing,
  __PLATSEC_DIAGNOSTIC(“example custom check”))
  return EPass;
else
  return EFail;
}

DeterminePolicyL() can be as simple or complex as required.

Using this structure encourages a more rigorous approach to determining the policy, rather than ad-hoc layers of logic and counter-logic being tested against the message. It also aids debugging, as there is a single place to inspect the policy being applied, in order to determine why it is failing or passing when it shouldn’t. Note that using the TSecurityPolicy class applied against the RMessage object received as shown here is strongly recommended as it maximizes the amount of diagnostics automatically generated by the server framework.

The aFailureAction and aMissing members passed into this method are primarily of use if you are implementing custom failure actions:

TCustomResult CustomFailureActionL(const RMessage2 &aMsg,
       TInt aFailureAction, const TSecurityInfo &aMissing);

The CustomFailureActionL method is called by the framework whenever a negative failure action is encountered in processing a security check – either in the iAction of the resolved TPolicyElement for the message, or returned in the second parameter to CustomSecurityCheckL().

It is primarily intended to allow servers to handle security policy failures in a central place. This handling may be for debug logging or tracing, or security audit purposes, or to aid step-through debugging. It may also be used to apply a final override to the outcome of the security check. Such an override is primarily useful for debugging purposes – but do ensure that any such debug-disable is not present in release builds! Also in some limited circumstances this method might be useful for offering the user an opportunity to override the policy failure in a controlled fashion, at run time.

The actual implementation of CustomFailureActionL() follows much the same pattern as CustomSecurityCheckL() above, so much of the advice expressed there holds here too.

The result of both of these custom methods is indicated in the same form: they should either return one of the three enumerations from CPolicyServer::TCustomResult, or leave with an appropriate error code. A leave will be propagated back to the client in the form of a standard error completion code. The custom result codes are used as follows:

  • CPolicyServer::EPass – The message is processed as normal; either by passing it to the ServiceL() method of a session or, in the case of a connection message, by creating a new session.
  • CPolicyServer::EFail – The message is considered to have failed its policy check; the aFailureAction parameter is used to determine what to do next (on entry this parameter is initialized to EFailClient, the common case).
  • CPolicyServer::EAsync – The derived class is responsible for further processing of the message; the policy server framework will do nothing more with it.

This last case, EASync, deserves a little more discussion. If either of the custom methods returns this value, it effectively removes the corresponding message object from the policy server framework’s control. It is then up to the specific server implementation to handle the message as and when it sees fit, in the same way as any other message within the ‘normal’ ServiceL() processing path of the server. That is, the same error-handling rules apply here as to any other message that might be completed asynchronously within the server. Most often this method is used where the policy to be applied requires an asynchronous operation to be performed before it can be determined – this might be fetching some data from another server, prompting the user, or whatever. Once this has been done, you can insert the message back into the policy server to continue processing. This is achieved by calling CPolicyServer::ProcessL() in the case of a policy pass, or CPolicyServer::CheckFailedL() in the case of a policy check fail. You can also complete the message directly, for example, through RMessagePtr2::Complete(KErrPermissionDenied), however this will skip the opportunity for any further custom failure action to be performed on the message.

Summary

In this chapter we have seen how to go about designing security into your client–server implementation. The simple example at the start gave a taste of the new machinery that is available within the context of a Symbian OS server, as you set about protecting it. It also demonstrated some of the fundamental principles of the security architecture:

  • Security checks should only be made when a process boundary (that is, a trust boundary) is crossed.
  • Where servers abstract complex underlying resources they may also require a complex security policy for access to those resources, whilst clients typically have a simple connection policy.
  • The kernel is the trusted intermediary between client and server, and both client and server rely on it for the implementation of their security policies.

The rest of the chapter then concentrated on the ways in which you can use these basic mechanisms to maximum benefit. First we looked at the threats that are specific to a server, and what you should consider when analyzing the threat model of a server. Next, we saw the various security measures available within the operating system, frameworks that are available for use within servers, and how to design usage of these into your server. Finally, we went though some notes on implementing servers, looking in particular detail at the policy server framework and how to use it.


Copyright © 2006, Symbian Ltd.