Tuesday, December 22, 2009

B.2 PIM Version 1 Packet Formats













B.2 PIM Version 1 Packet Formats


PIMv1 is the result of the collective work of Steve Deering, Deborah Estrin, Dino Farinacci, and Van Jacobsen. The packet formats are described in section 4 in the specification, which is included here in its entirety (it has been reformatted for consistency).


4 Packet Types


RFC-1112 specifies two types of IGMP packets for hosts and routers to convey multicast group membership and reachability information. An IGMP-Host-Query packet is transmitted periodically by routers to ask hosts to report which multicast groups they are members of. An IGMP-Host-Report packet is transmitted by hosts in response to received queries advertising group membership.


This document introduces new types of IGMP packets that are used by PIM routers. The packet format is shown in Figure 1


Figure 1. (Figure 8 in orig.) IGMP packet format




  • Version: This memo specifies version 1 of IGMP. Version 0 is specified in RFC-988 and is now obsolete.


  • Type: There are five types of IGMP messages:


    - 1 = Host Membership Query

    - 2 = Host Membership Report

    - 3 = Router DVMRP Messages

    - 4 = Router PIM Messages

    - 5 = Trace Messages


  • Code: Codes for specific message types. Used only by DVMRP and PIM. PIM codes are:


    - 0 = Router-Query

    - 1 = Register

    - 2 = Register-Stop

    - 3 = Join/Prune

    - 4 = RP-Reachability

    - 5 = Assert

    - 6 = Graft (dense-mode PIM only)

    - 7 = Graft-Ack (dense-mode PIM only)

    - 8 = Mode


  • Checksum: The checksum is the 16-bit one's complement of the one's complement sum of the entire IGMP message. For computing the checksum, the checksum field is zeroed.


  • Group Address: In a IGMP-Host-Query message, the group address field is zeroed when sent, ignored when received. In an IGMP-Host-Report message, the group address field holds the IP host group address of the group being reported. PIM-Join/Prune, PIM-Assert, and PIM-Mode messages have the group address field zeroed when sent to a point-to-point link and have the next-hop router address in it when sent to a multiaccess LAN. PIM-Register and Register-Stop messages have the group address field zeroed when sent. (See section 4.2 for RP-Reachability format.) PIM-Query message has the first 30 bits in the group address zeroed. The thirtieth bit carries the sparse mode reset flag, if 1, the modes of entries in the downstream routers are reset to periodic join mode. The thirty-first bit carries the mode of the interface where the PIM-Query is sent, if 1, the mode on the interface is configured as sparse mode only; if 0, the mode on the interface is configured as dense mode acceptable.



4.1 PIM-Join/Prune, PIM-Assert and PIM-Mode Messages

PIM-Join/Prune, PIM-Assert, and PIM-Mode messages have the following additional information appended to the fixed header (RP-Reachability message type will be described in section 4.2). The format of the additional information is shown in Figure 2


Figure 2. (Figure 9 in orig.) Additional packet format for PIM-Join/Prune




  • Reserved: Unused field, zeroed when sent, ignored when received.


  • Addr Length: The length in bytes of the encoded source addresses in the Join and Prune lists.


  • Maddr Length: The length in bytes of the encoded multicast addresses.


  • Number of Groups: The number of multicast group sets contained in the message.


  • Multicast group address: For IP, it is a 4-byte Class D address.


  • Multicast group address mask: A bit mask used against the multicast group address. This is the method to describe a range of multicast addresses. If the multicast group address field describes a single group address, the value must be 255.255.255.255.


  • Number of Join Sources: Number of join source addresses listed for a given group. For PIM-Assert message, this is the number of assert source addresses listed for a group. For PIM-Mode message, this is the number of source addresses listed for a group whose modes are changed.


  • Join Source Address-1 .. n: This list contains the sources that the sending router will forward multicast datagrams for if received on the interface this message is sent on. The address 0.0.0.0 indicates a Join for all sources.


  • Number of Prune Sources: Number of prune source addresses listed for a given group. PIM-Assert and PIM-Mode messages only use the join list, so the number of prune sources should always be 0.


  • Prune Source Address-1 .. n: This list contains the sources that the sending router does not want to forward multicast datagrams for when received on the interface this message is sent on. The address 0.0.0.0 indicates a prune for all sources.



4.1.1 Source Address Format

In PIM messages, all source addresses have the format shown in Figure 3


Figure 3. (Figure 10 in orig.) Source address format




  • Reserved: Unused field, zeroed when sent, ignored when received.


  • MD: Used when sending Mode messages. An upstream router indicates to downstream neighbor(s) if the specific group should operate in periodic join mode. If the bit is clear, the routers should operate in implicit join mode, which means they do not send periodic join messages to the upstream router. The Source Address should be set to 0.0.0.0 in Mode messages with the WC bit and RP bit set to 0 if wildcarding is used.




  • WC: The WC bit is a 1 bit value. If 1, the Join or Prune applies to the (*,G) entry. If 0, the Join or Prune applies to the (S,G) entry where S is Source Address. Joins and Prunes sent toward the RP should have this bit set.


  • RP: The RP bit is a 1 bit value. If 1, the information about (S,G) is sent toward the RP. If 0, the information should be sent about (S,G) toward S, where S is Source Address.


  • Mask Length: Mask length is 6 bits. The value is the number of contiguous bits left-justified used as a mask which describes the address. The mask length must be less than or equal to Addr Length * 8.


  • Source Address: The address length is indicated from the Addr Length field at the beginning of the header. For IP, the value is 4 octets. This address is either an RP address (WCbit = 1) or a source address (WCbit = 0). When it is a source address, it is coupled with the group address to make (S,G).



Represented in the form of <WCbit><RPbit><Mask length><Source address>:


A source address could be a host IP address :


< 0 >< 0 >< 32 >< 192.1.1.17 >


A source address could be the RP's IP address :


< 1 >< 1 >< 32 >< 131.108.13.111 >


A source address could be a subnet address to prune from the RP-tree :


< 0 >< 1 >< 28 >< 192.1.1.16 >


A source address could be a general aggregate :


< 0 >< 0 >< 16 >< 192.1.0.0 >




4.2 RP-Reachability Message

RP-Reachability messages have the packet format shown in Figure 4


Figure 4. (Figure 11 in orig.) RP-Reachability message packet format



Each RP will send RP-Reachability messages to all routers on its distribution tree for a particular group. These messages are sent so routers can detect that an RP is reachable. Routers that have attached host members for a group will process the message.


The RPs will address the RP-Reachability messages to 224.0.0.2.


Routers that have state for the group with respect to the RP distribution tree will propagate the message. Otherwise, the message is discarded. If an RP address timer expires, the router should attempt to send an PIM Join message toward an alternate RP provided for that group if one is available.



  • Group Address: Group address associated with RP.


  • Group Address Mask: A bit mask that allows the description of group ranges. Must be set to 255.255.255.255 when Group Address describes a single group address.


  • RP Address: The rendezvous point IP address of the sender.



