// Copyright (c) 2006-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of the License "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
// Provides the debug security server server implementation.
//
//
/**
@file
@internalTechnology
@released
*/
#include <e32base.h>
#include <e32base_private.h>
#include <rm_debug_api.h>
#include "c_process_pair.h"
#include "c_security_svr_session.h"
#include "c_security_svr_server.h"
#include "rm_debug_logging.h"
using namespace Debug;
/**
Server constructor, sessions are created as ESharableSessions, meaning that
each session will be used by at most one debug agent
*/
CSecuritySvrServer::CSecuritySvrServer(CActive::TPriority aActiveObjectPriority)
: CServer2(aActiveObjectPriority, ESharableSessions),
iSessionCount(0),
iShutdown()
{
LOG_MSG("CSecuritySvrServer::CSecuritySvrServer()\n");
}
/**
Standard implementation
@return pointer to new CSecuritySvrServer object
*/
CSecuritySvrServer* CSecuritySvrServer::NewLC()
{
LOG_MSG("CSecuritySvrServer::NewLC()\n");
CSecuritySvrServer* self=new(ELeave) CSecuritySvrServer(EPriorityStandard);
CleanupStack::PushL(self);
self->ConstructL();
return self;
}
/**
Server destructor, performs cleanup for the server
*/
CSecuritySvrServer::~CSecuritySvrServer()
{
LOG_MSG("CSecuritySvrServer::~CSecuritySvrServer()\n");
// stop the kernel side driver
iKernelDriver.Close();
User::FreeLogicalDevice(KDebugDriverName);
//deallocate both the debug maps
iPassiveDebugMap.ResetAndDestroy();
iActiveDebugMap.ResetAndDestroy();
}
/**
Starts the server and constructs and starts the servers shutdown timer
*/
void CSecuritySvrServer::ConstructL()
{
LOG_MSG("CSecuritySvrServer::ConstructL()");
StartL(KSecurityServerName);
iShutdown.ConstructL();
iShutdown.Start();
//load the kernel driver
TInt err = User::LoadLogicalDevice(KDebugDriverFileName);
if(! ((KErrNone == err) || (KErrAlreadyExists == err)))
{
User::Leave(err);
}
//create an information object for initialising the driver
TRM_DebugDriverInfo driverInfo;
driverInfo.iUserLibraryEnd = 0;
User::LeaveIfError(iKernelDriver.Open(driverInfo));
}
/**
Creates a new session with the DSS. A version check is done to ensure that an
up to date version of the DSS is available (according to the DA's needs).
The device driver is loaded if necessary and a session with the server and a
handle to the driver opened.
@param aRequiredVersion the minimal version of the DSS required by the DA
@return a pointer to the new sever session, or NULL if any of the
initialisation process failed
*/
CSession2* CSecuritySvrServer::NewSessionL(const TVersion& aRequiredVersion, const RMessage2& aMessage) const
//
// Session constructor
//
{
LOG_ARGS("version=%d.%d.%d", aRequiredVersion.iMajor, aRequiredVersion.iMinor, aRequiredVersion.iBuild);
//assert compatible version
TVersion currentVersion(KDebugServMajorVersionNumber, KDebugServMinorVersionNumber, KDebugServPatchVersionNumber);
if(!User::QueryVersionSupported(currentVersion, aRequiredVersion))
{
LOG_MSG("Requested version not compatible with this version. Asked for %d.%d.%d but this is %d.%d.%d", aRequiredVersion.iMajor, aRequiredVersion.iMinor, aRequiredVersion.iBuild, KDebugServMajorVersionNumber, KDebugServMinorVersionNumber, KDebugServPatchVersionNumber);
User::Leave(KErrNotSupported);
}
//get the debug agent's process id
RThread clientThread;
User::LeaveIfError(aMessage.Client(clientThread));
CleanupClosePushL(clientThread);
RProcess clientProcess;
User::LeaveIfError(clientThread.Process(clientProcess));
CleanupStack::PopAndDestroy(&clientThread);
TProcessId processId = clientProcess.Id();
clientProcess.Close();
//create session
LOG_MSG("About to call new(ELeave) CSecuritySvrSession()");
CSecuritySvrSession* servSession = new(ELeave) CSecuritySvrSession(processId);
CleanupStack::PushL(servSession);
servSession->ConstructL();
CleanupStack::Pop(servSession);
return servSession;
}
/**
Manages requests from debug agents to attach to target debug processes
Given the debug agent process ID and the target process name:
(1) checks whether the pair is already in either of the debug maps, if so
then returns KErrAlreadyExists
(2) if aPassive == ETrue then just add the pair to the passive map and return
whatever the return value of the array write was
(3) if aPassive == EFalse then check whether the target debug process is
already reserved by another debug agent. If it is then return KErrInUse,
otherwise add the pair to the active debug map and return the status
value of the array write.
@param aTargetProcessName original FileName of the process to attach to
@param aDebugAgentProcessId process ID of the debug agent
@param aPassive ETrue if wish to attach passively, EFalse otherwise
@return KErrNone if successfully attached, otherwise another system wide error
code as above
*/
TInt CSecuritySvrServer::AttachProcessL(const TDesC& aTargetProcessName, const TProcessId aDebugAgentProcessId, const TBool aPassive)
{
//store the pair of values
LOG_MSG3("CSecuritySvrServer::AttachProcessL() 0x%lx aPassive=%d", aDebugAgentProcessId.Id(), aPassive);
CProcessPair *processPair = CProcessPair::NewL(aTargetProcessName, aDebugAgentProcessId);
if(processPair == NULL)
return KErrNoMemory;
//check whether the pair already exists in the active debug map
for(TInt i=0; i<iActiveDebugMap.Count(); i++)
{
// Note that the equality is of the debug agent id and the proc name
if(*processPair == *(iActiveDebugMap[i]))
{
//process already exists
LOG_MSG( " AttachProcessL() error : KErrAlreadyExists in active map\n" );
delete processPair;
return KErrAlreadyExists;
}
}
//check whether the pair already exists in the passive map
for(TInt i=0; i<iPassiveDebugMap.Count(); i++)
{
if(*processPair == *(iPassiveDebugMap[i]))
{
//process already exists
LOG_MSG( " AttachProcessL() error : KErrAlreadyExists in passive map\n" );
delete processPair;
return KErrAlreadyExists;
}
}
if(aPassive)
{
//just add the pair and return
TInt err = iPassiveDebugMap.Append(processPair);
if(err != KErrNone)
{
// couldn't add pair for some unknown reason, so delete the pair
LOG_MSG2( " AttachProcessL() error %d appending passive process pair", err );
delete processPair;
}
return err;
}
else
{
//check whether the process Id has already been reserved
for(TInt i=0; i<iActiveDebugMap.Count(); i++)
{
// Now check if this is already debugged, but only if its for a particular process.
// We can have several entries from AttachAll calls, and its ok to add them
if( (!processPair->ProcessNameMatches(_L("*"))) &&
(processPair->ProcessNameMatches(*(iActiveDebugMap[i])) ) )
{
//process already being debugged
LOG_MSG( " AttachProcessL() error : process already being debugged" );
delete processPair;
return KErrInUse;
}
}
//try to add the pair.
TInt err = iActiveDebugMap.Append(processPair);
if(err != KErrNone)
{
// couldn't add pair for some unknown reason, so delete the pair
LOG_MSG2( " AttachProcessL() error %d inserting active process pair", err );
delete processPair;
}
return err;
}
}
/*
Detach from debugging the specified process
@param aTargetProcessName name of the process to detach from
@param aDebugAgentProcessId process ID of the debug agent
@return KErrNone if successfully detached, KErrNotFound if an attempt is made
to detach from a process which the debug agent hasn't previously attached to
*/
TInt CSecuritySvrServer::DetachProcess(const TDesC& aTargetProcessName, const TProcessId aDebugAgentProcessId)
{
LOG_MSG2( "CSecuritySvrServer::DetachProcess() for agent with id 0x%lx", aDebugAgentProcessId.Id() );
//check whether the pair is in the active debug map
for(TInt i=0; i<iActiveDebugMap.Count(); i++)
{
if(iActiveDebugMap[i]->Equals(aTargetProcessName, aDebugAgentProcessId))
{
//remove the process pair from the active debug map
delete iActiveDebugMap[i];
iActiveDebugMap.Remove(i);
return KErrNone;
}
}
//check whether the pair is in the passive debug map
for(TInt i=0; i<iPassiveDebugMap.Count(); i++)
{
if(iPassiveDebugMap[i]->Equals(aTargetProcessName, aDebugAgentProcessId))
{
//remove the process pair from the active debug map
delete iPassiveDebugMap[i];
iPassiveDebugMap.Remove(i);
return KErrNone;
}
}
//process pair wasn't in either map
return KErrNotFound;
}
/**
Given a debug agent process ID, removes all references to that debug agent
from the debug maps
@param aMessage message from the debug agent
@return returns KErrNone if successful, another system wide error code otherwise
*/
void CSecuritySvrServer::DetachAllProcesses(const TProcessId aDebugAgentProcessId)
{
//check whether the debug agent process ID is in the active debug map
for(TInt i=iActiveDebugMap.Count()-1; i>=0; i--)
{
if(iActiveDebugMap[i]->ProcessIdMatches(aDebugAgentProcessId))
{
//remove the process pair from the active debug map
delete iActiveDebugMap[i];
iActiveDebugMap.Remove(i);
}
}
//check whether the debug agent process ID is in the passive debug map
for(TInt i=iPassiveDebugMap.Count()-1; i>=0; i--)
{
if(iPassiveDebugMap[i]->ProcessIdMatches(aDebugAgentProcessId))
{
//remove the process pair from the passive debug map
delete iPassiveDebugMap[i];
iPassiveDebugMap.Remove(i);
}
}
}
/*
Check whether the specified debug agent is attaced to the specfied target
process.
@param aTargetThreadId thread ID of a thread in the target process
@param aMessage a message which originates with the debug agent
@param aPassive if EFalse then checks whether the debug agent is the active
debugger of the target process. If ETrue then checks whether the debug
agent is attached to the target process, irrespective of whether it is
attached passively or actively
@return ETrue if attached, EFalse otherwise
*/
TBool CSecuritySvrServer::CheckAttached(const TThreadId aTargetThreadId, const RMessage2& aMessage, const TBool aPassive)
{
//get a handle to the target thread
RThread targetThread;
TInt err = targetThread.Open(aTargetThreadId);
if(err != KErrNone)
{
return EFalse;
}
//get a handle to the target process
RProcess targetProcess;
err = targetThread.Process(targetProcess);
//finshed with the thread handle so close it
targetThread.Close();
if(err != KErrNone)
return EFalse;
//get the target process' file name
TFileName targetFileName = targetProcess.FileName();
// Tamperproofing. Ensure that the debug agent really has a superset
// of the target process PlatSec capabilities, as authorised
// by an OEM Debug Token (if any)
TSecurityInfo targetSecInfo(targetProcess);
// Now compare the capabilities, to ensure the DebugAgent has been authorised with
// sufficient capabilities from its OEM Debug token
CSecuritySvrSession* session = (CSecuritySvrSession*)aMessage.Session();
// Presume we need to check the target process is debuggable unless a valid OEM Debug token in effect?
if (!OEMTokenPermitsDebugL(session->GetOEMDebugCapabilities(), targetSecInfo.iCaps) )
{
// No debug token therefore check if the process is debuggable
err = iKernelDriver.IsDebuggable(targetProcess.Id());
}
//finished with the process handle so close it
targetProcess.Close();
if (err != KErrNone)
{
// The process was not marked as debuggable by the loader, and the OEM
// debug token did not override the lack of a debuggable bit.
// The process was not marked as debuggable by the loader
return EFalse;
}
return CheckAttachedProcess(targetFileName, aMessage, aPassive);
}
/*
Check whether the specified debug agent is attaced to the specfied target
process.
@param aTargetProcessId process ID of the target process
@param aMessage a message which originates with the debug agent
@param aPassive if EFalse then checks whether the debug agent is the active
debugger of the target process. If ETrue then checks whether the debug
agent is attached to the target process, irrespective of whether it is
attached passively or actively
@return ETrue if attached, EFalse otherwise
*/
TBool CSecuritySvrServer::CheckAttached(const TProcessId aTargetProcessId, const RMessage2& aMessage, const TBool aPassive)
{
//get a handle to the target process
RProcess targetProcess;
TInt err =targetProcess.Open(aTargetProcessId);
if(err != KErrNone)
{
return EFalse;
}
//get the target process' file name
TFileName targetFileName = targetProcess.FileName();
// Tamperproofing. Ensure that the debug agent really has a superset
// of the target process PlatSec capabilities, as authorised
// by an OEM Debug Token (if any)
TSecurityInfo targetSecInfo(targetProcess);
// Now compare the capabilities, to ensure the DebugAgent has been authorised with
// sufficient capabilities from its OEM Debug token
CSecuritySvrSession* session = (CSecuritySvrSession*)aMessage.Session();
// Presume we need to check the target process is debuggable unless a valid OEM Debug token in effect?
if ( !OEMTokenPermitsDebugL(session->GetOEMDebugCapabilities(), targetSecInfo.iCaps) )
{
// No debug token therefore check if the process is debuggable
err = iKernelDriver.IsDebuggable(targetProcess.Id());
}
//finished with the process handle so close it
targetProcess.Close();
if (err != KErrNone)
{
return EFalse;
}
return CheckAttachedProcess(targetFileName, aMessage, aPassive);
}
/*
Check whether the specified debug agent is attaced to the specfied target
process.
@param aTargetProcessName
@param aMessage a message which originates with the debug agent
@return ETrue if attached, EFalse otherwise
*/
TBool CSecuritySvrServer::CheckAttachedProcess(const TDesC& aTargetProcessName, const RMessage2& aMessage, const TBool aPassive) const
{
//get the debug agent's process id
TProcessId clientProcessId = 0;
TInt err = GetProcessIdFromMessage(clientProcessId, aMessage);
if(err != KErrNone)
return EFalse;
//check permissions
if(aPassive)
return IsDebugger(aTargetProcessName, clientProcessId);
else
return IsActiveDebugger(aTargetProcessName, clientProcessId);
}
/**
Tests whether the debug agent is attached actively to the target debug process
@param aTargetProcessName target debug process' FileName
@param aDebugAgentProcessId process ID of a debug agent
@return ETrue if the specified debug agent is actively attached to the
specified target debug process, EFalse otherwise
*/
TBool CSecuritySvrServer::IsActiveDebugger(const TDesC& aTargetProcessName, const TProcessId aDebugAgentProcessId) const
{
//check whether the pair is in the active debug map
for(TInt i=0; i<iActiveDebugMap.Count(); i++)
{
// If both match, which can include aTargetProcessName being "*"
if(iActiveDebugMap[i]->Equals(aTargetProcessName, aDebugAgentProcessId))
{
LOG_MSG2( "CSecuritySvrServer::IsActiveDebugger() for agent id 0x%lx matches name and pid",
aDebugAgentProcessId.Id() );
return ETrue;
}
// if the pid matches and the name in the map is "*", indicating that
// this is an attach all agent, and thus debugging
if( iActiveDebugMap[i]->ProcessIdMatches(aDebugAgentProcessId) &&
iActiveDebugMap[i]->ProcessNameMatches(_L("*")) )
{
LOG_MSG2( "CSecuritySvrServer::IsActiveDebugger() for AttachAll agent id 0x%lx",
aDebugAgentProcessId.Id() );
return ETrue;
}
}
//not found so return false
return EFalse;
}
/**
Tests whether the target process is being debugged
@param aTargetProcessName target process' FileName
@param aPassive indicates whether to check for the process being actively debugged,
or passively debugged
@return ETrue if the specified target process is being debugged,
EFalse otherwise
*/
TBool CSecuritySvrServer::IsDebugged(const TDesC& aTargetProcessName, const TBool aPassive) const
{
//get a reference to the appropriate list
const RPointerArray<CProcessPair>& map = (aPassive) ? iPassiveDebugMap : iActiveDebugMap;
//iterate through the map trying to match the aTargetProcessName
for(TInt i=0; i<map.Count(); i++)
{
if(map[i]->ProcessNameMatches(aTargetProcessName))
{
return ETrue;
}
}
return EFalse;
}
/**
Tests whether the debug agent is attached to the target debug process
@param aTargetProcessName target debug process' FileName
@param aDebugAgentProcessId process ID of a debug agent
@return ETrue if the specified debug agent is attached to the
specified target debug process (regardless of whether it is attached
passively or actively), EFalse otherwise
*/
TBool CSecuritySvrServer::IsDebugger(const TDesC& aTargetProcessName, const TProcessId aDebugAgentProcessId) const
{
//check whether the pair is in the active debug map
if(IsActiveDebugger(aTargetProcessName, aDebugAgentProcessId))
return ETrue;
//check whether the pair is in the passive debug map
for(TInt i=0; i<iPassiveDebugMap.Count(); i++)
{
if(iPassiveDebugMap[i]->Equals(aTargetProcessName, aDebugAgentProcessId))
return ETrue;
}
//not found so return false
return EFalse;
}
/**
Decrements the server's count of how many sessions are connected to it and
starts the shutdown timer if there are no sessions connected
*/
void CSecuritySvrServer::SessionClosed()
{
if(--iSessionCount < 1)
{
iShutdown.Start();
}
}
/**
Increments the servers count of how many sessions are connected to it and
cancels the shutdown timer if it is running
*/
void CSecuritySvrServer::SessionOpened()
{
iSessionCount++;
iShutdown.Cancel();
}
/**
Get the process id of the thread which sent aMessage
@param aProcessId process id of the thread which sent aMessage
@param aMessage message object sent by thread
@return KErrNone if aProcessId could be set, or one of the system wide error codes if not
*/
TInt CSecuritySvrServer::GetProcessIdFromMessage(TProcessId& aProcessId, const RMessage2& aMessage) const
{
//get the debug agent's thread
RThread clientThread;
TInt err = aMessage.Client(clientThread);
if(err != KErrNone)
{
return err;
}
//get the debug agent's process
RProcess clientProcess;
err = clientThread.Process(clientProcess);
//finished with the thread handle so close it
clientThread.Close();
//check if there was an error from getting the process
if(err != KErrNone)
{
return err;
}
//get the debug agent's process id
aProcessId = clientProcess.Id();
//finished with the process handle so close it
clientProcess.Close();
return KErrNone;
}
/**
Helper function which determines whether the capabilities of the
OEM Token are sufficient to permit debug of an application.
Normally, we use the AllFiles capability as a proxy which
means a Debug Agent can debug non-debuggable executables,
provided it has a superset of the capabilities of the executable
to be debugged.
However, this causes the problem that all OEM Debug Tokens implicitly
give the power to debug an AllFiles executable, even if all that
is really needed is the power to debug an app with no capabilities,
or capabilities other than AllFiles.
To address this, we treat the AllFiles capability in a special way.
The AllFiles capability in a token is taken to mean that an OEM has
signed the token, and hence can debug other executables. But this does
not inclue the ability to debug an AllFiles executable. To debug an AllFiles
executable, the token must also have TCB.
@param aTokenCaps - The PlatSec capabilities of a token
@param aTargetCaps - The PlatSec capabilities of a target app to be debugged
@return ETrue if authorised for debugging, EFalse otherwise.
@leave Any system error code.
*/
TBool CSecuritySvrServer::OEMTokenPermitsDebugL(const TCapabilitySet aTokenCaps, const TCapabilitySet aTargetCaps)
{
LOG_MSG("CSecuritySvrSession::OEMTokenPermitsDebugL\n");
// Is the token valid - i.e. does it have AllFiles.
if ( !aTokenCaps.HasCapability(ECapabilityAllFiles) )
{
// Token is not valid, as it does not have AllFiles.
LOG_MSG("CSecuritySvrSession::OEMTokenPermitsDebugL - Token does not have AllFiles\n");
return EFalse;
}
// Token MUST have a strict superset of capabilities
if ( !aTokenCaps.HasCapabilities(aTargetCaps) )
{
// Token does not have at least all the capabilities of the target
LOG_MSG("CSecuritySvrSession::OEMTokenPermitsDebugL - Token does not have superset of target capabilities\n");
return EFalse;
}
// Special case: If the target has AllFiles, the Token must have TCB
if ( aTargetCaps.HasCapability(ECapabilityAllFiles) )
{
// Target has AllFiles, so does the Token have TCB?
if ( !aTokenCaps.HasCapability(ECapabilityTCB) )
{
// Token does not have TCB.
LOG_MSG("CSecuritySvrSession::OEMTokenPermitsDebugL - Token does not have TCB when target has AllFiles\n");
return EFalse;
}
}
// If we have passed all the above tests, the token permits debug
return ETrue;
}
/**
* This looks at a debug tokens capability and ensures it is sufficient
* to provide access to the flash partition
* @param aTokenCaps Capabilties of the Token
* @return TBool Whether or not flash access is permitted
*/
TBool CSecuritySvrServer::OEMTokenPermitsFlashAccessL(const TCapabilitySet aTokenCaps)
{
//Must have TCB to access flash
return aTokenCaps.HasCapability(ECapabilityTCB);
}
//eof