|
||
This document provides details of the API and functional changes from 7.0s to Symbian OS as a result of platform security for the User Library.
The Kernel User Library (EUSER) API has been changed to both provide support for the capability model, and also to implement the changes required to secure the kernel. Some of the changes will only affect existing Symbian OS code as it is modified to implement a security policy of its own, others will require code changes due to source incompatibilities, some may require re-implementation of certain functionality using the new EUSER APIs, and others will just require the process to have a capability assigned.
Device drivers will also be changed to check the capabilities of the process that opens the channel. System servers that directly use a device driver will need to have the associated capability in order to operate in a secure platform. The mapping from capabilities to device access will be quite simple, reflecting the current use of device drivers in the OS.
API changes are categorised class by class, and document additions, removals, migrations (to another class) and behaviour changes/restrictions. The prototype of the item(s) in question is followed by a brief description of why the change is required.
The biggest overhaul is the changes to RThread
, which becomes a significantly less capable class as it cannot be used for inter-process activity anymore. All such interaction
is now managed by the new Version 2 IPC framework, in particular by RMessage2
[Ptr
]. Combined with the changes to the inter-process copy function, this will require reworking almost all servers to use the
new APIs.
You should refer to the document “How To Migrate to Client/Server V2 APIs” for more details of the changes required to migrate
a server implementation to the Version 2 APIs. These V2 APIs have been usually implemented using classes which have the same
names as those used for Version 1, except that they are suffixed with a '2'. E.g. CServer2
implements the V2 APIs which correspond to the V1 functionality provided by CServer
.
With the loss of capability in RThread
, there is little reason to migrate the old RMessage::iClient
member to RMessage2
. Additionally, using the message handle to manipulate the client thread allows RMessagePtr2
to implement the majority of the server API, with RMessage2
just supplying the extra message data. Most asynchronous requests would no longer need to retain the full message object,
merely the handle.
RMessage2
retains a Client()
member, but this now allocates a handle that the server is responsible for closing. One further consequence of this is that
it is inefficient for the file server to use a thread handle to identify the address space into which a media driver should
transfer data—the current API for TBusLocalDrive
would require the file server to create and destroy a thread handle for each client read or write request.
TBool Protected() const
void SetProtected(TBool aState) const
Superseded by the security model itself.
TInt Owner(RProcess &anOwner) const
void SetOwner(const RProcess& aProcess) const
Unused and not useful either.
TInt GetRamSizes(TInt& aCodeSize, TInt& aConstDataSize, TInt& anInitialisedDataSize, TInt& anUninitialisedDataSize)
This is already deprecated as replaced by MemoryInfo()
.
TInt Rename(const TDesC& aName) const
This is deprecated and is replaced by the static function, and only applies to the current process:
static TInt User::RenameProcess(const TDesC &aName)
void SetJustInTime(TBool aBoolean) const
Will only have effect if:
The request is on the current process, OR
The caller is the process creator and the process has not been resumed
void Kill(TInt aReason)
void Terminate(TInt aReason)
void Panic(const TDesC& aCategory,TInt aReason)
Will only have effect if:
The request is on the current process, OR
The caller is the process creator and the process has not been resumed, OR
The caller holds PowerMgmt
capability
void Resume()
Will only have effect if
The caller is the process creator and the process has not been resumed
void SetPriority(TProcessPriority aPriority) const
Will only have effect if:
The request is on the current process, OR
The caller is the process creator and the process has not been resumed, OR
The process has enabled ‘remote priority control’ and the requested priority is ‘foreground’ or ‘background’
void SetType(const TUidType& aType)
This has a near equivalent in User::SetIdentity()
, although the later would restrict itself to the 3rd UID of the process type only. If the 3rd UID is the file-system identity
token, then this API needs to be only applicable to the current process, and only if the process holds a system capability.
TInt CommandLineLength() const
void CommandLine(TDes &aCommand) const
Only applicable to the current process, and migrated to the User
class.
TBool System() const
void SetSystem(TBool aState) const
Superceded by a new API in the User
class.
void Rendezvous(TRequestStatus& aStatus) const
TInt RendezvousCancel(TRequestStatus& aStatus) const
static void Rendezvous(TInt aReason)
Add a special ‘logon’ request that is completed if the target process terminates or calls RProcess::Rendezvous()
. This supports implementing an interlock on process creation.
TInt Create(const TDesC& aName,TThreadFunction aFunction,TInt aStackSize,TAny* aPtr,RLibrary* aLibrary,RHeap* aHeap, TInt aHeapMinSize,TInt aHeapMaxSize,TOwnerType aType)
RHeap* Heap()
TInt SetInitialParameter(TAny* aPtr)
As they are only useful for managing the lack of process emulation in EKA1 WINS.
TBool Protected() const
void SetProtected(TBool aState) const
Superseded by the security model itself.
TInt Rename(const TDesC& aName) const
replaced by
static TInt User::RenameThread(const TDesC &aName);
Now a static function, only applies to the current thread.
TInt Open(const TDesC& aFullName,TOwnerType aType=EOwnerProcess)
Can only open a thread which is named.
void Resume() const
void Suspend() const
void Kill(TInt aReason)
void Terminate(TInt aReason)
void Panic(const TDesC& aCategory,TInt aReason)
void SetPriority(TThreadPriority aPriority) const
void RequestComplete(TRequestStatus*& aStatus,TInt aReason) const
Only operate on threads in the current process.
TInt SetProcessPriority(TProcessPriority aPriority) const
Only effective on processes that have enabled ‘remote priority control’.
TInt GetDesLength(const TAny* aPtr) const
TInt GetDesMaxLength(const TAny* aPtr) const
void ReadL(const TAny* aPtr,TDes8& aDes,TInt anOffset) const
void ReadL(const TAny* aPtr,TDes16 &aDes,TInt anOffset) const
void WriteL(const TAny* aPtr,const TDesC8& aDes,TInt anOffset) const
void WriteL(const TAny* aPtr,const TDesC16& aDes,TInt anOffset) const
Inter-process copying is now all done via RMessage2
[Ptr
].
TExceptionHandler ExceptionHandler() const
TInt SetExceptionHandler(TExceptionHandler aHandler,TUint32 aMask)
void ModifyExceptionMask(TUint32 aClearMask, TUint32 aSetMask)
TInt RaiseException(TExcType aType)
TBool IsExceptionHandled(TExcType aType)
Thread exception management is now part of the User
class, and acts on the current thread only.
TBool System() const
void SetSystem(TBool aState) const
Has been replaced by a slightly modified API in the User
class, and acts on the current thread only.
static TCritical Critical();
static TInt SetCritical(TCritical aCritical);
void Rendezvous(TRequestStatus& aStatus) const
TInt RendezvousCancel(TRequestStatus& aStatus) const
static void Rendezvous(TInt aReason)
Add a special ‘logon’ request that is completed if the target thread terminates or calls RThread::Rendezvous()
. This supports implementing an interlock on thread creation. A logon request is a request for notification.
static TInt SetMachineConfiguration(const TDesC8& aConfig)
static TInt MachineConfiguration(TDes8& aConfig,TInt& aSize)
These manipulate machine setup data, and hence may only be called by system processes.
enum TThreadCritical {ENotCritical, EProcessCritical, ESystemCritical};
static TThreadCritical Critical()
static TInt SetCritical(TThreadCritical aCritical)
Replaces the RThread
/RProcess
‘system’ attribute. A thread can mark itself as system-critical, in which case an abnormal exit results in an OS reboot;
or as process-critical, in which case an abnormal exit results in termination of the owning process. Only a process with ProtServ
capability can set a thread to system-critical.
static TExceptionHandler ExceptionHandler()
static TInt SetExceptionHandler(TExceptionHandler aHandler,TUint32 aMask)
static void ModifyExceptionMask(TUint32 aClearMask, TUint32 aSetMask)
static TInt RaiseException(TExcType aType)
static TBool IsExceptionHandled(TExcType aType)
Exception management is now restricted to the current thread only.
static TBool PriorityControl()
static void SetPriorityControl(TBool aEnable)
This API allows a process to elect to have its priority changed by another process between ‘foreground’ and ‘background’. By default a process does not allow this.
This replaces the previous RMessagePtr
class; see the document “How To Migrate to Client/Server V2 APIs”.
TInt GetDesLength(TInt aParam) const
TInt GetDesMaxLength(TInt aParam) const
void ReadL(TInt aParam,TDes8& aDes,TInt anOffset=0) const
void ReadL(TInt aParam,TDes16 &aDes,TInt anOffset=0) const
void WriteL(TInt aParam,const TDesC8& aDes,TInt anOffset=0) const
void WriteL(TInt aParam,const TDesC16& aDes,TInt anOffset=0) const
TInt Read(TInt aParam,TDes8& aDes,TInt anOffset=0) const
TInt Read(TInt aParam,TDes16 &aDes,TInt anOffset=0) const
TInt Write(TInt aParam,const TDesC8& aDes,TInt anOffset=0) const
TInt Write(TInt aParam,const TDesC16& aDes,TInt anOffset=0) const
These replace the inter-process copy functions in RThread
, and supercede those in the old RMessage class
, which has now been replaced by RMessage2
and derives from RMessagePtr2
. The remote descriptor is no longer referenced by a pointer, but by parameter number in the message. Non-leaving versions
of read and write have been added to remove the requirement for trap harnesses in many places.
void Kill(TInt aReason)
void Terminate(TInt aReason)
void Panic(const TDesC& aCategory,TInt aReason)
Terminates the client thread.
TInt SetProcessPriority(TProcessPriority aPriority) const
Only effective on processes that have enabled ‘remote priority control’.
TInt Client(RThread& aClient, TOwnerType aOwnerType=EOwnerProcess) const
Create a handle to the sending thread. This will have to be closed by the server.
TInt Handle() const
This returns the message handle, used to identify the message in the kernel. This is used when transferring data in client
address space to a media device using the TBusLocalDrive
class.
This replaces the previous RMessage
class; see the document “How To Migrate to Client/Server V2 APIs”. It now derives from RMessagePtr2
, and so inherits all of the members of that class.
const RThread& Client() const
The client thread handle is not implicit in the message body, and must now be created using the RMessagePtr2::Client()
member.
void Complete(TInt aReason) const
void ReadL(const TAny* aPtr,TDes8& aDes) const
void ReadL(const TAny* aPtr,TDes8& aDes,TInt anOffset) const
void ReadL(const TAny* aPtr,TDes16& aDes) const
void ReadL(const TAny* aPtr,TDes16& aDes,TInt anOffset) const
void WriteL(const TAny* aPtr,const TDesC8& aDes) const
void WriteL(const TAny* aPtr,const TDesC8& aDes,TInt anOffset) const
void WriteL(const TAny* aPtr,const TDesC16& aDes) const
void WriteL(const TAny* aPtr,const TDesC16& aDes,TInt anOffset) const
void Panic(const TDesC& aCategory,TInt aReason) const
void Kill(TInt aReason) const
void Terminate(TInt aReason) const
These have all move to RMessagePtr2
, the base class for RMessage2
.
RMessage2(const RMessagePtr2&)
This ‘reconstitutes’ the full message data from a message handle.
inline CSession2* RMessage2::Session() const;
Returns the session to which this message is to be delivered to.
TInt Count()
This function provides no real value, so it is removed.
TInt Count()
This function provides no real value, so it is removed.
TLibraryEntry EntryPoint() const
TUint* DllRefTable() const
These functions aren’t used and provide no real value.
This class is removed.
This class is removed.
TInt Read(TINT64 aPos, TInt aLength, const TAny* aTrg, TInt aThreadHandle, TInt anOffset);
TInt Write(TINT64 aPos, TInt aLength, const TAny* aSrc, TInt aThreadHandle, TInt anOffset);
The fourth parameter will be changed from a thread handle to a message handle. This results in no change in signature, but
the parameter will be translated as if it were an IPC message handle. This requires a new constant to provide the equivalent
of KCurrentThreadHandle
:
const TInt KLocalMessageHandle = -1;
This allows the generic APIs to be used to transfer data in the current process address space.
TInt Read(TInt64 aPos,TInt aLength,const TAny* aTrg,TInt aThreadHandle,TInt anOffset);
TInt Write(TInt64 aPos,TInt aLength,const TAny* aSrc,TInt aThreadHandle,TInt anOffset);
These APIs have to be changed to match the RLocalDrive
interface.
All APIs removed.
The only functionality which CSession
provided over that in CSharableSession
was to store a thread handle and provide thread Read/Write/Panic methods. As these RThread
methods are to be removed, there is no purpose in supporting separate CSession
and CSharableSession
classes. CSession2
replaces them both.
TInt RSessionBase::SendReceive(TInt aFunction,TAny *aPtr) const
TInt RSessionBase::Send(TInt aFunction,TAny *aPtr) const
void RSessionBase::SendReceive(TInt aFunction,TAny *aPtr,TRequestStatus &aStatus) const
TInt RSubSessionBase::SendReceive(TInt aFunction,TAny *aPtr) const
TInt RSubSessionBase::Send(TInt aFunction,TAny *aPtr) const
void RSubSessionBase::SendReceive(TInt aFunction,TAny *aPtr,TRequestStatus &aStatus) const
become:
TInt RSessionBase::SendReceive(TInt aFunction,const TIpcArgs& aArgs) const
TInt RSessionBase::Send(TInt aFunction,const TIpcArgs& aArgs) const
void RSessionBase::SendReceive(TInt aFunction,const TIpcArgs& aArgs,TRequestStatus &aStatus) const
TInt RSubSessionBase::SendReceive(TInt aFunction,const TIpcArgs& aArgs) const
TInt RSubSessionBase::Send(TInt aFunction,const TIpcArgs& aArgs) const
void RSubSessionBase::SendReceive(TInt aFunction,const TIpcArgs& aArgs,TRequestStatus &aStatus) const
To enable the kernel to police the use of RMessagePtr2::Read/Write/GetDes
methods, a way needs to be supplied for a client to specify the type of the arguments which it has passed via a message.
( See "Inter-thread transfer" )
This will be provided by a new class which wraps up all the arguments and type information, and it is a reference to this
class which will be passed to RSessionBase::SendReceive
instead of the current TAny*
.
The new class provides all-in-one constructors for zero to four arguments, plus Set methods to add arguments later of required.
class TIpcArgs
{
public:
enum TArgType
{
EUnspecified = 0,
EHandle = 1,
EFlagDes = 4,
EFlagConst = 2,
EFlag16Bit = 1,
EDes8 = EFlagDes,
EDes16 = EFlagDes|EFlag16Bit,
EDesC8 = EFlagDes|EFlagConst,
EDesC16 = EFlagDes|EFlagConst|EFlag16Bit,
};
enum { KBitsPerType=3 };
enum TNothing {ENothing};
public:
inline TIpcArgs();
template <class T0> inline explicit TIpcArgs(T0 a0);
template <class T0,class T1> inline TIpcArgs(T0 a0,T1 a1);
template <class T0,class T1,class T2> inline TIpcArgs(T0 a0,T1 a1,T2 a2);
template <class T0,class T1,class T2,class T3> inline TIpcArgs(T0 a0,T1 a1,T2 a2,T3 a3);
inline void Set(TInt aIndex,TNothing);
inline void Set(TInt aIndex,TInt aValue);
inline void Set(TInt aIndex,const TAny* aValue);
inline void Set(TInt aIndex,RHandleBase aValue);
inline void Set(TInt aIndex,const TDesC8* aValue);
inline void Set(TInt aIndex,const TDesC16* aValue);
inline void Set(TInt aIndex,TDes8* aValue);
inline void Set(TInt aIndex,TDes16* aValue);
public:
TInt iArgs[KMaxMessageArguments];
TInt iFlags;
};
The three RSessionBase::Share
methods are replaced by a single RSessionBase::ShareAuto method,
and the RSessionBase::Attach
method has been removed.
See see the document “How To Migrate to Client/Server V2 APIs”. for more details on this and other changes relating to the new V2 Client/Server APIs.
The following HAL functions will be restricted, and require special capabilities for their use.
UserHal::MachineInfo(TDes8& anInfo)
Needs ReadDeviceData capability because of the presence of Machine Unique ID.
HAL::Set(HAL::ECaseSwitchDisplayOn,TInt aValue)
HAL::Set(HAL::ECaseSwitchDisplayOff,TInt aValue)
HAL::Set(HAL::EPenDisplayOn,TInt aValue)
HAL::Set(HAL::EDisplayContrast,TInt aValue)
HAL::Set(HAL::EDisplayBrightness,TInt aValue)
HAL::Set(HAL::EBacklightState,TInt aValue)
UserHal::SetXYInputCalibration(const TDigitizerCalibration& aCalibration)
UserHal::RestoreXYInputCalibration(TDigitizerCalibrationType aType)
UserSvr::SetMemoryThresholds(TInt aLowThreshold, TInt aGoodThreshold)
Needs WriteDeviceData capability.
HAL::Set(HAL::EDisplayMode,TInt aValue)
HAL::Set(HAL::EDisplayPaletteEntry,TInt aValue)
Must be controlled by WSERV. Needs WSERV's SID (SID is used by the kernel to control access to UI device drivers), ProtServ and PowerMgtmt capabilities
HAL::Set(HAL::EDisplayState,TInt aValue)
UserHal::SwitchOff()
UserSvr::WsRegisterSwitchOnScreenHandling(TBool aState)
UserSvr::WsSwitchOnScreen()
Needs PowerMgmt capability.
UserSvr::AddEvent(const TRawEvent& anEvent)
Needs SwEvent capability.
HAL::Set(HAL::ELEDmask,TInt aValue)
May need special capability depending on what the LEDs do on a particular device.
ResetInactivityTime()
Requires PowerMgmt
SetHomeTime(const TTime &aTime)
Requires WriteDeviceDataWsRegisterThread and User::FsRegisterThread. Now takes no arguments
TSecureId SecureId() const;
TVendorId VendorId() const;
These return the Secure ID and Vendor ID of the target process
TBool HasCapability(TCapability aCapability,const char* aDiagnostic=0) const;
TBool HasCapability(TCapability aCapability1,TCapability aCapability2,const char* aDiagnostic=0) const;
These check whether the target process has a particular capability or set of capabilities, and return either True or False.
TSecureId SecureId() const;
TVendorId VendorId() const;
These return the Secure ID and Vendor ID of the process that owns the referenced thread.
TBool HasCapability(TCapability aCapability,const char* aDiagnostic=0) const;
TBool HasCapability(TCapability aCapability1,TCapability aCapability2,const char* aDiagnostic=0) const;
These check whether the owning process has a particular capability or set of capabilities, and return either True or False.
TSecureId CreatorSecureId() const;
TVendorId CreatorVendorId() const;
These return the Secure ID and Vendor ID of the process which created the current process
TBool User::CreatorHasCapability(TCapability aCapability,const char* aDiagnostic)
TBool User::CreatorHasCapability(TCapability aCapability1,TCapability aCapability2,const char* aDiagnostic)
These check whether the process which created the current process has a particular capability or set of capabilities, and return either True or False.
This replaces the previous RMessage
class; see section .
TUint32 SecureId() const;
TUint32 VendorId() const;
These return the Secure ID and Vendor ID of the client process
TInt HasCapability(TCapability aCap) const;
TInt HasCapability(TCapability aCap1, TCapability aCap2) const;
TInt HasCapability(TCapability aCap1, TCapability aCap2, TCapability aCap3) const;
These check whether the client process has a particular capability or set of capabilities, and return either KErrNone or KErrPermissionDenied.
DLL Entry points are not supported in the new kernel. They never worked as people expected and were flawed.
All implementations of E32Dll()
must be removed.
To enable different processes to cooperate in a secure way, mechanisms are provided to create 'protected' kernel resources. These have the same properties as private (local) resources, but a process which owns a handle on them may choose to share them with other processes.
The following kinds of kernel objects may be created as 'protected' kernel resources. Handles to these may be passed via client/server communications.
RMutex
RSemaphore
RChunk
RBusLogicalChannel
RSessionBase
A client process which has a handle on one of these can give a server the used of it by sending the handle as a RHandleBase
parameter in a TIpcArgs
package. The server can then open this handle by calling the following method on the appropriate resource class:
Open(RMessagePtr2 aMessage,TInt aParam,TOwnerType aType=EOwnerProcess)
where aMessage in the message that the server received from the client, and aParam is a number (0-3) indicating which message parameter holds the client's handle.
A server can return a handle to the client by completing a client message using the resource object as an argument:
RMessagePtr2::Complete(RHandleBase aHandle)
This will create a thread relative handle to the object and cause the client's request to complete with this value.
The client should check the returned value, a negative value indicates an error value, a positive value is a handle which
can be used to initialise the relevant R object by calling RHandleBase::SetHandle
.
Alternatively (and preferably) this check and set step can be performed by using the following method instead:
TInt RHandleBase::SetReturnedHandle(TInt aHandleOrError)
where aHandleOrError
is the value returned by the server. This method does the following:
If the value is a handle, then set the handle of the object to this, and return KErrNone
;
else, set the handle value to NULL
and return the error value.
The following methods are used to create protected resources:
TInt RBusLogicalChannel::DoCreate(const TDesC& aDevice, const TVersion& aVer, TInt aUnit, const TDesC* aDriver, const TDesC8* anInfo, TOwnerType aType, TBool aProtected=EFalse);
The create method has been extended to take an additional argument (aProtected
) to indicate if the channel is protected.
RSessionBase::ShareProtected()
This makes an already existing session a protected resource. This is only permitted if the server constructor specified the
type EGlobalSharableSessions
.
RSemaphore::CreateGlobal
RMutex::CreateGlobal
RChunk::CreateGlobal
Use of these existing methods with a zero length name (KNullDesC
) will create an anonymous and protected resource.
An additional method is provided for the RChunk
class which enables the creator process to set restrictions on the use of a chunk by another process.
RChunk::SetRestrictions(TUint aFlags)
Currently the only implemented restriction is the EPreventAdjust
flag, which prevents other processes from changing the memory allocation with Adjust, Commit, Allocate, or Decommit.
Private (local) servers can now be created by giving them a zero length name in CServer2::Start
. As they have no name, the usual server connection semantics don't work and threads in other processes won't be able to see
them.
To enable connections to be made to these servers the following methods have been added:
CServer2::Server()
This returns a copy of the RServer2
object used by the server. This can be used when making a connection.
SessionBase::CreateSession(RServer2 aServer, const TVersion &aVersion, TInt aMsgSlots)
This is a new overload of the create method which takes an RServer2
object instead of a name.
Connections to private servers may be given to other processes by creating a protected session to it, then passing this session's handle over another client/server link. (See the two previous sections.)
To prevent malicious software from masquerading as key OS servers a special naming convention has been adopted for these:
they should begin with a '!' character. Only processes with KCapabilityProtServ
capability may create servers with such names.
To help protect clients from other kinds of spoofed or badly behaved servers the following method for creating sessions has been added:
TInt RSessionBase::CreateSession(const TDesC& aServer,const TVersion& aVersion,TInt aAsyncMessageSlots,TRequestStatus* aStatus,TInt32 aIdentity=0)
The aIdentity
argument in this function specifies the Security Identitier of the process in which the client is expecting the server to
be running. (Zero means "don't care") If this identity doesn't match that of the server's process, then the session creation
fails with KErrNotFound
.
This new method also allows connecting to a server asynchronously (if aStatus
is zero then the connection is done synchronously). When the connection has completed, aStatus
will be signalled; if this holds anything other than KErrNone
then the connect failed and the programmer must Close()
the session. (Failure to do so, will result in leaked resources.)
Note that CreateSession
returns a standard error value; if this is not KErrNone
then the connection process has failed before sending a message to the server, and in this case aStatus
will not be signalled.
Some base APIs may be used in pre-v9.0 code to access other processes in ways that are not permitted with platform security. For such APIs, the kernel provides run-time checks for correct usage of them.
These checks are enabled by the PlatSecProcessIsolation
configuration set at ROM build time or in the Emulators INI file. If PlatSecProcessIsolation
is set 'on' then incorrect use of restricted APIs will emit diagnostic messages (if PlatSecDiagnostic
is 'on'.) and such use will cause panics/errors (if PlatSecEnforcement
is 'on'.) For details on how to configure these settings, see cedar\generic\base\documentation\Base_How_To_Configure_Platform_Security_Settings.doc
.
To test for use of these restricted APIs, take the following steps:
Execute all test code on the Emulator with
PlatSecProcessIsolation On
PlatSecDiagnostics On
PlatSecEnforcement Off
Collect all the EPOCWIND.OUT files generated by these tests.
Examine these logs for lines starting with the string
" *PlatSec* WARNING - Process Isolation check would have failed -"
This shows where APIs have been used in a prohibited manned.
Each warning includes the full name of the thread which used the API in the form of PROCESS_NAME::THREAD_NAME
. Check that this originates from the component being tested and not another in the system.
The warning message also includes an indication of which API caused the warning.
Modify production and test code so that they don't use restricted APIs in a prohibited manner.
Redo 4 and 5 for after running code on reference hardware rather than the Emulator.
If test coverage for a component isn't 100% then there may still be prohibited use of restricted APIs remaining in a component's code. This can only be determined by code inspection.