Remote Procedure CallsRemote Procedure Calls (RPC) allow applications to be designed and deployed in a distributed fashion by using a client/server architecture. Programmers can develop applications without worrying too much about the details of data encapsulation and transmission because the RPC interface handles these tasks automatically. There are two main RPC implementation and encoding standards: Open Network Computing (ONC) and Distributed Computing Environment (DCE). UNIX implements ONC-RPC (also known as Sun-RPC). RPC applications are constructed by developing a server that exports a number of routines clients can call, provided they have adequate credentials. Each server program has a unique program number handed off to a special process known as portmap. When clients want to call a routine for an RPC server, several steps are involved:
This description is a bit general. Some RPC servers have well-known ports (such as rpc.nfsd), and the interaction with portmap is sometimes unnecessary. Note that querying the portmap per service isn't necessarily required, and anyone who chooses to enforce access restrictions on clients by controlling access to the portmapper service isn't protecting the application. RPC Definition FilesMost RPC applications implement their interfaces by using an RPC definition file (usually with .x as a file suffix). This file defines structures used throughout the program and the interface the server exports. The rpcgen tool on most modern UNIX systems can process these files and automatically generate client and server stub routines for communicating data between client and server applications. This tool takes a lot of the developer's work out of dealing with data transmission primitives in accordance with RPC design principles. For code reviewers, this file is a convenient starting point for auditing RPC applications. You can quickly ascertain what functions are available to connecting clients and what arguments they take. The file format is quite straightforward. Developers can declare structures that the program is using as well as the RPC server interface (which is a structure definition). RPC abstracts the details of data transmission by using External Data Representation (XDR), a standard developed to represent data elements in a machine- and implementation-independent fashion. An RPC definition file that describes an RPC interface can represent arguments of different types. These types correspond directly to XDR basic types or structures composed of XDR basic types. The basic types in RPC definition files are as follows:
In addition to basic data types, XDR allows the declaration of arrays and vectors. (Vectors are fixed-length arrays, so just the term "arrays" is used in this section.) Arrays are defined by using brackets (<>) with an optional size parameter. A fixed-length array looks like this: int numberarray<1024>; In this case, the RPC runtime ensures that an array supplied by a client doesn't exceed this maximum limit. Arrays can also be unbounded, as in this example: int numberarray<>; In this case, clients are free to supply any number of integers they choose. When used with the string and opaque types, the brackets indicate the length of the string, not an array of strings. A string with a maximum length of 255 bytes is declared like so: string mystring<255>; The server interface is defined by using the program keyword followed by the structure describing what routines have been exported. This structure can define multiple versions of the RPC program (using the version keyword), with each version exporting a unique set of procedures (although typically, they export the same ones). The prototype for an exported function is much like a C function prototype, with some differences; primarily, the function name is in uppercase letters and is followed by the procedure number assigned to that routine. Each routine that has been exported appears in the source code, but it's lowercase and has _svc appended to indicate it's a service routine. For example, you have the following declaration in the RPC definition file: int HELLO_WORLD_1(void) = 1; The server routine that implements it in the source is named hello_world_1_svc(). Here's an example of a server definition. The following code fragment is from the sm_inter.x file, which defines the interface for the well-known rpc.statd service: program SM_PROG { The statd program has only one available version: version 1. It also exports six functions that clients can call remotely: sm_stat, sm_mon, sm_unmon, sm_unmon_all, sm_simu_crash, and sm_notify. To audit this application, an excellent starting point is looking for these functions in the source code because you know they're taking data from the client and processing it. You can also deduce what kind of data they're accepting from these prototypes; in the preceding example, they're specially defined structures, except sm_simu_crash, which doesn't take any arguments. To audit these functions, you can look up these structures to see what data you can supply. For example, if you want to audit the sm_stat function, you look for the definition of the sm_name structure, as shown: const SM_MAXSTRLEN = 1024; In this instance, you can supply a string that can be at most 1024 bytes. As you can see, RPC definition files allow you to quickly identify what code the server exposes to the client. RPC Decoding RoutinesThe RPC definition file isn't required to create an RPC application. Developers might choose to hand-code the client and server stubs, which involves creating decoders for data manually by using the XDR routines exported for encoding and decoding. (Usually, the rpcgen tool uses XDR subroutines to encode structures and types defined in the RPC specification file.) XDR exports encoding and decoding routines for all its basic types: xdr_int(), xdr_string(), xdr_bool(), and so on. This lower-level manipulation introduces the opportunity for mistakes in the routines responsible for decoding data destined for certain routines. For example, the sm_name structure above has one element: a string with a maximum length of 1024. The XDR routine generated by rpcgen looks like this: bool_t If developers create these types of routines, they might accidentally use the wrong constants for maximum string lengths, not deal with errors properly, and so on. Therefore, when a developer doesn't use the RPC definition file, there's an additional lower layer where things might go wrong. Note Whether developers use the RPC definition file or not, there's a chance some implementations of rpcgen will make mistakes or the XDR libraries might have decoding errors. However, the system libraries usually aren't your primary concern when auditing an applicationbut they are well worth browsing in your spare time! AuthenticationRPC provides a number of authentication methods that can be used in applications that need to enforce access control for the functions they export:
RPC applications could possibly implement additional security features to help tighten control over applications, although additional features are used less often than they should be. If RPC authentication is in place, there's code to manually verify credentials in server routines or a dispatch function. In either case, some code is available to examine authentication data supplied with requests. It looks something like this: int authenticate(struct svc_req *svc) This code has some verification of the requester's credentials, but it's using the AUTH_UNIX authentication method. As you know now, that method isn't much better than having no authentication at all. |
Monday, December 21, 2009
Remote Procedure Calls
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment