How to share file handles between processes

This section describes the sharing of file handles between two processes, and the APIs provided for this purpose. This feature is only supported on Symbian platform versions with EKA2 architecture.

It is possible to share file handles between two processes, allowing an open file to be passed from one process to another. This is a necessary feature in secure versions of Symbian platform.

Some further implementation considerations are provided to support this mechanism. A description of passing file handles is given, followed by examples, which show you how to implement a binary using this feature.

See also

File Server Client Side Overview

Platform security.

This section contains the following topics:

Overview

Allowing an open file to be passed from one process to another is a necessary feature in secure versions of Symbian platform. The platform security model provides a data caging mechanism, allowing processes and the OS itself to hide private data from other processes.

However, while it is desirable to keep a file safe from other processes, it is useful to give a specific process access to the file:

  • without the need to give that process any special capabilities

  • without the need to give the recipient of the file the full path name of that file.

New RFile member functions enable an open file handle to be passed from client to a server, from server to a client or from one process to another process.

Note that file handles are not really handles in the usual sense of referring to a kernel object, but are simply numbers that refer to an open file within a file server session.

APIs that support the sharing of file handles

The following RFile member functions support the sharing of file handles between processes:

The owner of an open Rfile object uses the Transfer***() member functions to transfer ownership of that object to another process. Note that the file-server session must be marked as shareable by calling RFs::ShareProtected() before any file handles are transferred, otherwise the Transfer***() functions return KErrBadHandle.

A receiving process uses the Adopt***() member functions to adopt an RFile object passed from another process.

A receiving process uses the RFile::Name() member function to retrieve the file name and extension (but not the path) from the adopted RFile object. This is needed mainly by recognisers, which sometimes examine a file’s extension to determine whether it is valid or not.

The RFile::Duplicate() member function allows a process to clone a received RFile object so that, for example, two separate functions or threads in the receiving process can read the file independently.

As another example, consider a client that only grants access to its data caged private area files, to a specific system server. The process that opens the file and shares it (using one of the Transfer***() functions) controls read and write access to the file, while the corresponding client or server simply calls one of the Adopt***() functions. In effect, the Adopt***() functions behave like a file opening API such as RFile::temp(), RFile::Create() and RFile::Open(). The adopted file retains the access attributes of the file set by the process doing the sharing. The access attributes of the file can be changed by the adopting process if the security model permits it.

The following sections present example code and describe how to share a file between two processes. This example code uses a simple server that offers the kind of APIs required to implement the sharing. Details of this server are not given.

Note that for the sake of clarity, there is no error checking in the example code.

Example 1 - Client passing a file handle to a server

In this example, a client passes an open file’s handle to a server. The server in turn passes the handle over to a second server.

The example assumes that the paths and files used exist and are correct. A file server session (it is recommended that this session is used specifically for this purpose) is set as shared by calling RFs::ShareProtected(). This enables the session to be used by another process. The file is then opened (it exists in the client’s private directory). The subsession handle and the fileserver session are then passed to the server using RFile::TransferToServer(). The file handle can now be adopted by the server’s process.

Client code:


    RFileHandleSharer handsvr;    // handle to server1
    User::LeaveIfError(handsvr.Connect());        // connect to server1
    CleanupClosePushL(handsvr);
    RFs fs;
    User::LeaveIfError(fs.Connect());
    CleanupClosePushL(fs);
    User::LeaveIfError(fs.ShareProtected());

    RFile file;
    User::LeaveIfError(file.Open(fs, _L("test.txt"), EFileRead));
    CleanupClosePushL(file);
    
    
    // store the RFs handle in message slot 0 and the RFile handle in slot 1
    TIpcArgs ipcArgs;
    User::LeaveIfError(file.TransferToServer(ipcArgs, 0, 1));        

    // send to server
    User::LeaveIfError(handsvr.SendReceive(EMsgXXX, ipcArgs));

    // ...continue to use file
    //

    CleanupStack::PopAndDestroy(3); // close file, fs, and handsvr

Server1 code:


    void CFHSession::PassFileHandleL(const RMessage2& aMsg)
        {
        RFileHandleSharer2 handsvr2;    // connect to server2
        User::LeaveIfError(handsvr2.Connect());
        CleanupClosePushL(handsvr2);
    
        RFile file;
    
        // Adopt the file using the RFs handle from message slot 0 and the RFile handle from slot 1
        User::LeaveIfError(file.AdoptFromClient(aMsg, 0, 1));    
        CleanupClosePushL(file);
    
        // Use the file
        // …
    
    
        // pass the file handle on to server2
        TIpcArgs ipcArgs;
        User::LeaveIfError(file.TransferToServer(ipcArgs, 0, 1));
        User::LeaveIfError(handsvr2.SendReceive(EMsgXXX, ipcArgs));
    
        // continue to use file
        // …

        CleanupStack::PopAndDestroy(2);    // close file and handsvr2
    
        aMsg.Complete(KErrNone);
        }

Server2 code:


    void CFHSession2::PassFileHandleL(const RMessage2& aMsg)
        {
        RFile file;

        // Adopt the file using the RFs handle from message slot 0 and the RFile handle from slot 1
        User::LeaveIfError(file.AdoptFromClient(aMsg, 0, 1));    
        CleanupClosePushL(file);
    
        // ..use the file
    
        CleanupStack::PopAndDestroy();    // close file
    
        aMsg.Complete(KErrNone);
        }

Example 2 - Client requesting a file handle from a server

This example is similar to Example 1 but there are some differences.

A client connects to a server that provides a shared fileserver session and a file handle to an open file when requested.

Client code:


    RFileHandleSharer handsvr;    // handle to server
    User::LeaveIfError(handsvr.Connect());        // connect to server
    CleanupClosePushL(handsvr);

    // Retrieve the RFs and RFile handles from the server
    TInt fsh;            // session (RFs) handle
    TPckgBuf<TInt> fh;    // sub-session (RFile) handle
    
    fsh = handsvr.SendReceive(EMsgXXX, TIpcArgs(&fh));    // pointer to fh in slot 0
    User::LeaveIfError(fsh);

// Adopt the file using the returned handles
RFile file;
User::LeaveIfError(file.AdoptFromServer(fsh, fh()));
    CleanupClosePushL(file);

// ..use the file
//

    CleanupStack::PopAndDestroy(2);    // close file and handsvr

Server code:


    void CFHSession::GetFileHandleL(const RMessage2& aMsg)
        {
        RFs fs;
        User::LeaveIfError(fs.Connect());
        CleanupClosePushL(fs);
    
        User::LeaveIfError(fs.ShareProtected());
    
        RFile file;
        User::LeaveIfError(file.Open(fs, _L("test.txt"), EFileRead));

        // transfer to client: store the RFile handle into the package buffer in slot 0 
        // and complete the message with the RFs handle
        // NB this assumes that if TransferToClient() return an error, then
        // the standard CServer2::RunError() will complete the message
        User::LeaveIfError(file.TransferToClient(aMsg, 0));
        ASSERT(aMsg.IsNull());    // message should have been completed

        file.Close();
        CleanupStack::PopAndDestroy(1);    // fs
        }

Example 3 - Client passing a file handle to another process

Creator code:


    RFs fs;
    User::LeaveIfError(fs.Connect());
    CleanupClosePushL(fs);

    User::LeaveIfError(fs.ShareProtected());
    
    RFile file;
    User::LeaveIfError(file.Open(fs, _L("test.txt"), EFileRead));
    CleanupClosePushL(file);
    
    // create test process
    RProcess p;
    User::LeaveIfError(p.Create(_L("FHServer.exe"),  KNullDesC));
    CleanupClosePushL(p);

    // transfer to process storing the RFs handle into environment  slot 1 and the RFile handle into slot 2
    // NB slot 0 is reserved for the command line
    User::LeaveIfError(file.TransferToProcess(p, 1, 2));

    // Wait for handle to be transferred; wrap in an active object if blocking this thread is undesirable
    TRequestStatus transStatus;
    p.Rendezvous(transStatus);

    if(transStatus != KRequestPending)
        { // Process creation failed
        p.RendezvousCancel(transStatus);
        p.Kill(0);
        User:Leave(transStatus.Int());
        }
    // Start the process
    p.Resume();


    User::WaitForRequest(transStatus);
    User::LeaveIfError(transfStatus.Int());

    // Now we can safely close the fs
    CleanupStack::PopAndDestroy(3);    // close p, file, and fs

Created process code:


    RFile file;

    // Adopt the file using the RFs handle into environment  slot 1 and the RFile handle into slot 2
    User::LeaveIfError(file.AdoptFromCreator(1));
    CleanupClosePushL(file);
    RProcess::Rendezvous(KErrNone); // Signal transfer completed successfully

    // ..use the file
    //

    CleanupStack::PopAndDestroy();    // close file                                                                                                                                   

Implementation Considerations

An RFile object contains two handles:

  • a file (or subsession) handle

  • a file server handle

When a file handle is adopted by the receiver, the new RFile object that is created by the Adopt***() member functions contains a duplicate of the sender’s file server handle. This new RFile object, however, has a flag set in the subsession handle to allow it to close its associated file server handle automatically whenever RFile::Close() is called. This removes the need to maintain a separate RFs object when adopting a file.

Security issues

It is strongly recommended that the process from which a shared file originates, opens a file server session specifically for this purpose, and closes it after it has finished using the shared file. This is because the session handle is shared along with the file handle, and therefore, any other files opened in that session may be accessible to the other process. The receiving process can increment file handle numbers (remember file handles are only numbers) and gain access to files.

It is also recommended that the adopting process does not open other files with the session it receives, as the process that shared the file may do the same.

RFs and RFile lifetime issues

The RFile subsession handle passed to Transfer***() and Adopt***() member functions is duplicated by the Transfer***() functions so that the original RFile handle can either be closed immediately or used for a period and then closed. This is because it is not the same handle as the one being adopted.

If there is no need to continue using the RFile handle, then the RFs handle can be safely closed as follows:

  • For TransferToServer() or AdoptFromClient(), the RFs handle can be safely closed once the server has called AdoptFromClient(). This would normally be done on return from a synchronous call.

  • For TransferToClient() or AdoptFromServer(), the RFs handle can be safely closed once the server has called TransferToClient().

  • For TransferToProcess() or AdoptFromCreator(), the RFs handle can be safely closed once the client has called TransferToProcess().

Testing

Extensive testing of implementations of this mechanism are not required as, in effect, you are simply using a file server subsession (RFile) API. It is recommended that you test the client-server system, but that will be specific to how you use it. If it works you have probably implemented it correctly, provided you adhere to the security considerations of the shared file.

Summary

Passing files between processes is a useful tool, but it is important that you adhere to the security advice.

To share a file handle, take the following steps:

  1. Ensure the file exists. A file to be shared between two processes is a resource that lives in a system or private location for security.

  2. Set the fileserver session as shareable so that it can be used by more that one process. A fileserver session (as with all server sessions) is a Kernel object.

  3. Transfer the session handle and the subsession handle from the open file using one of the RFile Transfer***() member functions and the message passing system between client and server.

  4. Use one of the RFile Adopt***() member functions to begin using the handles in the receiving process.

  5. Once you have finished, close the received RFile object.