A NotQuiteNullDacl Class

Environment: VC6 -- WinNT4, Win2000, WinXP

Default security is not always good enough

Many of the Win32 API functions, specifically those that create objects such as files, mutexes, and pipes, require a pointer to a SECURITY_ATTRIBUTES structure. Most of us program for a long time before we ever have to code anything other than a NULL for that pointer. The NULL pointer makes the object uninheritable and indicates the created object should be assigned a default security descriptor. That default security is just the security associated with the creating process and that is sufficient for most purposes.

However, if you try to share an object created this way between, say, a Service and a regular App, or, now that we have XP, between two logon session on the same workstation where the logged on users have different access rights, the objects created with default security begin to fail you right and left.

Take the case of a mutex that you are trying to use to let a Service and an App be aware of each other's existence. Say the App is used to configure the Service. The App needs to work a little differently if the Service is running than if the Service is not. And the Service shouldn't start at all if the App is running, on the theory that its configuration parameters may be in an inconsistent state.

Traditionally each module uses a CreateMutex call to open a handle to a named mutex. If that call succeeds, each module then calls GetLastError to check for ERROR_ALREADY_EXISTS. If the App sees that it is not the original creator of the mutex, it knows the Service is running and modifies its behavior accordingly. If the Service sees that it is not the original creator of the mutex, it just terminates.

The usual code to do this is pretty straightforward. Each module has a section that looks something like:

HANDLE hMutex = CreateMutex( NULL, FALSE, "MyMutex" );
DWORD dwErr = GetLastError();

if ( hMutex != NULL )
{
   // the call succeeded
   // but is it a new mutex or an existing one?
   if ( dwErr != ERROR_ALREADY_EXISTS )
   {
      // we actually created the mutex
      // free to run in normal mode
   }
   else
   {
      // we got a handle to an existing mutex,
      // take appropriate action
   }
}
else
{
   // call to create the mutex failed
   // use the dwErr value to see why
}

This is entirely ordinary code and always works fine between two copies of an App or two different Apps running in the same login session. However, if you test it using a Service and an App, you see that, if the Service is already running, the App's call to CreateMutex fails with a GetLastError result of 5, access denied. A little digging in books or MSDN reveals the problem. The Service is running under the LocalSystem account, so when it creates the mutex with default security, it implicitly denies the App permission to open a handle to the mutex, since the App is running under a user logon.

The Null Dacl solution

This forces you to finally confront the documentation on the SECURITY_ATTRIBUTES structure and the all-important Security Descriptor it points to. After reading a bit and giving your eyes time to stop spinning, the most likely solution you'll hit upon is to build a SecurityDescriptor with a Null Discretionary Access Control List (Dacl). Using a Null Dacl allows anyone any access to the object created and certainly solves the problem at hand. And it is SIMPLE TO CODE; you can read about it, code it up, and test it in a mortal amount of time.

The code now looks like:

// declare and initialize a security attributes structure
SECURITY_ATTRIBUTES MutexAttributes;
ZeroMemory( &MutexAttributes, sizeof(MutexAttributes) );
MutexAttributes.nLength = sizeof( MutexAttributes );
MutexAttributes.bInheritHandle = FALSE; // object uninheritable

// declare and initialize a security descriptor
SECURITY_DESCRIPTOR SD;
BOOL bInitOk = InitializeSecurityDescriptor( &SD,
                         SECURITY_DESCRIPTOR_REVISION );
if ( bInitOk )
{
   // give the security descriptor a Null Dacl
   // done using the  "TRUE, (PACL)NULL" here
   BOOL bSetOk = SetSecurityDescriptorDacl( &SD,
                                            TRUE,
                                            (PACL)NULL,
                                            FALSE );
   if ( bSetOk )
   {
      // Make the security attributes point 
      // to the security descriptor
      MutexAttributes.lpSecurityDescriptor = &SD;

      // use the attributes with the Null Dacl to
      // create the mutex
      hMutex = CreateMutex( &MutexAttributes,
                            FALSE,
                            MutexName );
      dwMutexErr = GetLastError();

      ... same logic as before

   }
   else
      
}
else
   // Initialize failed, use GetLastError to find out why

The problem is that using the Null Dacl is bad practice. By allowing anyone any access, it conceivably allows some other process to hijack your object, change its accesses out from underneath you, and disable your application(s) or some part of the file system.

Sound far-fetched? Well, it probably is. And, despite Microsoft's dire warnings, which have gotten louder and more strident with its recent emphasis on secure programming, lots of good programmers have been using the Null Dacl approach for along time and the sky has not yet fallen.

Still, the danger of just coding up a Null Dacl for your little app-that-no-one-would-bother-to-hijack is that we all borrow code all the time, usually when we are in a hurry. The next time you need to solve this problem, perhaps for a more critical app, or the day the junior programmer down the hall needs an example of how to get around it, the code you write today will get propagated. And generations of code hence, it could just be the centerpiece of an embarrassing incident.

