[CLOSED] Reservierung von großen Seiten - AdjustTokenPrivileges gibt ERROR_INVALID_PARAMETER zurück



  • Ich habe einen Allokator geschrieben, über den man die Seitengröße für die Speicherreservierung angeben kann, und eine Anwendung, für die ich die letzten Tage Windows-Support eingebaut habe. Das Durchreichen von MEM_LARGE_PAGES auf Windows funktioniert, aber VirtualAlloc schlägt bei der Reservierung fehl. Lasse ich das Flag weg, funktioniert die Allokation.

    Windows hat ein (im Vergleich zu Linux) absolut bescheuertes System, um große Seiten zu verwalten - erst muss man die Nutzer, die große Seiten verwenden sollen, einem bestimmten Privileg (über "Lokale Sicherheitsrichtlinie", "Lokale Richtlinien", "Zuweisen von Benutzerrechten", "Sperren von Seiten im Speicher") zuweisen (ist dem Fall), dann muss man noch zwei Funktionsaufrufe ( LookupPrivilegeValue und AdjustTokenPrivileges , von denen letztere nicht mal direkte Fehlerindikation geben kann) durchführen - ist beinahe der Fall ... denn AdjustTokenPrivileges gibt mir auf Umwegen den Fehlercode ERROR_INVALID_PARAMETER (87) zurück.

    Also vergleiche ich meinen Code mit dem, was man so im Internet zu dem Thema findet, und finde aber keinen Fehler:

    DWORD set_privilege
    (
        LPCTSTR privilege_name,
        BOOL enable
    )
    {
        DWORD my_errno;
        HANDLE token;
    
        TOKEN_PRIVILEGES privileges;
    
        /*Gibt keinen Fehler ...*/
        if(!OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,&token))
        {
            my_errno = GetLastError();
            goto END_NO_TOKEN;
        }
    
        /*Gibt keinen Fehler ...*/
        if(!LookupPrivilegeValue(NULL,privilege_name,&privileges.Privileges[0].Luid))
        {
            my_errno = GetLastError();
            goto END_TOKEN;
        }
    
        if(enable)
            privileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
        else
            privileges.Privileges[0].Attributes = 0;
    
        SetLastError(0);
        if(!AdjustTokenPrivileges(token,FALSE,&privileges,0,NULL,NULL))
        {
            /*Dieser Fehlerpfad wird genommen, mit Code ERROR_INVALID_PARAMETER!*/
            my_errno = GetLastError();
            if(my_errno != ERROR_SUCCESS)
                goto END_TOKEN;
        }
    
        my_errno = ERROR_SUCCESS;
    END_TOKEN:
        CloseHandle(token);
    
    END_NO_TOKEN:
        return my_errno;
    }
    

    Vielleicht stehe ich gerade massiv auf dem Schlauch, aber das Beispiel ist von der Struktur identisch mit dem von MS veröffentlichten Beispiel, wie man einem Prozess Zugriff auf große Seiten verschafft. Und ja, das System nach der Änderung der Benutzerprivilegien neugestartet habe ich auch. Mehrmals.

    Was muss ich noch tun, um der Anwendung das Privileg zu geben, große Seiten zu reservieren?
    Dass ich später noch AllocateUserPhysicalPages aufrufen muss, um die Anzahl an großen Seiten zu bestimmen, lassen wir jetzt mal vollkommen außer Acht; soweit sind wir noch gar nicht.



  • privileges.Count = 1;

    :p



  • Yup, ich wusste es - ich bin ein Vollidiot. 😋

    Vielen Dank!



  • Hm, ich fürchte, ich muss den Thread doch noch mal ausgraben.

    Die Dokumentation von MS besagt, dass es keine Probleme darstellen sollte, nachdem man erfolgreich AdjustTokenPrivileges aufgerufen hat, VirtualAlloc mit MEM_LARGE_PAGES aufzurufen:

    1. Obtain the SeLockMemoryPrivilege privilege by calling the AdjustTokenPrivileges function. For more information, see Assigning Privileges to an Account and Changing Privileges in a Token.
    2. Retrieve the minimum large-page size by calling the GetLargePageMinimum function.
    3. Include the MEM_LARGE_PAGES value when calling the VirtualAlloc function. The size and alignment must be a multiple of the large-page minimum.

    Jetzt habe ich noch ein bisschen nachgeforscht und bin auf diesen Thread gestoßen, in dem behauptet wird, dass ich nicht MEM_RESERVE mit MEM_LARGE_PAGES in dem gleichen Funktionsaufruf verknüpfen kann. Deswegen habe ich ein kleines Programm geschrieben, welches über VirtualAlloc zuerst eine etwas größere Range reserviert, dann die nächste alignbare Adresse ermittelt und diese einem zweiten VirtualAlloc -Aufruf übergibt (mit MEM_COMMIT | MEM_LARGE_PAGES ).

    In dem folgenden Programm habe ich alle drei Punkte beachtet, und trotzdem bekomme ich jetzt vom zweiten VirtualAlloc -Aufruf ERROR_PRIVILEGE_NOT_HELD (1314) zurück.

    #include <windows.h>
    #include <stdio.h>
    
    DWORD set_privilege
    (
        LPCTSTR privilege_name,
        BOOL enable
    )
    {
        DWORD my_errno;
        HANDLE token;
    
        TOKEN_PRIVILEGES privileges;
    
        if(!OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,&token))
        {
            my_errno = GetLastError();
            goto END_NO_TOKEN;
        }
    
        if(!LookupPrivilegeValue(NULL,privilege_name,&privileges.Privileges[0].Luid))
        {
            my_errno = GetLastError();
            goto END_TOKEN;
        }
    
        if(enable)
            privileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
        else
            privileges.Privileges[0].Attributes = 0;
        privileges.PrivilegeCount = 1;
    
        SetLastError(0);
        if(!AdjustTokenPrivileges(token,FALSE,&privileges,0,NULL,NULL))
        {
            my_errno = GetLastError();
            if(my_errno != ERROR_SUCCESS)
                goto END_TOKEN;
    	}
    
    	my_errno = ERROR_SUCCESS;
    END_TOKEN:
    	CloseHandle(token);
    
    END_NO_TOKEN:
    	return my_errno;
    }
    
    int main(void)
    {
        DWORD my_errno;
        size_t size_page;
        size_t size_real;
        size_t size_effective;
        void*ptr,*aligned_ptr,*return_ptr;
    
        if(ERROR_SUCCESS != (my_errno = set_privilege(SE_LOCK_MEMORY_NAME,TRUE)))
        {
            fprintf(stderr,"set_privilege schlug fehl, Fehlercode: %u\n",my_errno);
            goto END;
        }
    
        size_page = GetLargePageMinimum();
        size_effective = (1024 * 1024);
        size_real = size_effective + size_page;
    
        printf("Seitengroesse:\t\t%I64x\n",size_page);
        printf("Bytes zu reservieren:\t%I64x\n",size_effective);
    
        ptr = VirtualAlloc(NULL,size_real,MEM_RESERVE,PAGE_NOACCESS);
        aligned_ptr = (void*)((uintptr_t)ptr + (size_page - ((uintptr_t)ptr % size_page)));
        SetLastError(0);
        return_ptr = VirtualAlloc(aligned_ptr,size_effective,MEM_COMMIT | MEM_LARGE_PAGES,PAGE_READWRITE);
        my_errno = GetLastError();
    
        printf
        (
            "Gesamtbereich:\t\t%p - %p\n"
            "Effektiver Bereich:\t%p - %p\n"
            "Mapping erstellt:\t%p\n"
            "Fehlercode:\t\t%u\n",
            ptr,(void*)((uintptr_t)ptr + size_real),
            aligned_ptr,(void*)((uintptr_t)aligned_ptr + size_effective),
            return_ptr,my_errno
        );
    
    END:
        getc(stdin);
        return 0;
    }
    

    Ausgabe:

    Seitengroesse:        200000
    Bytes zu reservieren: 100000
    Gesamtbereich:        0000000000720000 - 0000000000A20000
    Effektiver Bereich:   0000000000800000 - 0000000000900000
    Mapping erstellt:     0000000000000000
    Fehlercode:           1314
    

    Auch diesmal bin ich mir sicher, irgendeinen blöden Fehler gemacht zu haben, aber ich sehe ihn nicht.



  • Ich würde sagen du schläfst erstmal deinen Rausch aus bevor du weiter programmierst.

    The size and alignment must be a multiple of the large-page minimum.

    Dein Code:

    size_effective = (1024 * 1024);
    VirtualAlloc(aligned_ptr,size_effective,MEM_COMMIT | MEM_LARGE_PAGES,PAGE_READWRITE);
    

    size_effective ist kein mehrfaches von large-page minimum.



  • Nö, eben nicht:

    size_effective = size_page;
    

    Das anstelle von (1024 * 1024) bringt immer noch den gleichen Fehlercode. Außerdem besagt die Doku von VirtualAlloc:

    If the lpAddress parameter is NULL, this value is rounded up to the next page boundary. Otherwise, the allocated pages include all pages containing one or more bytes in the range from lpAddress to lpAddress+dwSize.

    Aber unabgängig davon funzt es ja eh nicht, wenn ich direkt 2 Mebibyte als Größe angebe.



  • Ich behaupte mal, dass Du SeLockMemoryPrivilege gar nicht hälst und daher auch nicht enablen kannst. Deswegen solltest Du Dir den Aufruf von AdjustTokenPrivileges mal genauer ansehen:

    if(!AdjustTokenPrivileges(token, FALSE, &privileges, 0, nullptr, nullptr))
    {
        my_errno = GetLastError();
        if(my_errno != ERROR_SUCCESS)
            goto END_TOKEN;
    }
    else
    {
        if(GetLastError() == ERROR_NOT_ALL_ASSIGNED)
        {
            MessageBox(nullptr, TEXT("Nix Privilege!"), nullptr, MB_OK);
        }
    }
    


  • Sehr gut. Ja, der Abfragecode bekommt so nicht alle Fehler sein, weil ich davon ausgegangen bin:

    If the function succeeds, the return value is nonzero.

    Ich habe das jetzt mal geändert, und es kommt tatsächlich ERROR_NOT_ALL_ASSIGNED zurück.

    Jetzt bin ich aber fast soweit, einen Screenshot zu posten von einer Testmachine, wo ich die Zugriffsrechte spezifiziert habe. Ich habe es hier genau stehen: user (der Default-User, unter dem ich das Programm auch ausführe), Administrator, und Gast.

    Ich habe jetzt mal eine Testmachine aufgesetzt, alle Daten rüberkopiert, und versucht zu starten. Nichts.
    Dann habe ich versucht, die Anwendung als Administrator zu starten. Da hat die Anwendung überhaupt nicht mehr gestartet, aber in der Ereignisanzeige tauchte ein PCA2-Fehler auf. Keine Ahnung, was das nun wieder war.

    Am Ende habe ich UAC deaktiviert und neugestartet. Und JETZT funktioniert die Reservierung großer Seiten. Wer einen guten Link hat, der das beschriebene Verhalten erklärt, bitte her damit - ich schlage nur noch die Hände über dem Kopf zusammen.



  • dachschaden schrieb:

    Sehr gut. Ja, der Abfragecode bekommt so nicht alle Fehler sein, weil ich davon ausgegangen bin:

    If the function succeeds, the return value is nonzero.

    Und direkt danach steht:

    MSDN schrieb:

    To determine whether the function adjusted all of the specified privileges, call GetLastError, which returns one of the following values when the function succeeds:

    Lesen, Leute, lesen!

    dachschaden schrieb:

    Am Ende habe ich UAC deaktiviert und neugestartet. Und JETZT funktioniert die Reservierung großer Seiten. Wer einen guten Link hat, der das beschriebene Verhalten erklärt, bitte her damit - ich schlage nur noch die Hände über dem Kopf zusammen.

    Das Ganze funktioniert natürlich auch mit UAC, sowohl unter einem administrativen als auch einem eingeschränkten Konto. Wenn man aber beim Verwalten von Privilegien schon überfordert ist, bleibt wohl nur noch das Zusammenschlagen der Hände über dem Kopf...



  • Nein die Windows API ist an dieser Stelle einfach geisteskrank. 😃 Bei Erfolg GetLastError aufrufen müssen?? Gehts noch?



  • So, jetzt reicht's.

    OpenProcessToken und LookupPrivilegeValue geben wie AdjustTokenPrivileges BOOL s zurück. Der Standardweg der Fehlerermittlung bei solchen Funktionen ist sonst immer: Prüfe, ob die Funktion FALSE zurückgibt, wenn ja, rufe GetLastError auf, um den Fehlercode zu bekommen. Wie bei VirtualAlloc . Oder VirtualProtect . Verschissen nochmal immer.

    Und die Fehlercodeanfrage hat das Problem auch nicht hervorgerufen. Sie hat sie nicht angezeigt, gut, da hat sie nicht funktioniert. Aber der von VirtualAlloc zurückgegebene Code hat das Problem ja bereits angezeigt.

    Dann: du hast nicht kapiert, dass ich den Nutzern, die das Privileg haben sollen, das Privileg auch zugewiesen haben.

    Sperren von Seiten im Speicher: user,Administrator,Gast

    Was zur Hölle soll denn noch zugewiesen werden?! Steht da nicht. Eingetragen sind sie aber! Als "user" wird das Privileg nicht erhalten, als Administrator wollte Windows das Programm nicht mal starten und hat lieber die Latschen aufgestellt. Ohne Fehlermeldung. Lieber wird in der Ereignisanzeige ein Fehler festgestellt, der mir beim Googeln überhaupt keine Hilfestellung ist. Die UAC zu deaktivieren gefällt mir auch nicht. Hatte ich eine andere Wahl? Vielleicht. Darauf wird aber nicht hingewiesen.

    So eine inkompetente Drecksscheiße habe ich bei Linux noch nicht erlebt. In Linux kann root beim Booten eine Hugepage-Seitenanzahl angeben, die man später noch ändern kann. Und dann kann jedes verdammte Programm diese Seiten reservieren. Und hier musste ich nicht ein einziges verdammte Mal nachfragen, weil die Dokumentation relativ klar ist und kein manuelles Gefrickel in einer Privilegienliste benötigt.

    Ich bleibe bei meinen zusammengeschlagenen Händen. Dennoch vielen Dank! Und Windows-Nutzer können bei kleinen Seiten bleiben, das ist viel zu viel Aufwand für so eine kleine Sache, bei der es auf ordentlichen Betriebssystemen überhaupt keine Probleme gibt.



  • dachschaden schrieb:

    So, jetzt reicht's.

    OpenProcessToken und LookupPrivilegeValue geben wie AdjustTokenPrivileges BOOL s zurück. Der Standardweg der Fehlerermittlung bei solchen Funktionen ist sonst immer: Prüfe, ob die Funktion FALSE zurückgibt, wenn ja, rufe GetLastError auf, um den Fehlercode zu bekommen. Wie bei VirtualAlloc . Oder VirtualProtect . Verschissen nochmal immer.

    So auch hier. Ich sehe da keinen Unterschied. Nur wenn Du weitere Informationen haben willst, rufst Du GetLastError auf. In diesem Fall darfst Du das im Erfolgsfall, deswegen ist das hier auch so dokumentiert. Es gibt da schlielich auch andere verschissene Funktionen die das genau so machen, z.B. CreateMutex.

    Das Problem hier ist doch, dass Du einerseits Deinen Rechner nicht bedienen kannst und andererseits zu blöde zum Lesen der Doku bist. Und nur deswegen brauchtest Du hier weitere Info.

    Vielleicht solltest Du doch lieber Briefmarken sammeln, das schont die Nerven.



  • Mox schrieb:

    Das Problem hier ist doch, dass Du einerseits Deinen Rechner nicht bedienen kannst und andererseits zu blöde zum Lesen der Doku bist. Und nur deswegen brauchtest Du hier weitere Info.

    Hm, also ich überlege:

    - ich befolge die Dokumentation zum Setzen der Privilegien.
    - es funktioniert nicht.
    - ich versuche etwas, das Betriebssystem stellt sich in die Querre.
    - ich deaktiviere den Teil, der sich in die Querre stellt.
    - es funktioniert auf einmal.
    - aber ich habe mein Betriebssystem nicht im Griff.

    Denkst du eigentlich nach, bevor du postest, oder sind das Reflexhandlungen? Ich glaube, es sind Reflexhandlungen.

    Mox schrieb:

    Vielleicht solltest Du doch lieber Briefmarken sammeln, das schont die Nerven.

    Das kann ich unumgewunden zurückgeben. Von deiner passiven Aggressivität muss ich mich nicht beeindrucken lassen.

    EDIT: Und der hier ist für das Archiv. Genau das habe ich gemacht, Schritt für Schritt, für alle Konten. Und dennoch funktioniert es nur ohne UAC. "Klarer Fall von PEBKEC", am Arsch.


Log in to reply