Finally, in a future version of this document we will specify a new IGMP message type that allows hosts to advertise a list of 1 to n RP addresses associated with a particular group address.













    1.1 Overview











    Team-Fly

     

     

















    Documenting Software Architectures: Views and Beyond
    By
    Paul Clements, Felix Bachmann, Len Bass, David Garlan, James Ivers, Reed Little, Robert Nord, Judith Stafford
    Table of Contents

    Chapter 1. 
    The Module Viewtype







    1.1 Overview


    In this chapter and the next, we look at ways to document the modular structures of a system's software. Such documentation enumerates the principal implementation units, or modules, of a system, together with the relationships among these units. We refer to these descriptions as module views. As we will see, these views can be used for each of the purposes outlined in the Prologue: education, communication among stakeholders, and the basis for analysis.


    The concept of modules emerged in the 1960s and 1970s, based on the notion of software units with well-defined interfaces providing a set of services�typically, procedures and functions�together with implementations that either fully or partially hide their internal data structures and algorithms. More recently, these concepts have found widespread use in object-oriented programming languages and modeling notations, such as UML.


    Today, the way in which a system's software is decomposed into manageable units remains one of the important forms of system structure. At a minimum, it determines how a system's source code is partitioned into separable parts, what kinds of assumptions each part can make about services provided by other parts, and how those parts are aggregated into larger ensembles. Choice of modularization often determines how changes to one part of a system might affect other parts and hence the ability of a system to support modifiability, portability, and reuse.



    ADVICE


    Plan for your documentation package to include at least one view in the module viewtype.



    It is unlikely that the documentation of any software architecture can be complete without at least one view in the module viewtype.


    We begin by considering the module viewtype in its most general form. In the next chapter, we identify four common styles:



    • The decomposition style, used to focus on containment relationships among modules


    • The uses style, to indicate functional dependency relationships among modules


    • The generalization style, to indicate specialization relationships among modules


    • The layered style, to indicate the allowed-to-use relation in a restricted fashion between modules













      Team-Fly

       

       





      Top



      Summary









      Summary


      Windows supports a complete set of synchronization operations that allows threads and processes to be implemented safely. Synchronization introduces a host of program design and development issues that must be considered carefully, to ensure both program correctness and good performance.


      Looking Ahead


      Chapter 9 concentrates on multithreaded and synchronization performance issues. The first topic is the performance impact of SMP systems; in some cases, resource contention can dramatically reduce performance, and several strategies are provided to assure robust or even improved performance on SMP systems. Trade-offs between mutexes and CRITICAL_SECTIONs, followed by CRITICAL_SECTION tuning with spin counts, are treated next. The chapter concludes with guidelines summarizing the performance-enhancing techniques, as well as performance pitfalls.


      Additional Reading


      Windows

      Synchronization issues are independent of the OS, and many OS texts discuss the issue at length and within a more general framework.


      Other books on Windows synchronization have already been mentioned. When dealing with more general Windows books, however, exercise caution because some are misleading when it comes to threads and synchronization, and most have not been updated to reflect the NT5 features used here. One very popular and well-reviewed book, for instance, while consuming a large number of pages of prose, never mentions the need for volatile storage, does not explain the four event combinations adequately, and recommends the deadlock-prone multiple semaphore wait solution (discussed in the section on semaphores) as a technique for obtaining more than one semaphore unit.


      David Butenhof's Programming with POSIX Threads is recommended for in-depth thread and synchronization understanding, even for the Windows programmer. The discussions and descriptions generally apply equally well to Windows, and porting the example programs can be a good exercise.









        Software Installation Tools










         < Free Open Study > 











        Software Installation Tools


        This chapter has described a variety of tools and techniques for software installation. This section will describe the following tools and discuss the ones provided by most distributions:




        • GNU autoconf




        • Red Hat's RPM




        • Slackware's tarballs




        • Debian's dpkg format




        After reading this material, users will be familiar with how software is built and installed on a variety of distributions. This section probably isn't quite a complete reference, but it can be viewed as a sort of "quick guide" for using these tools. The most common uses are covered, but readers who need more extensive information should consult the documentation for the specific tools.




        Using GNU autoconf


        Many open source packages use the GNU autoconf tools to configure the software's compile-time options. GNU autoconf works by generating standard Makefiles from templates produced by the developers of the software. The auto-conf tools scan a system, looking for the presence or absence of specific features by compiling small test programs. The Makefiles that it generates are then used to automate the actual compilation of the software.


        GNU autoconf can be somewhat arcane to write templates for; fortunately, only the software developers have to worry about that, and the tools are fairly straightforward for users who are only interested in building a package. Usually, there will be a program called "configure" in the top directory of the source code tree; users simply run ./configure and the program will examine the system and construct Makefiles, header files, and similar files used by the make tool and by the source code.



        The ./configure program generally takes a number of arguments. The most common of these is the –prefix option, which sets the destination directory that the software will be installed into; for example, the command ./configure –prefix=/usr/local/my-software-1 will configure the software so that it is installed into /usr/local/my-software-1.0. Many packages support their own options via configure; users can check what options are available by executing the command ./configure –help. After the software has been configured in this way, users simply run make, which will build the software, and usually make install to complete the installation. A given package's documentation will explain the specific compile-time configuration options for that package, but Table 7-1 lists some common parameters used by GNU autoconf.









































        Table 7-1: Common Options for GNU autoconf's configure Program

        OPTION



        MEANING




        –help



        Displays the list of arguments and options supported by this package




        –prefix=[path]



        Causes the software to be installed in the directory [path]




        –host=[name]



        Specifies the architecture and platform of the computer building the software; for example, alphaev56-unknown-linux-gnu or i686-unknown-linux-gnu; used to specify that the software should be built for a different architecture than the default




        –with-include-dir=[path], –with-lib-dir=[path]



        Adds [path] to the list of directories containing header files or libraries for the build process




        –enable-[option]



        Activates the option named [option] in the software




        –with-[package]=[path]



        Instructs the build process to look for the package named [package] in the directory [path]; for example, –with-openssl=/usr/local/openssl-0.9.5a















        However, some packages treat a prefix of /usr as a special case. These packages will place binaries and libraries in /usr/bin and /usr/lib, but will place configuration files in /etc (rather than /usr/etc as might be expected). Watch out for these programs! To override this behavior, you can probably add an option such as –config-prefix=/usr/etc.


        GNU autoconf is an extremely powerful tool for writing portable software (that is, software that runs on multiple operating systems) since it can customize a Makefile for almost any Unix-like system. Because it's so powerful, it's also very widely used. These days, you'd be hard-pressed to find an open source software package that didn't use these tools! A working understanding of the autoconf tools is a huge benefit to any user or administrator of a Linux-based system.






        Using Red Hat's Tools


        The packaging system used by Red Hat Linux—the Red Hat Package Manager (RPM)—was described in Chapter 4. That chapter briefly mentioned the support RPM has for source packages. This section will describe that functionality in more detail.



        RPMs and Architectures


        Each RPM package has an "architecture" associated with it. Normally, this architecture denotes the hardware platform for which the package was compiled; for example, a .i386.rpm package is an RPM compiled for the Intel platform. A source RPM has the extension .src.rpm. Whereas a binary RPM is obviously only useful on platforms for which it was compiled, a source RPM can be built into a binary RPM and so is useful for a variety of tasks.





        Construction of Source RPMs


        Typically, a source RPM is constructed to match the configuration of a binary RPM. For example, along with the actual installation CDs, Red Hat also ships CDs containing source RPMs for each package included with the system. When built, these source RPMs will produce RPMs just like the ones included on the installation CDs. That is, rebuilding all the source RPMs will duplicate all the RPMs on the CDs. This is useful, for example, in building a copy of Red Hat Linux on a platform for which Red Hat does not deliver a distribution (though in practice it's seldom that simple).


        Each source RPM is built with a specific configuration. That is, the contents of the source RPM are set up with a predetermined set of compile-time options.



        How these options are specified varies by the software being packaged; the most common method, as mentioned earlier in this chapter, is through GNU autconf. These compile-time options are specified in RPM specification files with extensions of .spec. The .spec files can be customized to alter the configuration of an RPM.





        RPM Commands


        The RPM command can be used to build binary RPMs from source, and there are generally two ways to accomplish this. The quickest and simplest method is to rebuild a source RPM into a binary package with its default configuration. This can be accomplished with the command rpm –rebuild <filename>.


        The rpm –rebuild command recompiles a source package with its default configuration. In cases where a source RPM's settings need to be customized, a somewhat lengthier process can be used. Each source RPM usually contains a tarball containing the software, the specification file for the RPM, and sometimes supporting files (such as patches to be applied to the software). Installing a source RPM (via the usual rpm –install command described in Chapter 4) causes these files to be extracted and installed, and then they can be customized.





        Contents of the /usr/src/redhat Directory


        Installing a source RPM on a Red Hat system causes its contents to be placed in the /usr/src/redhat directory. This directory contains several subdirectories; the uses of these directories are described in Table 7-2.






































        Table 7-2: Red Hat Linux /usr/src/redhat Contents

        DIRECTORY



        CONTENTS



        BUILD



        Separate, temporary scratch subdirectories for packages as they are built from source code



        RPMS



        Completed (i.e., compiled) binary RPM packages of source RPMs that were successfully built



        SOURCES



        Source code tarballs and patches used to build software extracted from source RPMs that are installed



        SPECS



        Specification files for building binary RPM packages from source code installed in SOURCES



        SRPMS



        Copies of source RPMs (SRPMS) that have been installed, for later reference









        After a source RPM is installed, it can be customized by editing its .spec file, which will be placed in /usr/src/redhat/SPECS. After editing the RPM's .spec file, the rpm -ba <filename> command can be used to instruct RPM to build the package, including all stages. (There are several stages to an RPM build, from configuration to installation, and each phase can be specified independently, though usually this isn't necessary.) The .spec file will be examined and its contents (which are much like a script) will be executed to compile the package. During this process, it will attempt to locate the tarball for the source code in /usr/src/redhat/SOURCES, and when the process is complete, it will place the new binary RPM in /usr/src/redhat/RPMS.


        In most cases, system administrators and users need to alter a few configuration options, such as disabling a compile-time feature or altering a path. These needs can be met by the preceding techniques. For more complicated tasks, though, users should consult Red Hat's extensive RPM documentation at http://www.redhat.com. (Alternatively, the Maximum RPM reference book is included in digital form on the documentation CD of recent Red Hat Linux distributions.)








        Using Slackware's Tools


        The Slackware Linux distribution was discussed in Chapter 5. Slackware's packaging mechanism is based on simple tarballs, which are standard compressed Unix tar (Tape Archive) files. Typically these files have extensions of .tar.gz or .tar.Z. Since it uses such a simple format, Slackware has limited capability to add extensive support for source code packages.


        The typical method for installing a package from source code on a Slackware Linux system is therefore pretty simple. The administrator simply builds the package manually, by whatever mechanism is required, and then constructs a Slackware package from it and installs it via the standard tools. Typically, this involves running the ./configure script that is provided with most packages.


        For example, you might install the hypothetical package my-software to the directory /opt/my-software-1.0 by following these instructions:




        1. Download the source code tarball, and consult the documentation for compile-time options.




        2. Extract the software source code into a convenient directory, such as /usr/src/my-software. Choose a temporary directory to "install" the software to, such as /tmp/my-software-tmp/opt/my-software-1.0. This will not be the permanent location, but is instead passed to the command that follows.




        3. Run the ./configure program with the appropriate arguments, in particular the –prefix option (e.g., ./configure –prefix=/tmp/my-software-tmp/opt/my-software-1.0).




        4. Build the software via the make command; when it completes, "install" it into the temporary directory via the make install command.




        5. Enter the temporary directory—that is, cd /tmp/my-software-1.0.




        6. Create a Slackware package from the temporary directory, via a command such as makepkg /tmp/my-software-1.0.tgz. This command will create the Slackware package (which is simply a standard tarball with some extra information).




        7. Install the newly created package via the command installpkg /tmp/my-software-1.0..




        Some readers may wonder why the software was built and installed into a temporary directory such as /tmp/my-software-1.0 in steps 3 and 4. The other alternative would have been to directly install the software in its ultimate location (such as /opt, /usr/local, or the root directory). However, if this approach is used, the software will be installed in its permanent location, without the knowledge of the packaging tools. This means that the tools won't be able to uninstall, upgrade, or otherwise manage the package. Instead, the software is installed in a temporary scratch directory, so that the makepkg tool can be run to locate and consolidate all the files. When the package is installed, the tools will know about it and therefore be able to manage it. (In reality, you could always just edit the contents of /var/log/packages directly, but that's a lot of work and defeats the purpose of the tools in the first place!)


        One last thing to note about the installation process is the actual temporary directory chosen. In this example, it was /tmp/my-software-tmp/opt/my-software-1.0. The reason for this is to make sure that the software is installed into its temporary directory in such a way that the makepkg tool can properly create the package. The tool will include all files and subdirectories contained in the current directory in the resulting package; thus, by installing the software into the temporary /tmp/my-software-tmp/opt/my-software-1.0 and running the command from /tmp/my-software-tmp, the package will be created so that all files are stored in opt/my-software-1.0. Since installpkg will install the files relative to the root directory, this will cause the package contents to be placed in /opt/my-software-1.0—which was the original goal. Similarly, if the goal was to install into /usr/local, meanwhile, the scratch directory would have been /tmp/my-software-tmp/usr/local.



        Slackware's tools are very simple, in keeping with the Slackware philosophy. This makes them easy to use but still leaves the administrator a bit more work to do. (Compare this example for Slackware with the shorter rpm –rebuild command used on Red Hat Linux systems.) On the other hand, even though Slackware's tools may require a bit more work when building software from source code, they're a lot easier to understand and don't require as much study as more sophisticated tools.






        Using Debian's Tools


        Building Debian packages is accomplished using the dpkg-deb program included with Debian GNU/Linux. This tool has similarities to the approaches of both RPM and Slackware's system. Specifically, Debian's system has a custom format for archives, but it generally builds such packages by simply processing and storing up the contents of a particular directory. The custom format makes Debian's system similar to RPM, in that you can perform operations like install, uninstall, and query on packages, and the system itself keeps track of dependencies for you. However, creating such packages involves just archiving the contents of the (specially prepared) current directory, which is similar to Slackware's approach.






        Cross-Reference 


        Creating Debian packages is also discussed in the "Manipulating Debian Package Archives" section in Chapter 6.



        For example, suppose you wanted to create a Debian package for the hypothetical "my-software" package considered in the previous section on Slackware's tools. At a very high level, the step for building such a package for Debian is very similar to the seven steps outlined in the Slackware section. The main differences are that instead of using the makepkg command in step 6, you'd use the command dpkg-deb –build /tmp/my-software-1.0.deb. Similarly, once you've created the package, you would use the command dpkg -i /tmp/my-software-.0.deb to install it, rather than the installpkg command in step 7.


        As you can see, creating Debian packages from source code is conceptually fairly similar to the equivalent process on Slackware Linux. On the other hand, once you have the file in hand, you can perform various queries on it (such as listing its contents, or fetching its description) as you could with an RPM. However, this story isn't quite complete, and there is another way in which Debian's system is similar to Red Hat's.



        Specifically, the process just outlined omits a very important step—namely, the creation of the required files and directory for Debian archives. In order to run correctly, the dpkg-deb command expects to find a directory named "debian" as a subdirectory of the tree you wish to build into a Debian package. This directory contains several additional files, which are summarized in Table 7-3.



































        Table 7-3: Debian Archive Files

        FILE



        PURPOSE



        control



        Contains basic information about the software being packaged, such as its version, description, and dependencies



        rules



        A script used to prepare the directory's contents for packaging



        changelog



        Lists a history of changes made to the package



        copyright



        Identifies the copyright holder and related issues








        You've probably realized by now that when taken together, the files outlined in Table 7-3 amount to the equivalent of Red Hat's ".spec" files for RPM packages. These files are used by the dpkg-deb command to prepare the directory and construct the Debian package from its contents. Thus, while mechanically the process has similarities to Red Hat (in that it builds an archive from the contents of the current directory), it also has strong similarities to Red Hat. Clearly, there's more than one way to skin this particular cat. (Most commercial Unix flavors usually each have yet another way of tackling this problem.)


        Unless you're a software developer, you may not need to create Debian packages at all. As a user, all you probably need to know about this process is that it's not as easy to rebuild a Debian package from source code as it is an RPM, since there is no ready-packaged "source .deb" akin to a "source RPM." Since this isn't a book on software development or how to build Debian packages, if you need more detail than that you should consult the Debian Project's documentation, which you can find at http://www.debian.org/doc/manuals/programmer/index.html.




















         < Free Open Study > 



        5.3 Complex products











         < Day Day Up > 











        5.3 Complex products




        5.3.1 Structures of complex products


        Most relevant products consist of many components and are developed as complex systems. A complex system consists, per definition, of many parts (called subsystems or components), and to cope with this complexity its development process is performed by different teams. The teams use different technologies and procedures to develop their specific type of documents and components. The result from each team is assembled on the system level to give a final product ready for production or delivery. Common to all product development activities is the necessity to manage data on the system level, between the teams, and within the teams.


        On the system level, in addition to information about components, information about the contents of the products, customers, vendors, suppliers, baselines, releases, prices, and markets are needed. The project manager must know if the project is following the time schedule. The CM manager needs to know the current product configuration and its status, as well as related documents for the entire system and for all components (subsystems) included in the system. The designer must have access to requirements documents, project specification, and all information related to the product to be developed. The production engineer needs to find all documents related to a product ready for manufacturing. The sales person needs to find information about the product to present to a customer. There are many different roles within the company that need access to the right information about the right product at the right time. All of this data, created by different subsystems, must be available on a system level.



        Figure 5.5 shows examples of systems and subsystems of different kinds of products. Figure 5.5(a) shows a system containing three different subsystems developing-mechanical, ASIC, and electronic. Products developed from the different subsystems will be integrated and tested on a system level before the product will be manufactured and shipped to customers. Figure 5.5(b) shows a pure software product in which all subsystems develop different parts (components) [4] of the software. The software will then be integrated and tested on the system level before it will be available for delivery to customers. While the required support at the subsystem level is strongly software related, at the system level the support required is more similar to the support for hardware products. On the system level, mostly data about the products (i.e., metadata) is used, rather than direct product data. Figure 5.5(c) shows an example of a complex product that includes both software and hardware components. Once again, the procedures, technologies, and hence the support required are specific at the subsystem level, but common at the system level. At the system level, the differences between the subsystems are disregarded and each subsystem is treated in the same way, irrespective of whether it is a software or a hardware component.






        Figure 5.5: Product development configurations.






        5.3.2 Information flow


        The information flow is not directed only from the subsystem level to the system level. In the beginning of the development process, much of the information that will be used at the subsystem level is generated at the system level. A development process begins at the system level in which the overall goals, constraints, and requirements are identified, and an overall design of the system is prepared. Further, the system is developed by a successive division into subsystems, which can be developed in parallel, followed by their integration. This process is shown in Figure 5.6, in which we distinguish processes from life cycle models. A process consists of activities, while a life cycle is characterized by states and milestones. A system or a component is in a state during the performance of particular activities. The activities are concluded when their goals, which are indicated by milestones, are achieved. The entire process can be divided into three parts-a common part in which activities related to the system are performed and information needed later in all subprocesses is obtained, an independent part in which the subprocesses are progressed in parallel and the information in each subsystem is generated independently of other subprocesses, and finally an integrated part in which the information from all processes must be accessible and integrated in common information.






        Figure 5.6: Complex PLC.

        In Figure 5.6 we can also see when, at certain points in the development life cycle, common activities are divided into separate activities and when they are merged together again. At these points, it is important that all system-related information is easily accessible and that it is possible to import this information into the tools used in the activities in the subsystems. Figure 5.1 and Figure 5.2 show the information flow through the phases of the parallel subprocesses. From these figures, we can summarize that we need the information flow of the following assets:




        • From common activities to the parallel hardware and software activities, we need requirements, CRs, and overall system design (defined partly by a product structure). We also need information related to the project management.




        • From the parallel activities to the integration phase, we need the refined requirements, final detailed design, and final deliverables (executable code for software, prototype specifications for hardware, and documentation for both).




        From this, we can conclude that both hardware and software development processes should follow common procedures for product structure management, RM, change management, and document management in general.






        5.3.3 Integration


        PDM tools deal with metadata and have advanced functions for data retrieval, data classification, and product structure management. This implies that PDM tools are suitable for managing the system level. For development of hardware products, PDM includes or is well integrated with many development tools (e.g., CAD/CAM). In this way, PDM supports procedures at the subsystem levels. However, there is no, or inadequate, integration of PDM tools with software development environments. This means that for a product configuration shown in Figure 5.5(a), a PDM tool can be used successfully, while this is impossible for products of the types shown in Figure 5.5(b, c).


        SCM tools are, of course, not integrated with hardware development environments and therefore do not support product configurations as shown in Figures 5.5(a) (pure hardware) and 5.5(c) (hardware and software). The question is whether SCM can support pure software products. SCM tools do not provide complete support on the system level. As a product becomes increasingly complex, many activities at the system level are not strictly related to the pure software domain, and the efficiency of SCM support then becomes much less than at a subsystem (development) level.









        [4]There is a difference between subsystems and components. A subsystem is an integral part of the system, while a component can be a part that is independent of the system. A component can be used in different systems, developed independently of the systems, and easily replaced. Most of the hardware products are developed from components. This is also a noticeable trend in software development, although many concepts of component-based development of software systems are not yet precisely defined and established in practice.



















         < Day Day Up > 



        Section 1.1.&nbsp; Tell Everyone the Truth All the Time










        1.1. Tell Everyone the Truth All the Time


        The most important principle in this book is transparency
        . A project manager constantly makes decisions about the project. If those decisions are based on real information that's gathered by the team and trusted by management, that's the most likely way to make sure the project succeeds. Creating a transparent environment means making all of that information public and explaining the rationale behind your decisions. No software project goes exactly as planned; the only way to deal with obstacles is by sharing the true nature of each problem with everyone involved in the project and by allowing the best solution to come from the most qualified people.


        But while anyone would agree with this in principle, it's much harder to keep yourself and your project honest in practice. Say you're a project manager, and your project is running late. What do you do if your bossmuch to your surpriseannounces to the world that your project will be done on time? Unfortunately, when faced with this situation, most project managers try to change reality rather than deal with the truth. It's not hard to see why that approach is appealing. Most people in software engineering are very positive, and it's not hard to convince them that an unrealistic deadline is just another technical challenge to be met. But the passage of time is not a technical challenge, and if the expectations are unrealistic, then even the most talented team will fail to meet them. The only real solution to this problem is to be open and honest about the real status of the projectand that's going make your boss unhappy.


        And so, instead of telling the truth, many project managers faced with a deadline that's clearly unrealistic will put pressure on their team to work late and make up the time. They silently trim the scope, gut quality tasks, start eliminating reviews, inspections, and pretty much any documentation, and just stop updating the schedule entirely. And, above all, they wait until the very last minute to tell everyone that the project is late.., hoping against hope that luck, long hours, and a whole lot of coffee will correct the situation.


        And sometimes it works... sort of, until the users have to work around bugs or missing features, until programmers have to start patching the software, and until managers have to go on a charm offensive in order to smooth over rough relations among everyone involved. Even if the deadline was met, the software was clearly not really ready for release. (And that's assuming the team even managed to squeeze it out on time!)


        That's why the most important part of building better software is establishing transparency. It's about making sure that, from the very beginning of the project, everyone agrees on what needs to be built, how long it will take to build it, what steps will be taken in order to complete the project, and how they will know that it's been done properly. Every tool, technique, and practice in this book is based on the principles of freely sharing information and keeping the entire project team "in the loop" on every important decision.












        COM











        COM


        The Component Object Model (COM) and Distributed Component Object Model (DCOM) facilities in Windows provide a framework for developing language- and location-independent components. These components can be created and accessed from within a process, between different processes on the same computer, or remotely over a network.


        Note



        COM has become an umbrella term that encompasses DCOM (remote COM) and other COM-related technologies. Previously, the term COM referred to object access and manipulation between different processes on the same computer; DCOM extended this functionality to make objects accessible over the network. Presently, they can all be referred to as COM technologies.




        COM is essentially an object-oriented wrapper for RPC; in fact, DCOM uses RPC for method invocation and communication. For the purposes of this discussion, COM and DCOM are viewed more as extensions of RPC. These similarities can help you apply what you've already learned about RPC.



        COM: A Quick Primer


        The following sections give you a brief rundown of the COM architecture, in case you have limited experience with COM programming. These basics are essential to understanding the information that follows on potential security issues in COM applications.



        Components

        COM promotes the development of reusable components, much like the use of classes in object-oriented programs. Each component provides an interface (or several interfaces) that describes a series of methods for manipulating the object. In the context of COM, "interface" refers to a contract between COM objects and their clients. This contract specifies a series of methods the object implements.


        There are some major differences between a COM object and a class in an object-oriented program. COM objects are already precompiled and are accessible system-wide to any process that wants to use them. They are language independent and available to any application without having to be recompiled. Indeed, COM is a binary specification of sorts; it requires that objects export interfaces in a certain manner but doesn't care about the internal structure of how those objects can be implemented. In addition to being accessible to any language, COM objects can be implemented in a variety of languages; their internals are irrelevant as long as they adhere to their contracts.


        COM objects are uniquely identified on the system by a globally unique identifier (GUID) called a class ID (CLSID). When a COM object is registered on the system, it adds a key to the registry with the same name as the object's CLSID. This key is stored in HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID.


        Note



        The HKEY_CLASSES_ROOT key is an alias for the HKEY_LOCAL_MACHINE\Software\Classes\CLSID, so the same CLSIDs can also be found at HKEY_CLASSES_ROOT\CLSID.




        These keys are installed so that the COM subsystem can locate and instantiate objects as they're requested. You can view registered COM objects on the system with the Registry Editor (regedit.exe), shown in Figure 12-2.




        Figure 12-2. Viewing COM objects with Regedit

        [View full size image]




        As you can see, quite a few subkeys and values are installed for each CLSID; they're described as needed in the following discussion.


        Because CLSIDs are hard to remember and aren't meaningful to people, COM objects often have namesaliases that can be used to refer to the object in place of the CLSID. These aliases are called program IDs (ProgIDs) and are entirely optional. A program ID is stored in the ProgID value in the HKEY_LOCAL_MACHINE\Software\Classes\CLSID\<CLSID> key. A program ID can have any format, but the MSDN-recommended format is Program.Component.Version. For example, one of the Microsoft Excel component is named Excel.Sheet.8. Of course, it would take a long time to look up program IDs if every CLSID key were queried to see whether its ProgID matches a request, so another key is used for forward lookups: HKEY_LOCAL_MACHINE\Software\Classes\<ProgID>. This key has a CLSID value that points to the ProgID's associated class.


        COM objects operate in a client/server architecture; the endpoints of a COM connection can be different threads in the same process, threads in different processes, or even on different systems. An exposed COM interface is accessed in much the same way an RPC function is called. In DCOM, this launching process includes starting applications if necessary, applying security permissions, and registering DCOM applications as being available on certain endpoints.


        A COM object can be an in-process server or out-of-process server. In-process servers are implemented in DLLs that are loaded into the client process's address space on instantiation. For the most part, you don't need to worry about in-process servers because they are in the caller's address space and security context. Of course, ActiveX controls represent a special case of an in-process server, and they are discussed in "ActiveX Security" later in this chapter.


        An out-of-process server, however, runs inside its own process space. There are two types of out-of-process servers: local servers on the same system as the caller and remote servers on another machine. Communication is performed via IPC primitives exposed by the COM runtime. In fact, DCOM uses RPC to transport messages behind the scenes. An out-of-process server can potentially run in a different context from the client, so it might have additional security considerations.




        Interfaces

        The whole point of COM objects is that they expose interfaces that are accessible to any clients that can use their functionality. A COM object can expose any number of interfaces, which consist of a series of functions related to the task. Each interface has a registered interface ID (IID) that uniquely identifies the interface. IIDs are recorded in the registry at HKEY_CLASSES_ROOT\Classes\Interface\<Interface ID>.


        This key contains a series of subkeys for each registered interface. As a code auditor, you need to examine these interfaces to see what attack surface they expose.


        Each COM interface is derived directly or indirectly from a base class called IUnknown, which provides a generic method of interaction with every COM object. Every COM object must provide an interface with the following three methods:


        • QueryInterface() Used to retrieve a pointer to a COM interface, given the IID of that interface

        • AddRef() Used to increment the reference count of an instantiated object

        • Release() Used to decrement the reference count of an instantiated object and free the object when the reference count drops to zero


        The QueryInterface() method is the real core of the IUnknown interface. It provides the capability to acquire instances of other interfaces the COM object supports. When reading COM documentation and technical manuals, you often encounter references to IUnknown. For example, the CoCreateInstance() function takes LPUNKNOWN type as a parameter, which allows the function to create an instance of any COM object because all COM objects are derived from IUnknown.




        Application IDs

        A collection of COM objects is referred to as a COM application or component. Each COM application has a unique ID, called an AppID, used to uniquely refer to a COM application on the system. Like CLSIDs, AppIDs are installed in the registry and contain a number of subkeys and values for per-application security settings. The AppID key provides a convenient location for enforcing security for applications hosting multiple COM objects. AppID keys are located in the registry at HKEY_LOCAL_MACHINE\SOFTWARE\Classes\AppId.


        Note



        AppID keys are also accessible at HKEY_CLASSES_ROOT\AppId.






        Mapping CLSIDs to Applications

        You've learned how to look up registered COM objects in the registry, but how do you find the implementation of each object? This information can also be found in the registry. The HKEY_LOCAL_MACHINE\Software\Classes\CLSID\<CLSID> keys have one or more of the following values, depending on the threading capabilities of the COM object. The values of interest are as follows:


        • InprocHandler32 or InprocHandler Used to indicate a handler DLL that provides the COM API interface; this DLL is normally ole32.dll (or ole2.dll for 16-bit servers). It's rare, although possible, for a COM server to specify its own handler.

        • InprocServer32 or InprocServer Used to indicate a server DLL that houses the implementation of the COM object. This value is used when the COM object is an in-process server.

        • LocalServer32 or LocalServer Used to indicate an executable that houses the implementation of the COM object. It's used when the COM object is an out-of-process server.




        OLE

        Object Linking and Embedding (OLE) is the predecessor to modern Windows COM. The original version of OLE uses DDE to allow interaction between components of different applications. This functionality is still part of the basic COM infrastructure, although it doesn't affect the discussions of DCOM. However, it's worth mentioning this relationship because the term "OLE" appears in many COM functions and data types.





        Automation Objects

        Automation objects are a special subclass of COM objects that originally provided a simpler form of IPC for controlling another application (referred to as an automation server). For example, Internet Explorer and Microsoft Word expose automation interfaces that allow clients to completely control the application and documents it contains. Automation servers generally expose scriptable methods, which are methods called through an IDispatch interface accepting VARIANT arguments. This interface is compatible with scripting languages because it doesn't use language specific elements such as object vtables and typed parameters. When a script invokes a method on an object, the scripting engine can use the IDispatch interface to ask for the unique ID of a method. The ID is then passed along with an array of VARIANT arguments via the IDispatch::Invoke() method.




        Threading in COM

        Windows evolved from a simple single-threaded OS to a true multiuser, multithreaded OS. This evolution has required some scaffolding to allow older, thread-unsafe COM objects to function properly in multithreaded versions of Windows. This scaffolding is provided in the form of apartments.


        The historical version of COM is the single-threaded apartment (STA); a COM process can have any number of STAs, with each one running on a separate thread. The STA uses DDE to perform method calls on objects, thus requiring a window message pump to function. The advantage of using the STA is that it synchronizes all messages processed by the application. This synchronization makes it fairly easy to implement a basic single-threaded COM object. From a security perspective, an STA COM object presents unique concerns only if it's running in a privileged context on an interactive desktop. These issues have been discussed previously in the sections on window messaging and shatter vulnerabilities.


        The multithreaded apartment (MTA) is also referred to as the free threaded apartment; a COM process has at most one MTA shared across all MTA objects in the process. The COM subsystem makes direct use of the object vtable when dispatching methods in an MTA, so it doesn't require any mechanism for handling window messages. Of course, this means COM method calls provide no guarantee of sequencing or serialization for an MTA.


        A thread must set its apartment model before calling any COM functions. This is done by calling CoInitializeEx(), which has the following prototype:


        HRESULT CoInitializeEx(void *pReserved, DWORD dwCoInit)


        The dwCoInit argument dictates whether the thread enters a new STA or enters the process's MTA. It can take the following values:


        • COINIT_MULTITHREADED Indicates the thread enters the MTA.

        • COINIT_APARTMENTTHREADED Indicates the thread should create a new STA.


        Of course, an in-process server has no way of knowing what model its client process is using, so it can't rely on CoInitializeEx() for properinitialization. In this case, the in-process server must specify at registration what threading models it supports, which is done in the registry value HKEY_CLASSES_ROOT\Classes\<CLSID>\InprocServer32\ThreadingModel.


        The in-process server can specify one of three options in this value:


        • Apartment The STA model.

        • Free The MTA model.

        • Both An STA or MTA.


        When an object is created, the COM runtime examines this registry key and tries to put the object in an existing MTA. If the correct apartment isn't present, COM creates a new one of the required type. If this value isn't present, the COM runtime assumes the in-process server requires the STA model.


        Threading issues come into play when more than one thread can operate on an object; that is, more than one thread is in the same apartment as the object. This issue occurs in-process when both the client and server run in an MTA; however, it can occur out-of-process with an MTA server accessed by more than one client of any type. In both cases, COM developers must make the server object thread safe because any number of threads can be operating on it simultaneously.


        One more important detail on COM threads is how the COM subsystem manages threads. Like RPC, the COM subsystem manages calls via a pool of worker threads. This means a call can occur on any thread, and developers can't assume that calls in sequence occur on the same thread. So a COM MTA can have no thread affinity, which means it can't make any assumptions about its thread of execution between calls. Threading issues in general are a complex topic, covered in depth in Chapter 13. Keep threading issues in mind when auditing COM objects in the MTA model.




        Proxies and Stubs

        COM objects can't directly call routines between different apartment models or across process boundaries. Instead, COM provides an IPC method in the form of proxies and stubs. Much like RPC requests, the COM subsystem handles calling remote components and marshalling data. In fact, DCOM uses the native Windows RPC mechanisms for its COM remoting.


        On the client side, the code that bundles the data and sends it to the server is referred to as an interface proxy (or sometimes just "proxy") because it looks and acts exactly like the real object to the caller. The proxy has the same interface as the real object. The fact that the proxy is just a stand-in is transparent to the rest of the client application.


        The server code responsible for decapsulating a request and delivering it to the server application is called a stub. A server application receives a request from a client stub and performs the necessary operations. It then returns a result to the stub, which handles all marshaling and communications.




        Type Libraries

        The easiest method of deploying and registering a COM component generally involves using type libraries. A type library describes all the interface and typing information for COM objects. It can include a variety of information, such as COM object names, supported interfaces, method prototypes, structures, enumerations, and relevant GUIDs for interfaces and objects. Developers can use type libraries to incorporate components into their applications with minimal effort.


        Each type library can be registered with the system. Like interfaces and COM classes, they are given a unique GUID to ensure that each type library can be identified. Type library IDs are stored in the registry in HKEY_CLASSES_ROOT\Classes\Typelib, with subkeys identifying the location of the type library. In addition, CLSIDs and interfaces can indicate that a type library applies to them by using the Typelib subkey in their locations in the registry.


        Type libraries can be in a standalone file (usually with the extension .tlb) or included as a resource in a DLL or executable. As you see later in "Auditing DCOM Applications," type libraries provide a wealth of essential information, especially when you don't have access to the source code.





        DCOM Configuration Utility


        The following sections focus on programmatic configuration of DCOM applications. You can also use the DCOM Configuration utility to view and manipulate the registered attributes of DCOM components. To run this utility, type dcomcnfg.exe at the command line or in the Run dialog box. In Windows XP and later, this command starts an instance of the Microsoft Management Console (MMC), as shown in Figure 12-3.




        Figure 12-3. Viewing all registered DCOM objects

        [View full size image]




        The DCOM Configuration utility can be used to manipulate all DCOM-related security settings, including the base subsystem security, default component security, and individual component security. This utility should be your starting point for reviewing an installed DCOM application. The Properties dialog box for a COM object shows you the application name, the application ID, security permissions associated with the object, and more useful tidbits of information you need to evaluate application exposure (see Figure 12-4).




        Figure 12-4. Viewing properties of COM objects












        DCOM Application Identity


        Unlike local COM, a remote COM server often doesn't run under the access token of the launching user. Instead, the base identity is designated by the DCOM object's registration parameters. A DCOM server can run in these four user contexts:


        • Interactive user This context causes the application to run as whichever user is currently logged on. If no users are logged on, the application can't be started.

        • Launching user This context causes the application to run with the credentials of the user who's launching the server. If no identity is established in the registry, this context is the default setting.

        • Specified user This context causes the application to be launched by using a specific user's identity, no matter who the launching user is. The credentials of the target user are required to configure this context.

        • Service The application DCOM server is hosted inside a service and runs under a local service account.


        Generally, running as the launching user is the simplest, most secure option. This context causes the application to impersonate the launching user; however, accessing objects across the network from the server fails in Windows 2000 and earlier because of the lack of impersonation delegation. Long-lived COM servers might require running under a local service account or a specified account. In Windows XP and later, the network service account is often used. Developers can also create a tightly restricted account for the DCOM object.


        The most dangerous application identity is probably the interactive user because any method of running arbitrary code results in unrestricted impersonation of the interactive user. This identity is especially dangerous if the COM interface allows remote access. If you encounter this identity setting, examine all interfaces closely. Pay special attention to any capabilities (intentional or otherwise) that allow code execution or arbitrary file and object manipulation.




        DCOM Subsystem Access Permissions


        Starting with Windows XP SP2 and Windows Server 2003 SP1, Microsoft provides granular system-wide access control for DCOM, which can be accessed through the DCOM configuration in the System Properties dialog box. To manipulate these system-wide settings, click the Edit Limits buttons on the Security tab. These configuration parameters supersede the default and component-specific settings, so they can be used to completely restrict DCOM access. The access rights are summarized in Table 12-5.


        Table 12-5. COM Object Access Rights

        Access Right

        Meaning

        COM_RIGHTS_EXECUTE

        Allows users to make calls on a COM interface.

        COM_RIGHTS_EXECUTE_LOCAL

        Required to allow local clients to make calls on a COM interface.

        COM_RIGHTS_EXECUTE_REMOTE

        Required to allow remote clients to make calls on a COM interface.

        COM_RIGHTS_ACTIVATE_LOCAL

        Required to allow local clients to activate the interface.

        COM_RIGHTS_ACTIVE_REMOTE

        Required to allow remote clients to activate the interface.



        The COM_RIGHTS_EXECUTE right is required for remote COM to function at all. The default assignment of the remaining rights allows only administrators to activate and launch remote COM objects. However, all users are allowed to launch local COM objects and connect to existing remote objects. Earlier versions of Windows support only the COM_RIGHTS_EXECUTE permission.




        DCOM Access Controls


        You've already learned how RPC can use native Windows access control mechanisms to provide fine-grained authentication and authorization. DCOM makes use of this same infrastructure for its own access control features. However, DCOM authorization comes into play in a slightly different manner: at activation time and call time.



        Activation

        A DCOM object must be instantiated before a client can receive an interface pointer to it and before any of its methods can be called by that client. Usually, this instantiationcalled activationis done via RPC. The RPC subsystem locates the DCOM server a client is trying to access and launches it if it's not already running.


        The Service Control Manager (SCM) determines whether the requesting principal is allowed to launch the object by examining the launch permission ACL for the requested class. This ACL is maintained in the registry key HKEY_CLASSES_ROOT\APPID\<APPID>\LaunchPermission.


        The LaunchPermission value might be absent if no special permissions are required. If so, the class inherits the default permissions. This ACL is stored in the system registry at HKEY_LOCAL_MACHINE\Software\Microsoft\OLE\DefaultLaunchPermission.


        Note



        A DCOM server can't set launch permissions programmatically for the current call. Generally, the installing application or system administrator sets these permissions programmatically or with the DCOM Configuration utility. Therefore, insufficient launch permissions fall into the operational vulnerability classification.







        Invocation

        After a DCOM object is activated, developers can apply additional levels of control by enforcing call-level security, which controls the principals allowed to make interface calls on a specific object. There are two ways to enforce call-level security: through registry key settings and programmatically. The first method involves consulting the registry. First, the ACL for the application is checked, which is in the registry key HKEY_CLASSES_ROOT\APPID\<APPID>\AccessPermission. If this value is absent, application access has no special security requirements, and the default ACL is applied from the Registry key HKEY_LOCAL_MACHINE\Software\Microsoft\OLE\DefaultAccessPermission.


        These registry keys are set manually or via the DCOM Configuration utility. The other way to enforce call access permissions is programmatically with the CoInitializeSecurity() function:


        HRESULT CoInitializeSecurity(PSECURITY_DESCRIPTOR pVoid,
        LONG cAuthSvc, SOLE_AUTHENTICATION_SERVICE *asAuthSvc,
        void * pReserved1, DWORD dwAuthLevel, DWORD dwImpLevel,
        SOLE_AUTHENTICATION_LIST *pAuthList,
        DWORD dwCapabilities, void * pReserved3)


        The CoInitializeSecurity() function gives developers extensive control over the basic security of COM objects. The security measures this function puts in place are process wide; that is, if a process has multiple DCOM object interfaces exposed, all interfaces are affected by a call to this function. The first argument actually provides the majority of the security capability. Although the prototype indicates that this argument is a pointer to a security descriptor, it can also point to two other structures: an AppID structure or an IAccessControl object. When an AppID structure is specified, the relevant AppID is located in the registry and permissions are applied according to the subkey values stored there. An IAccessControl object is a system-provided DCOM object that supplies methods for enforcing restrictions on other interfaces. The client can call CoInitializeSecurity() only once, and any attempt to call it again fails.


        Note



        Remember that CoInitializeSecurity() restrictions are applied to every interface the calling process has registered.




        In addition to security descriptor settings, quite a few other security restrictions can be put in place with CoInitializeSecurity(). The dwAuthLevel parameter can also be used to enforce certain authentication levels. DCOM uses the same authentication levels as RPC, so they aren't repeated here. Refer to the "RPC Servers" section earlier in this chapter for details on these authentication levels.


        The downside of CoInitializeSecurity() is that it can be called only once and affects all DCOM calls in the current process. However, to modify authentication behavior on a per-proxy basis, clients can also use the CoSetProxyBlanket() function, which has the following prototype:


        HRESULT CoSetProxyBlanket(IUnknown * pProxy, DWORD dwAuthnSvc,
        DWORD dwAuthzSvc, WCHAR * pServerPrincName,
        DWORD dwAuthnLevel, DWORD dwImpLevel,
        RPC_AUTH_IDENTITY_HANDLE pAuthInfo,
        DWORD dwCapabilities)


        This function operates similarly to CoInitializeSecurity(), except the authentication parameters affect only the proxy indicated by the pProxy argument rather than every proxy interface a client uses. Also, unlike CoInitializeSecurity(), CoSetProxyBlanket() can be called more than once.





        Impersonation in DCOM


        DCOM allows servers to impersonate clients by using the underlying RPC implementation. A DCOM application enforces impersonation levels programmatically and through the use of registry settings. Registry settings provide initial security requirements, but they can be overridden programmatically while the application is running. You might have noticed that both CoInitializeSecurity() and CoSetProxyBlanket() have a dwImpLevel parameter. This parameter allows clients to specify the impersonation level, and it works just as it does in RPC. This parameter is simply passed to the underlying RPC transport, discussed earlier in this chapter. However, impersonation can be performed only if the authentication level is RPC_C_IMP_LEVEL_IMPERSONATE or higher; the default value is C_IMP_LEVEL_IDENTIFY.


        In addition to the standard IPC impersonation issues, DCOM objects might be more at risk from impersonation attacks. As Michael Howard and David Leblanc point out in Writing Secure Code, a server application is likely to act as a client when an event source/sink pair is set up and interfaces are passed as arguments to a server process.


        For those unfamiliar with sources and sinks, they are older COM mechanisms for handling asynchronous events through the use of connection points. A connection point is simply a communication channel an object can establish with another object. You've seen examples of the client making calls to a server and receiving a result immediately. Sometimes, however, the server needs to advise the client that an event has occurred. This event might be based on a user action, or it might indicate that a time-consuming operation is finished. In this situation, the client exposes its own COM interface and passes it to the server. When the server wants to indicate an event occurred, it simply calls a method in this interface. To do this, the server must be a connectable objectthat is, expose the IConnectionPoint interface (among several others). The server's outgoing interface for a connection point is called a source, and the client's receiving interface is called a sink. The problem with this process is that the server is now a client, and its impersonation level is just as important as the client's. If a malicious client connects to an unprotected server, it can use CoImpersonateClient() in its sink interface to steal the server's credentials. Remember, the server needs to set fairly lax permissions to be vulnerable to this type of attack, as in the following example:


        BOOL InitializeCOM(void)
        {
        HRESULT rc;

        rc = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);

        if(FAILED(rc))
        return FALSE;

        rc = CoInitializeSecurity(NULL, -1, NULL, NULL,
        RPC_AUTHN_LEVEL_NONE, RPC_C_IMP_LEVEL_IMPERSONATE,
        NULL, 0, NULL);

        if(FAILED(rc))
        Return FALSE;

        return TRUE;
        }


        If a server (or a client) for a connectable object initializes COM security as in this example, impersonation vectors are a definite threat because they might allow connecting clients to steal credentials. This type of attack is one of the main reasons for Microsoft's introduction of COM cloaking and RPC_C_IMP_LEVEL_DELEGATE.





        MIDL Revisited


        MIDL was introduced in "Microsoft Interface Definition Language" earlier in this chapter. IDL is primarily intended to express RPC interfaces, but it can also be used to describe COM interfaces. In fact, the MIDL compiler has language support for the Object Description Language (ODL), which can be used to represent objects as well as RPC interfaces. When auditing COM applications, you might see some COM object interfaces expressed in IDL, so this section reviews some of the main attributes and keywords for expressing COM objects.


        The most important difference between COM ODL and RPC IDL is the presence of the object attribute in the IDL header. This keyword indicates that the interface is a COM object and directs the MIDL compiler to generate a COM proxy and stub, as opposed to RPC client/server stubs. The other main difference is indicating that the interface is derived from another interface. Remember that all COM objects are derived from IUnknown; so you must indicate that in the interface definition.


        Note



        Instead of being derived directly from IUnknown, COM objects can be derived from another class. However, the parent class is directly or indirectly derived from IUnknown.




        Putting this together, a sample COM interface definition in an IDL file might look something like this:


        import "iunknwn.idl"

        [

        object,
        uuid(12345678-1234-1234-1234-123456789012),
        ]

        interface IBankAccountObject : IUnknown
        {
        BOOL LoadDetails([in] PUSER_DETAILS userDetails);
        BOOL GetBalance([out] PBALANCE balanceInfo);
        BOOL GetHistory([out] PHISTORY historyInfo);

        ... other methods ...
        }


        As you can see, it looks a lot like an RPC interface definition. The most important part is locating all the available interface methods and determining what arguments they take. Then you must examine the implementation of each function to identify any vulnerabilities.


        In addition to defining just the interfaces, objects themselves can also be expressed. The coclass keyword is used to represent a COM object. The class definition contains a list of interfaces the object implements. Returning to the previous example of the bank interface, the class definition would follow the interface definition and look something like this:


        [
        uuid(87654321-4321-4321-4321-210987654321),
        version(1.0),
        helpstring("Bank Account Class")
        ]

        coclass CBankAccount
        {
        [default] interface IBankAccountObject;
        }


        This simple example shows the definition of the COM class CBankAccount. This object's CLSID is indicated by the uuid attribute. This class implements only one interface: IBankAccountObject.


        Note



        The default attribute listed before the interface definition is optional and doesn't need to be there. It simply indicates that IBankAccountObject is the default interface for the CBankAccount class. Other interface-specific attributes can be used; for more information, read the COM section of the MSDN.




        Reviewing the code for a class exposing multiple interfaces requires examining each interface separately because the interfaces' functionality might be exposed to untrusted (or semitrusted) clients.


        Type library information is also generated by using MIDL. Specifically, the library keyword can be used to create a .tlb file, like so:


        library libname
        {
        importlib("stdole.tlb");

        interface IMyInterface1;
        coclass CClass;

        ... other stuff you want to appear in the TLB ...
        }


        This section doesn't delve into the syntax for library definitions. When you have the source code, the type library doesn't offer much additional information. After all, you already know the available objects and their interfaces from looking at the rest of the IDL data.




        Active Template Library


        The Active Template Library (ATL) is another approach developers can use for developing COM applications. It allows developers to define interfaces in their code and automatically takes care of many of the more tedious aspects of implementing COM interfaces. For example, ATL can be used to automatically generate the IUnknown member functions QueryInterface(), AddRef(), and Release(). It can also be used to generate code for several other interfaces, such as IClassFactory.


        ATL is used extensively, so you need to be able to identify COM interfaces in ATL-generated code. As it turns out, this is easy. All you need to be familiar with is the COM_MAP macro used to define a COM object; a COM object definition using COM_MAP looks something like this:


        BEGIN_COM_MAP(CObjectName)
        COM_INTERFACE_ENTRY(IMyInterface1)
        COM_INTERFACE_ENTRY(IMyInterface2)
        END_COM_MAP()


        Simple, right? You can easily see that the COM object CObjectName is being declared, and it exposes two interfaces: IMyInterface1 and IMyInterface2. From there, all you need to do is locate the methods for each interface entry in the COM MAP. Each COM_INTERFACE_ENTRY() in the COM_MAP is an interface definition from an IDL file, which is generated by the development environment when ATL wizards are used. When ATL is used to auto-generate COM objects, you have the IDL data at your disposal as well.





        Auditing DCOM Applications


        Now that you're familiar with the general structure of COM programming and security measures, you need to walk through the most effective ways of auditing COM client and server programs. Auditing COM servers isn't too different from auditing RPC servers; you need to address the following questions:


        • Are sufficient access controls in place to restrict the interface to authorized parties?

        • Are the exposed interface functions secure?

        • Is impersonation being used properly, or does it pose a risk?

        • What launching rights are granted to the server?

        • Are there any threading or synchronizations issues that could be exploited?


        You can break down this list of requirements into the following steps:






        1.
        Check DCOM application security settings programmatically or by using the DCOM Configuration utility.


        2.
        Examine how CoInitializeSecurity() is called (if it's called) to back up your findings from the registry. This step also sheds some light on what sort of impersonation defaults are enforced.


        3.
        Locate the interface routines exposed by the COM server and apply the standard vulnerability-auditing methods you've learned in this book.



        When determining the security of interface functions, you should look for the issues described in the following sections.



        COM Registration Review

        Now that you know how access controls can be applied to COM objects, it should be evident that determining whether access controls aren't secure is a two-step process: examining the activation access controls and examining the call-level access controls.


        Activation access controls aren't in the application code; they reside in the registry. Although you might not have access to the target machines the application will be installed on, an install procedure should be in place to govern who can activate the object.


        COM applications are often self-registering. That is, they can perform their own registration automatically so that manual setup isn't required. To do this, they export a pair of functions, DllRegisterServer() and DllUnregisterServer(), in one of the binary files bundled with the application. The DllRegisterServer() function contains code to make registration settings. The DllUnregisterServer() function does the reciprocalremoving all registration established in DllRegisterServer().


        A COM application providing this interface is installed and removed with the regsvr32.exe program. When this program starts, it locates the DllRegisterServer() routine in the specified binary and runs it, thus removing the requirement for manual registration.


        Note



        ActiveX controls are self-registering COM objects. This just means users don't need to run the regsvr32 application because Internet Explorer does so automatically when downloading a new component. ActiveX controls are covered in "ActiveX Security" later in this chapter.




        After the application is installed, you can use standard Windows utilities to inspect security settings. The easiest approach is to use the DCOM Configuration utility; however, the associated registry keys can be manipulated directly. These keys are located at HKEY_LOCAL_MACHINE\SOFTWARE\Classes\AppID\<AppID>. Table 12-6 lists the MSDN-provided values that affect a server's DCOM security parameters.


        Table 12-6. COM Registry Values

        Named Value

        Description

        AccessPermission

        Sets an ACL that determines access.

        ActivateAtStorage

        Configures client to activate on the same system as persistent storage.

        AppID

        Identifies the AppID GUID that corresponds to the named executable.

        AuthenticationLevel

        Sets the authentication level for the AppID, overriding LegacyAuthenticationLevel. Available only on Windows NT 4.0 SP4 and later versions.

        DllSurrogate

        Specifies that a DLL server is to use a surrogate.exe file. If the path is not specified, the system-provided surrogate is used.

        DllSurrogateExecutable

        Specifies that a DLL server is to use a custom surrogate.exe file. If the custom file is not specified, the system-provided surrogate is used.

        Endpoints

        Configures a COM application to use a specified TCP port number for DCOM communications.

        LaunchPermission

        Sets an ACL that determines who can launch the application.

        LocalService

        Sets the application as a Win32 service.

        RemoteServerName

        Sets the name of the remote server.

        RunAs

        Sets an application to run only as a given user.

        ServiceParameters

        Sets parameters to be passed to a LocalService on call.

        SRPTrustLevel

        Sets the trust level of the software restriction policy (SRP). Available only on Windows XP and later versions.



        You have already seen that you can determine the launching identity of a COM application by checking the RunAs and LocalService keys listed in Table 12-6. These keys are usually absent, so the default action is taken, which causes the COM application to run in the context of the launching user. Running in this context roughly equates to a standard local process execution and generally requires no further inspection. However, further inspection is needed if the COM subsystem allows remote users to launch COM objects, as vulnerabilities in these methods could result in remote process execution. The remaining options might require far more inspection, particularly long-lived DCOM applications that run inside services.




        Auditing COM Interfaces

        Auditing the actual implementation of COM objects is one of the most critical components of auditing a COM-based application. After all, a vulnerability in the implementation of the functions could allow attackers to undermine all external access controls and the underlying system's integrity. The choice of authentication and impersonation parameters can reduce the impact of attacks. However, all exposed interfaces still need to be audited for the general classes of vulnerabilities discussed elsewhere in this book.



        COM Source Audits

        Auditing the source code makes your review easier because you can read interface definitions from IDL files or read the ATL definitions. From there, you can refer to the source code to find the implementation of relevant functions and determine whether the object exposes any vulnerabilities.




        COM Binary Audits

        You might be required to perform binary audits of COM applications. The principles for auditing a COM application (and indeed any application) are the same whether you have the binary or source code. However, the extra steps in the binary audit can be a major hurdle. With that in mind, this section gives you a brief summary of identifying and auditing COM interfaces as they appear in binary files.


        Say you're auditing a COM application, and you want to identify which interfaces the object exposes, what methods are available in each interface, and what type of arguments they take. The most useful source of information is type libraries, if they are available.


        Note



        Type libraries are always available for automation objects because the IDispatch interface needs to publish the information in them.




        As mentioned previously, the type library information might be stored in a separate file. However, most often it's stored as a resource in the executable or DLL that implements the object. You can find the location of a type library by consulting the HKEY_CLASSES_ROOT\CLSID\<CLSID>\TypeLib key.


        Note



        The HKEY_CLASSES_ROOT\Interface key can also contain a TypeLib key.




        This key provides a TypeID GUID value that matches a subkey in HKEY_CLASSES_ROOT\TypeLib. This key has a version subkey indicating the location of the type library. If it's embedded in an executable, you can simply view it with a PE resource viewer (such as PE Editor at www.heaventools.com). This library information is especially useful because it gives you GUIDs, structure definitions, methods exposed by interfaces, and even type information for arguments to those methods.


        After you have this information, you need to determine how to find the methods to audit in the binary. The first method is by locating entry points. An executable that implements a COM object must register each class object by using the CoRegisterClassObject() function. This requires indicating a CLSID along with a pointer to the class's IUnknown interface. By locating instances of CoRegisterClassObject(), you can find the vtable for IUnknown and then read the QueryInterface() function to learn about other interfaces the object exposes.


        In fact, the QueryInterface() function exported by an object is always useful because it must return pointers to all its supported interfaces. So another way to locate functions exported by an object is to find the QueryInterface() implementation in the COM server to see how it handles requests for different IIDs. Remember, access to any interface other than IUnknown is done via the QueryInterface() function, so the implementation always looks something like this:


        HRESULT QueryInterface(REFIID iid, void **ppvObject)
        {
        if(iid == IID_IMyInterface1)
        {
        *(IMyInterface1 *)ppvObject = this;
        AddRef();
        return NOERROR;
        }
        *ppvObject = NULL;
        return E_NOINTERFACE;
        }


        Because the second argument always points to an interface upon success, you can find every assignment for this argument and deduce which functions are exported. Take a look at a practical example. The following disassembly is taken from C:\Windows\System32\wiaacmgr.exe, which hosts a COM server on a Windows XP machine (CLSID 7EFA65D9-573C-4E46-8CCB-E7FB9E56CD57). The code is divided into parts so that you can see what's going on more easily.


        In this first part, the QueryInterface() function is initialized. As you can see, all that's done at this point is setting the ppvObject parameter to NULL so that it doesn't initially point to any interface:


        .text:010054C5 QueryInterface proc near ; CODE XREF:
        .text:0100A7F7j
        .text:010054C5 ; DATA XREF:
        .text:off_100178Co
        .text:010054C5
        .text:010054C5 this_ptr = dword ptr 8
        .text:010054C5 riid = dword ptr 0Ch
        .text:010054C5 ppvObject = dword ptr 10h
        .text:010054C5
        .text:010054C5 mov edi, edi
        .text:010054C7 push ebp
        .text:010054C8 mov ebp, esp
        .text:010054CA mov edx, [ebp+ppvObject]
        .text:010054CD push ebx
        .text:010054CE push esi
        .text:010054CF mov esi, [ebp+riid]
        .text:010054D2 push edi
        .text:010054D3 xor ebx, ebx
        .text:010054D5 push 4
        .text:010054D7 pop ecx
        .text:010054D8 mov edi, offset IID_IUnknown
        .text:010054DD xor eax, eax
        .text:010054DF mov [edx], ebx ; *ppvObject = NULL;


        This next part of the code compares the riid argument against IID_IUnknown. If the comparison succeeds ppvObject is set to point to the current (this) object. The jmp instruction at the end jumps to the function epilogue, which returns a successful result:


        .text:010054E1    repe cmpsd
        .text:010054E3 jnz short loc_10054F2
        ; test for IID_IUnknown
        .text:010054E5
        .text:010054E5 loc_10054E5: ; CODE XREF: QueryInterface+3Cj
        .text:010054E5 mov eax, [ebp+this_ptr]
        .text:010054E8
        .text:010054E8 loc_10054E8: ; CODE XREF: QueryInterface+5Bj
        .text:010054E8 mov [edx], eax ; *ppvObject = this;
        .text:010054EA mov ecx, [eax]
        .text:010054EC push eax
        .text:010054ED call dword ptr [ecx+4] ; call AddRef()
        .text:010054F0 jmp short loc_100552A


        Evidently, this object has two interfaces in addition to IUnknown. This next part of the code compares the riid argument against two more interface IDs. If there's a match, the ppvObject parameter is set to the this object pointer and a successful return happens:


        .text:010054F2 loc_10054F2:    ; CODE XREF: QueryInterface+1Ej
        .text:010054F2 mov esi, [ebp+riid]
        .text:010054F5 push 4
        .text:010054F7 pop ecx
        .text:010054F8 mov edi, offset IID_Interface1
        .text:010054FD xor eax, eax
        .text:010054FF repe cmpsd
        .text:01005501 jz short loc_10054E5 ;test IID_Interface1
        .text:01005503 mov esi, [ebp+riid]
        .text:01005506 push 4
        .text:01005508 pop ecx
        .text:01005509 mov edi, offset IID_Interface2
        .text:0100550E xor eax, eax
        .text:01005510 repe cmpsd ; test IID_Interface2
        .text:01005512 jnz short loc_1005522 ; go to failure
        .text:01005514 mov eax, [ebp+this_ptr]
        .text:01005517 lea ecx, [eax+4]
        .text:0100551A neg eax
        .text:0100551C sbb eax, eax
        .text:0100551E and eax, ecx
        .text:01005520 jmp short loc_10054E8 ; *ppvObject = this;


        Note



        The second interface causes ppvObject to be set to the this pointer with 4 added to it.




        If there's no match, the riid argument is deemed invalid, and the jnz instruction bolded in the previous code causes a jump to an error epilogue that returns the error E_NOINTERFACE, as shown in the following code snippet:


        .text:01005522 loc_1005522:    ; CODE XREF: QueryInterface+4Dj
        .text:01005522 and dword ptr [edx], 0
        .text:01005525 mov ebx, 80004002h ; E_NOINTERFACE
        .text:0100552A
        .text:0100552A loc_100552A: ; CODE XREF: QueryInterface+2Bj
        .text:0100552A pop edi
        .text:0100552B pop esi
        .text:0100552C mov eax, ebx
        .text:0100552E pop ebx
        .text:0100552F pop ebp
        .text:01005530 retn 0Ch
        .text:01005530 QueryInterface endp


        By finding QueryInterface(), you can figure out what interfaces are available based on how the ppvObject parameter is set. You don't even have to read the QueryInterface() code in many cases. You know that QueryInterface() is part of the IUnknown interface, and every COM interface must inherit from IUnknown. So vtable cross references to QueryInterface() are often COM interfaces, allowing you to focus on finding all cross-references to the QueryInterface() function. In the preceding code, there are two cross-references to QueryInterface(), which fits with what you learned from examining the code. Following one of these cross-references, you see this:


        .text:0100178C off_100178C    dd offset QueryInterface ; DATA XREF: sub_100A6B7+Do
        .text:0100178C ; sub_100A9AF+13o
        .text:01001790 dd offset sub_1005468
        .text:01001794 dd offset sub_1005485
        .text:01001798 dd offset sub_1005538
        .text:0100179C dd offset sub_1005582
        .text:010017A0 dd offset sub_10055CC
        .text:010017A4 dd offset sub_100ACA1


        This code is a table of function pointers, as you expected, for one of the COM interfaces the object exposes. The two functions under QueryInterface() are AddRef() (sub_1005468) and Release() (sub_1005485): the other two IUnknown functions. These three functions are always at the top of every exposed COM interface vtable.


        Similarly, DLL objects need to expose the DllGetClassObject() function. The responsibility of this function is to provide an interface pointer for an object, given a CLSID and an IID. Therefore, by reading through this function, you can find what classes are supported as well as what interface IDs are supported on each object. Typically, DllGetClassObject() implementations look something like this example taken from MSDN at http://windowssdk.msdn.microsoft.com/library/en-us/com/html/42c08149-c251-47f7-a81f-383975d7081c.asp:


        HRESULT_export  PASCAL DllGetClassObject
        (REFCLSID rclsid, REFIID riid, LPVOID * ppvObj)
        {

        HRESULT hr = E_OUTOFMEMORY;

        *ppvObj = NULL;

        CClassFactory *pClassFactory = new CClassFactory(rclsid);

        if (pClassFactory != NULL) {
        hr = pClassFactory->QueryInterface(riid, ppvObj);
        pClassFactory->Release();
        }

        return hr;
        }


        An object is usually instantiated and then queried for the specified IID. Therefore, initialization functions are commonly called from DllGetClassObject(), which sets up vtables containing the COM object's exposed methods.


        There are certainly other methods for finding object interfaces, although sometimes they're less precise. For example, if you know the IID of an interface you want to find an implementation for, you could simply do a binary search for some or all of that IID, and then follow cross-references to methods using that IID. Often a cross-reference points to the QueryInterface() routine where that IID can be requested.




        Automation Objects and Fuzz Testing


        Automation objects are required to publish type information from their type libraries. This means clients can learn about all the callable methods and argument types they take just by asking the object for its type information. Therefore, by having a client that asks for this information and then using it to stress-test each available method, you could quickly find vulnerabilities in the application.


        It turns out that a tool exists to do just this. Frederic Bret-Mounet designed and developed the COMbust tool, which he spoke about at the Blackhat Briefings conference in 2003. This tool takes any automation object specified by a user and does some basic fuzz testing on any methods it identifies. It's configurable, so users can tune it to test for specific conditions, and is available at www.blackhat.com/html/bh-media-archives/bh-archives-2003.html.




        Another easy way to locate a QueryInterface() implementation without reading any code is to do a text search on the relevant binary code for the E_NOINTERFACE value (80004002). Any match for this number is usually a QueryInterface() implementation returning an error or a client checking for this error when it has called QueryInterface() on an object. By the context of the match, you can easily tell which it is.






        ActiveX Security


        An ActiveX control is simply a self-registering COM object deployed inside another application, such as a Web browser. The "Active" part of the name comes from the fact that these objects can register themselves, thus simplifying their deployment. Most ActiveX controls also expose IDispatch interfaces so that they can be instantiated and manipulated easily by scripting languages. Generally, these controls are hosted in Internet Explorer, although they can be hosted inside any application. ActiveX is an important Windows technology with serious security implications explored in the following sections.


        Note




        Changes to Internet Explorer 6 and the upcoming Internet Explorer 7 do a lot to mitigate the dangers of ActiveX controls. Internet Explorer 7 introduces site-based opt-in for controls to prevent a malicious site from instantiating installed controls.





        ActiveX Code Signing

        An ActiveX control is just a bundle of binary code that runs in the context of instantiating user. Because of the potential danger of running native code, Microsoft designed ActiveX controls to support validation through an Authenticode signature. Developers can sign controls with their private keys, and users can validate the source of the unmodified control. This signature doesn't in any way state that the control is free of vulnerabilities, and it doesn't prevent the control from being malicious. It just means there's a verifiable paper trail leading back to the developer.




        Safe for Scripting and Safe for Initialization

        In addition to code signing, ActiveX controls have a few additional parameters to limit their attack surface when deployed inside Internet Explorer. These parameters are termed "safe for scripting" and "safe for initialization." There are two ways to mark interfaces as safe. The first is performed at installation by modifying the registry key HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\<GUID of control class>\Implemented Categories\<GUID of category>. The safe for scripting category GUID is {7DD95801-9882-11CF-9FA9-00AA006C42C4}, and the safe for initialization category GUID is {7DD95802-9882-11CF-9FA9-00AA006C42C4}.


        The second approach to marking a control as safe requires that the control implement the IObjectSafety interface, which exposes the GetInterfaceSafetyOptions() method to the hosting container. The hosting container calls this method to determine whether a specific interface is marked as safe for scripting or initialization and can also request that the control be marked as safe by calling the IObjectSafety.SetInterfaceSafetyOptions() method.


        Any control marked as safe for scripting can be instantiated and manipulated in Internet Explorer. Microsoft advises marking a control as safe for scripting only if it must be manipulated from Internet Explorer and doesn't provide any means for unauthorized parties to alter the state of the local system or connected systems. This guidance is given because a safe for scripting control exposes its methods to any site users view, so attackers can leverage the functionally exposed by a control to exploit client users. For example, say a scriptable control allows the manipulation of arbitrary files. This issue might be part of a faulty design or the result of a vulnerability in path checking. Regardless, it would present an unacceptable vulnerability for an ActiveX control because it allows any remote attacker to drastically alter the victim's system after connecting to a malicious Web site. When reviewing ActiveX controls, you need to treat every scriptable method as attack surface and assess them as you would any other potentially vulnerable code.


        ActiveX controls can also store and retrieve data between instantiations by using the IPersist interface, which is exposed to controls marked as safe for initialization. Microsoft advises marking a control as safe for initialization only if it must store persistent data internal to Internet Explorer and it handles this data properly. A security vulnerability can occur if the object stores sensitive data and exposes it to an untrusted source or if a control fails to treat persistent data as data originating from an untrusted source.


        Some people might be a little fuzzy on why a control must be separately marked as safe for initialization. After all, the control is just a binary, so it can call any Windows API function on its own. This means it can read the registry or file system without the need for an IPersist interface, so exposing sensitive data is still a concern. However, a control can be initialized with parameters provided by a Web site, as shown in this HTML fragment that instantiates a control:


        <OBJECT ID="MyControl"
        CLASSID="CLSID:F2345FA3-E11B-40AE-A86D-32C487C3EE54"
        CODEBASE="MyControl.CAB">
        <PARAM NAME="MyServer" VALUE="malicious.com" />
        </OBJECT>


        This fragment creates an instance of a control and attempts to initialize it with the MyServer parameter. This parameter is accepted through the IPersistPropertyBag interface, which inherits from the base IPersist interface. The control retrieves the parameter with the following code:


        STDMETHODIMP MyControl::Load(IPropertyBag *pProps,
        IErrorLog* pErrLog)
        {
        _variant_t myVar;
        int hr = 0;

        hr = pProps->Read("MyServer", &myVar, pErrLog);
        if (hr != 0) return hr;
        strcpy(m_serverName, myVar);

        return hr;
        }


        This code is a simple implementation of the IPersistPropertyBag::Load() method. Internet Explorer calls this method when loading the control, and the control then retrieves the PARAM values via the IPropertyBag interface. What's important here is that you follow the path of these properties and see what they affect. The _variant_t class in this code has overloaded operators to handle type conversions, so don't be distracted by that part. Instead, just note that the bold line copies the property string into a member variable. Here's the declaration of that member variable:


        char   m_serverName[512];


        It's fairly obvious that this code is performing an unbounded string copy into a fixed-size buffer, so this particular IPersist interface is vulnerable to a straightforward buffer overflow. This vulnerability might seem obvious, but this exact pattern has been seen in more than one ActiveX control. The issue is that developers often don't consider control instantiation to be an exposure point. You need to pay special attention to all IPersist interfaces to see whether they handle input in an unsafe manner.




        Site-Restricted Controls

        One of the best ways of limiting a control's attack surface is to instantiate it only for a known set of locations. Implementations can limit instantiation based on hostname, but restrictions can be based on any connection information by implementing the IObjectWithSite interface and the SetSite() method. The WebBrowser control can then be used to provide detailed connection information. Microsoft provides the SiteLock template as a starting point for creating a site-restricted control.


        If a control is locked to a particular site, you need to determine how effective that lock is. There might be issues in the string comparisons that allow you to bypass the checks, similar to the topics discussed in Chapter 8, "Strings and Metacharacters." There might also be Web application vulnerabilities at the hosting site that allow you to instantiate the control in the context of the site, but with your own parameters and scripting. Read Chapters 17, "Web Applications," and 18, "Web Technologies," for more information on vulnerabilities that involve this attack vector.




        The Kill Bit

        Sometimes a vulnerability is identified in a signed control. This control can then be delivered by a malicious Web site, allowing attackers to exploit a control that otherwise appears safe. A site-restricted control is less vulnerable to this type of attack; however, Web application vulnerabilities (such SQL injection and cross-site scripting) might allow attackers to exploit the underlying vulnerability. For this reason, Microsoft introduced the ActiveX kill bit, which is used to mark a control version as unauthorized. The kill bit is set by setting the CompatibilityFlags DWORD value to 0x00000400 in this registry location: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\ActiveX Compatibility\<GUID of control class>.


        This key and value aren't usually present, so they need to be created by the control's installer. Developers often have a new control set this value for all previous versions, just to prevent earlier versions from being installed. Note whether this value is set; if it's not, you might want to look at vulnerabilities in previous control versions.




        Threading in ActiveX

        Most ActiveX controls are registered for the STA model, so thread synchronization issues aren't generally a problem. However, an ActiveX control can be registered as an MTA. This model is a bad idea from a usability perspective because it can cause GUI synchronization issues. However, an MTA control might also expose synchronization vulnerabilities.




        Reviewing ActiveX Controls

        Proprietary ActiveX controls are often frowned on in modern Web application development. They've mostly been replaced with newer technologies that are more portable and less prone to security issues. However, they are still deployed in many legacy and corporate intranet sites. As a reviewer, one of your first considerations should be whether a Web-hosted ActiveX control is necessary and determining the cost of replacing it.


        If the control is necessary, review it as you would any other binary application. However, you also need to ensure that the control handles the considerations mentioned previously in this section. Here's a basic checklist:


        1. If you're reviewing the control as part of a larger system, check that it's signed with a certificate trusted by clients. If the control isn't signed, look for vulnerabilities in the rest of the system that could allow attackers to deploy a malicious control.

        2. If the control must be marked safe for scripting, evaluate all exposed IDispatch paths closely, including vulnerabilities resulting from the intended functionality and implementation vulnerabilities.

        3. If a control must be marked safe for initialization, evaluate all IPersist calls closely. Look for any exposure of sensitive data. Also, look for any mishandling of persistent data, such as conditions that could result in memory corruption.

        4. Check whether the control is site restricted. If it is, look for vulnerabilities in the restriction implementation that could allow it to be instantiated by another site. Also, check for any other implementation vulnerabilities that could make this interface exploitable. If the control is part of a larger system, look for Web application vulnerabilities that could be used to circumvent the site lock.

        5. Check to see whether the control sets the kill bit for previous versions. If not, you might want to do a cursory analysis for vulnerabilities in earlier versions of the control.

        6. If the control uses the MTA model, check for synchronization issues that could be exploited by scriptable methods.