NotQuiteNull Dacl to the rescue

So, what we need is an equally easy to use but more secure Dacl, the NotQuiteNull Dacl. This Dacl explicitly grants all accesses to the Everyone group (which is essentially what the Null Dacl does) but filters that by first denying Everyone access to the WRITE_DAC and WRITE_OWNER permissions. This means that all users of the object except the original creator get all of the permissions they need to use the object and none of the permissions needed to hijack it.

There are examples of coding for this Dacl lying around, but they are not particularly easy to find. The 'Ask Dr. Gui #49' MSDN article has a pretty straightforward discussion and coding. The Richter/Clark Server-Side Applications book (see references) has a much more general discussion and coding in Chapter 10, pp 458-460, but the NotQuiteNullDacl code is pretty well buried in the example.

I've created a wrapper class, called NotQuiteNullDacl, to package the coding and make the better Dacl almost as easy to use as the Null Dacl. The full class is included in the sample code files. The code jumps through some hoops to create a SID for the Everyone group (m_pEveryoneSid) and to initialize the dacl (m_pDacl). But the critical code is:

BOOL bAddAce;
   
// Add an ace that denies WRITE_OWNER and WRITE_DAC to 
// 'everyone' which denies everyone but the owner of the
// associated object access to the object's security.

bAddAce = 
  AddAccessDeniedAce( m_pDACL,  // the acl to add the ace to
              ACL_REVISION,     // must be ACL_REVISION
              WRITE_OWNER | WRITE_DAC, // accesses to deny
              m_pEveryoneSid ); // SID to be denied (everyone)
if ( bAddAce )
{
   // Add an ace that gives 'everyone' all accesses 
   // to the object. By itself, the bit of code below
   // would be the moral equivalent of a NULL DACL -- 
   // it gives all rights to everyone.  But, because
   // accesses are evaluating in order of their placement
   // in the DACL the DeniedACE above acts as a filter, 
   // before this can be evaluated.
   bAddAce = 
     AddAccessAllowedAce( m_pDACL, // the acl to add the ace to
                          ACL_REVISION, // must be ACL_REVISION
                          GENERIC_ALL,  // accesses to allow
                          m_pEveryoneSid ); // SID to be allowed
   if ( bAddAce )
   {
      bAddedOk = true;
   }
}

if ( !bAddedOk )
{
   // use GetLastError() to find out why the add failed
}

Using the NotQuiteNullDacl class

Once you've included the class header, using the NotQuiteNullDacl is pretty straightforward. The setup for the CreateMutex call becomes:

// create the not-quite-null-dacl
NotQuiteNullDacl Dacl;
bool bDaclOk = Dacl.Create();

if ( bDaclOk)
{
   bool bSetupOk = false;

   // declare and initialize a security attributes structure
   SECURITY_ATTRIBUTES MutexAttributes;
   ZeroMemory( &MutexAttributes, sizeof(MutexAttributes) );
   MutexAttributes.nLength = sizeof( MutexAttributes );
   MutexAttributes.bInheritHandle = FALSE; // object uninheritable

   // declare and initialize a security descriptor
   SECURITY_DESCRIPTOR SD;
   BOOL bInitOk = InitializeSecurityDescriptor( &SD,
                           SECURITY_DESCRIPTOR_REVISION );
   if ( bInitOk )
   {
      // assign the dacl to it
      BOOL bSetOk = SetSecurityDescriptorDacl( &SD,
                                               TRUE,
                                               Dacl.GetPDACL(),
                                               FALSE );
      if ( bSetOk )
      {
         // Make the security attributes point to 
         //the security descriptor
         MutexAttributes.lpSecurityDescriptor = &SD;
         bSetupOk = true;
      }
   }

   if ( bSetupOk )
   {
      // use the security attributes to call CreateMutex
   }
   else
   {
      // the setup failed somehow, use GetLastError() 
      // to find out why
   }
}
else
{
   // nqnDacl create failed, 
   // use Dacl.GetErr() to find out how
}

Side effects? There are a few

Using the NotQuiteNullDacl is not exactly the same as using a Null Dacl. For example, we're used to using CreateMutex either to create a new mutex or to open a handle to an existing mutex. But CreateMutex implicitly asks for MUTEX_ALL_ACCESS on the handle to be opened. If the Mutex was created in one logon session, say by a Service, with the NotQuiteNullDacl, another process won't be able to get MUTEX_ALL_ACCESS since WRITE_DAC and WRITE_OWNER have been explicitly denied. The second process CAN use OpenMutex, asking for SYNCHRONIZE permission, to determine the existence of the mutex.

