Why does SetFileValidData fail even though I enabled the SE_MANAGE_VOLUME_NAME privilege?


A customer reported that their call to Set­File­Valid­Data was failing with ERROR_PRIVILEGE_NOT_HELD despite the fact that they held the privilege whose name is defined by the symbol SE_MANAGE_VOLUME_NAME. (Note that the "name" in "manage volume name" doesn't mean that you are managing the name of the volume; rather it means "This is the name of the privilege for managing volumes.")

The customer was kind enough to reduce the problem to a simple program:

#include <windows.h>

int main(int argc, char** argv)
{
 // This succeeds
 HANDLE h = CreateFileW(L"test", GENERIC_WRITE, 0, nullptr,
   CREATE_ALWAYS, FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED,
   nullptr);

 LARGE_INTEGER newSize;
 newSize.QuadPart = 256 * 1024 * 10;
 // This succeeds
 SetFilePointerEx(h, newSize, nullptr, FILE_BEGIN);

 // This succeeds
 SetEndOfFile(h);

 HANDLE hToken;
 // This succeeds
 OpenProcessToken(GetCurrentProcess(),
      TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);

 // This succeeds
 SetPrivilege(hToken, SE_MANAGE_VOLUME_NAME, TRUE);

 // This succeeds
 CloseHandle(hToken);

 // This fails with ERROR_PRIVILEGE_NOT_HELD
 SetFileValidData(h, newSize.QuadPart);

 CloseHandle(h);

 return 0;
}

The answer is hidden in the documentation for the Set­File­Valid­Data function:

A caller must have the SE_MANAGE_VOLUME_NAME privilege enabled when opening a file initially.

The program didn't enable the manage volume privilege until after it had already created the file handle.

This requirement that the privilege be active at the point the handle is created becomes less surprising when you realize that the general policy for kernel object security is that security is checked at the time handles are created, rather than when they are used.

Comments (13)
  1. Antonio Rodríguez says:

    It's obvious. After reading the code and without reading the documentation quote, I realized the problem. Checking the privileges when creating the handle, as the NT kernel does, makes a lot of sense: its very efficient (a single handle can be used for hundreds or thousands of operations), it's coherent (once opened, you don't have to worry for it to stop working out of the blue), and it does that at the cost of ignoring at infrequent corner case: changing permissions or privileges after opening.

    1. poizan42 says:

      > and it does that at the cost of ignoring at infrequent corner case: changing permissions or privileges after opening.

      That's actually a feature because it allows you to drop privileges when they are no longer needed while still having access to what you need to work on.

      1. exchange development blog team says:

        Security people see it as a bug, not a feature. It even has its own name, TOCTOU, time of check/time of use, a race condition where you verify the privilege at the wrong time.

        1. kme says:

          In this case it is specifically a security feature: checking the permissions at the time of the handle creation rather than use means that you can safely pass handles from low-privilege scopes to high-privilege scopes, without the high-permission side getting tricked into using its privileges when it didn't intend to. UNIX does the same thing for the same reason.

  2. Karellen says:

    So, the value behind SE_MANAGE_VOLUME_NAME is the name of the privilege, and "SE_MANAGE_VOLUME_NAME" is what the privilege's name is called. I take it that the privilege itself is called SE_MANAGE_VOLUME. All we need to know now is what the privilege *is* ("A-Sitting on a Gate"[0], perhaps?) and we're done!

    ...any further comparisons between Microsoft APIs and other classic works of nonsense literature are left as an exercise for the reader ;-)

    [0] https://en.wikipedia.org/wiki/A-Sitting_On_A_Gate

    1. dave says:

      +1 for the Haddocks' Eyes reference.

      I consider the distinctions made therein to be essential knowledge for any serious programmer.

    2. dave says:

      A privilege is, of course, a-sitting on a gate that controls access to something.

  3. alegr1 says:

    Does that mean a handle has additional permission mask which is not part of the standard 32 bit mask?

    1. Antonio Rodríguez says:

      I don't think it does. My guess is that it has an standard permission mask that gets assigned at the time of opening in function of the requested permissions and the user's actual permissions. Later, operations on the handle are quickly checked against that local permission mask. Anyway, wether it works that way or other, it's an implementation detail.

      1. Killer{R} says:

        Handle entry doesn't have captured priviledges. I guess IO manager or even FS driver does this tracking.
        There're also some inconsistencies:
        - For access rights its rather obvious that they're checked only when CreateFile called since there is parameter with declared access rights needed, like I will write to file using this handle. But there is nothing to declare 'I will hmm.. manage volume by this handle' :) So its kind of semantic inconsistency.
        - Functions RegRestoreKey/RegBackupKey require SE_RESTORE_NAME/SE_BACKUP_NAME privileges respectively. But AFAIR they checks for it against current caller, but not the one who opened handle to key beinf restored/backed up. So its inconsistency with older API behaviour.

  4. dirk gently says:

    > Note that the "name" in "manage volume name" doesn't mean that you are managing the name of the volume; rather it means "This is the name of the privilege for managing volumes."

    It always amazed me how English has no morphological difference between nouns and adjectives, and the function is determined by the word order, which fails spectacularly if there are more than two words in the expression, like, is it "(manage volume) name" or "manage (volume name)"?

    1. dave says:

      manage-volume name
      manage volume-name

    2. Ben Voigt says:

      But if apps hungarian (appropriately adapted for all-uppercase macros) were being used, the problem would go away.

      SE_PRVLGNAME_MANAGE_VOLUME

      "Privilege name" is the category of the value, and "manage volume" identifies the particular one. An alphabetical sort will then also group all the privilege names together, which is far more useful than grouping all the "SE_MANAGE*" stuff together.

      In a language that supports namespaces and/or scoped enums, it might be Security::PrivilegeName::MANAGE_VOLUME

      English adjective-noun word order does not work as well as hierarchical systems for identifier naming.

Comments are closed.

Skip to main content