So now, rather than issuing a CreateMutex call and then having to check the error status when it returns a valid handle, which has always been a little jarring to me, you first try to open the mutex. If you can open it, you know it already exists and can act accordingly. If you can't open it, you can create it. The result is rather more readable code and a much higher level of security in the object created.

References:

  1. Ask Dr. Gui #49
  2. Programming Server-Side Applications for Microsoft Windows 2000, Richter/Clark, Microsoft Press 2000

Downloads

Download source - 3 Kb


Comments

  • clarisonic mia selling in the ebay is merit to pull someone's leg

    Posted by iouwanzi on 06/06/2013 11:06am

    [url=http://www.australiaclarisonic.com/]clarisonic brush[/url] Mon conjoint et j’ai très certainement besoin personne se produit et en outre choisir de me donner personnellement un Styler F HI-DEF en ce qui concerne la période des fêtes… donc si il l’homme devrions vraiment besoin pour gagner me réjoui, mon conjoint et moi obtenir la connexion pointe GHD Gold Styler et Document a parlé du fait que le Styler juste est au prix de 179 kilos, ainsi que la boîte proverbiale coûte 215 euros nuit gamme. (Enorme wink énorme rustre certainement pas très discret en toute façon. [url=http://www.miaclarisonicaustralia.org/]clarisonic mia online[/url] modestes caractéristiques consistent non pas un mais deux défini guides mise en page talents Deco, simultanément disponible ce sont unique. Toute la galerie Scarlet comprend un sac soie cramoisi tolérant à chaleur merveilleuse lesoù aider à stocker le fer à lisser vos plaques ghd IV Styler cheveux pour s’assurer vous rose profond, par le biais de non pas un mais deux trucs cheveux sauvages dont une instance appropriée.Écrit à l’intérieur d’une boîte commune inflammed inanité habilitée par période de Style déco, tous le groupe écarlate fantaisie ghd styler intemporelle métal jaune comprend une alimentation réduite option rouge et en outre une mallette de rangement, un voyage fabuleux crinière sèche-cheveux ghd, couple de crinière de films et en outre un important carton satiné résistant à la chaleur. [url=http://www.miaclarisonicaustralia.org/]clarisonic mia online[/url] Nous n’avons jamais perdu pour protéger mes propres cheveux sauvage à l’aide d’un jet clean. Je veux partager avec vous une autre section de mon propre régime cheveux (traitement).Avec mon vieux métal bon personnel, nous requise pour élever la température à 100 °, conçu pour la chevelure lisse, bien que je ne savais jamais même la présence de squirt résistant à la chaleur en ! Cela va sans la nécessité d’énoncer dans lequel mes cheveux sauvage était une bonne affaire cassé !

    Reply
  • Nike Air Max 1 FB press, have a fervent color grain, the unheard of shoes

    Posted by Geozyoceada on 04/19/2013 10:02am

    In the summer in a tumbler backing bowels the serene sprite seems to be a wholesome fitting, but if the sprite "feet"? Will also supply you a lapse, accompany a sustenance! This summer, Nike and Sprite [url=http://fossilsdirect.co.uk/glossarey.cfm]nike huarache[/url] and his sneakers to a fuse of enduring snow spread of green, white and blue color system in the definitive Nike Superciliousness Max 1 shoes let slip a like a breath of fresh air chill scent.[url=http://northernroofing.co.uk/roofins.cfm]nike free[/url] Summer is the time to select a cleanly shoe, shoes should be a creditable choice. Qualifying series Nike Publicize Max HomeTurf metropolis recently finally comes up, this series in the first-rate Air Max shoes to London, Paris and Milan the three paid glorification to the iconic city of Europe, combined with the characteristics of the three cities, Air Max 1 HYP,Allied Max 90 HYP,Air Max 1 and shoes such as Quality Max 95, combined [url=http://markwarren.org.uk/property-waet.cfm]nike air max 90[/url] with the Hyperfuse, as kindly as a variety of materials, such as suede, Whether you crave practicable or retro-everything.

    Reply
  • You crave some tomato basil and mozzarella. To indoor utilization, these slippers are as emerge considering and manueverable as sneakers.

    Posted by Soaceddew on 04/19/2013 07:30am

    Has honourable released some chic color Free Inneva Woven shoes, Nike recently with another technique to bring shoes with distinguishable styling to all [url=http://northernroofing.co.uk/roofins.cfm]nike free uk[/url] eyes. This brings important issue Let off Inneva Woven is a Creamy Marker of works in the series, represents shoes Italian made the assurance. Latest Allowed Inneva Woven swart and blue are readily obtainable in two color schemes, to hand-knit Woven vamp in extension to infiltrated Italy's [url=http://markwarren.org.uk/goodbuy.cfm]nike free run[/url] finest crafts, in the meantime gives athletes close to the foot of comfort, the most consequential thing is the outclass of Loose 5 configuration, barefoot be aware it will give birth to cannot be ignored. Nike Empty Inneva Woven SP Pale-complexioned Label Order off on March 16 at outlets around the [url=http://northernroofing.co.uk/roofins.cfm]nike free run uk[/url] brand on the shelves, and on in stock in restricted form, interested friends should recompense terminate attention to Nike announced the news.

    Reply
  • A race condition with {open? ...create}

    Posted by /df on 12/07/2005 05:58am

    "So now, rather than issuing a CreateMutex call and then having to check the error status when it returns a valid handle, which has always been a little jarring to me, you first try to open the mutex. If you can open it, you know it already exists and can act accordingly. If you can't open it, you can create it."
    
    There's no guarantee that the mutex will not exist when you try to create it. Consider threads A, B in a single processor (the least dangerous) scenario:
    
    time    A                             B
    
    |   open->FILE_NOT_FOUND 
    |   // OK, so go and create
    |   (unscheduled)
    |                                (scheduled)
    |                           open->FILE_NOT_FOUND 
    |                           // OK, so go and create
    |  
    |                           create->OK
    |
    |                               (unscheduled)
    |       (scheduled)
    |
    |   create->ACCESS_DENIED
    |
    |
    v
    
    Obviously what's needed is a mutex to protect the {open ... create} logic! :-(
    
    What options do you have?
    
    a) loop around doing {open? ...create} until one succeeds - but how many times?
    
    b) nominate a master process that will create the mutex (maybe your configuration involves a service and n user processes - then this would make sense).
    
    c) the logic suggested by Dr GUI is {create? ... open} which may be less susceptible to the race condition (the creating process is presumably performing some operations while it has the mutex, so allowing time for another process's {create? ... open} to complete.
    
    d) anything else?

    Reply
  • Small Addendum

    Posted by kenamills on 01/09/2005 08:24pm

    Possibly because of Windows XP SP2 tightening up security, I found the rights assigned here weren't enough to access mutexes or events created in a service in a client running in a limited user logon account and using OpenMutex/OpenEvent. If you're experiencing this, try replacing 'GENERIC_ALL' in the ::AddAccessAllowedAce call with (GENERIC_ALL|STANDARD_RIGHTS_ALL|SPECIFIC_RIGHTS_ALL) & (~(WRITE_OWNER|WRITE_DAC)). This seemed to work with my code.

    Reply
  • Very useful code sample

    Posted by Legacy on 10/16/2003 12:00am

    Originally posted by: mrr

    Thanks--I did a web search for null DACL and found this very useful article. I knew what I wanted to do, but I didn't know how to do it.

    Reply
  • Very clear and to the point....

    Posted by Legacy on 09/03/2003 12:00am

    Originally posted by: soda

    This saved me a ton of work :)

    Reply
  • Good article...However I had problems w/ return errors on XP

    Posted by Legacy on 04/14/2003 12:00am

    Originally posted by: Kathleen Langone

    In essence the return error code of: ERROR_ALREADY_EXISTS didn't occur w/ my logic... so I ended up checking for 'access_denied' when a second app tried to access/create the same mutex:
    
    

    if ( ( g_hMutexAppRunning != NULL ) && ( GetLastError() == ERROR_ALREADY_EXISTS) ||
    ( g_hMutexAppRunning == NULL ) && ( GetLastError() == ERROR_ACCESS_DENIED))


    Also for the CreateMutex I used the "global" area... which helps facilities apps from two different XP logics to communicate:

    g_hMutexAppRunning = CreateMutex( &my_attr, FALSE, "Global\\myapp.exe");

    I found some of the above information in a good Microsoft Knowledge Base articles... but don't have a link to it.

    Reply
  • Excellent Article

    Posted by Legacy on 09/14/2002 12:00am

    Originally posted by: Bill Gates

    Saved me a ton of time. Could not figure out why the service and the UI app could not share a mutex.

    Reply
  • This solved my named pipes problem.

    Posted by Legacy on 09/05/2002 12:00am

    Originally posted by: Jason Welco

    Many thanks. Microsoft's documentation has not kept up with the complexity of their APIs. This helps me quickly solve my problem with getting a client to connect to my pipe server.

    I still need to dig into this further, however, because I don't want anybody to be able to toast my pipe by feeding it garbage.

    Reply
  • Loading, Please Wait ...

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • It's hardly surprising that half of small businesses fail within the first 1-5 years. It's not easy to launch a new product, single-handedly manage everything from IT to accounting, fend off the competition, and grow a customer base – all at the same time – even with a great concept. Offering awesome customer service can make the difference between a startup that flies and a startup that dies. Read this white paper to learn nine ways customer support can help you beat the competition and grow your …

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds