CreateProcess e i Job di Windows Vista/7.

Salve a tutti.

Oggi volevo puntare lo sguardo su un flag delle varie CreateProcess, CreateProcessAsUSer, CreateProcessWithTokenW, CreateProcessWithLogonW: CREATE_BREAKAWAY_FROM_JOB.

Cosa dice MSDN a proposito dei Job: http://msdn.microsoft.com/en-us/library/ms684161(VS.85).aspx

Un Job object è un insieme di processi che possono essere maneggiati come fossero un processo unico per alcuni aspetti.. ad esempio, possono avere un nome, possono avere delle ACL, e condividono alcune proprietà sulle quali possono essere imposti dei limiti, tipo quanta memoria al massimo possono consumare tutti insieme, quanto tempo possono sopravvivere al massimo, su quali processori devono girare o quanti processi contemporanemente possono essere in esecuzione.

Quindi, quando un processo è parte di un Job, consapevolmente o inconsapevolmente (perchè nel vostro design non avevate immaginato che un giorno il vostro eseguibile potesse essere aggiunto ad un Job), se da quello volete creare un processo esterno che non sia parte del Job, dovete passare il flag CREATE_BREAKAWAY_FROM_JOB.

Questo flag che una volta non veniva preso in considerazione adesso diventa di fondamentale importanza, in quanto non potete più dare per scontato che il vostro programma nel quale usate una delle funzioni su menzionate, non venga per un motivo a voi sconosciuto e comunque inatteso, incluso in un Job.

Senza quel flag, il programma che andate ad avviare verrà incluso nel Job stesso, se le regole applicate al Job lo consentono. Se non lo consentono il processo NON verrà creato!!

Potrebbe anche essere che il Job abbia regole così restrittive che neanche la presenza di questo flag potrà aiutare. Ma questo è il caso limite..

Normalmente, i Job sono creati con il flag JOB_OBJECT_LIMIT_BREAKAWAY_OK che consente la crezione di nuovi processi esterni al Job a patto che la CreateProcess passi quel parametro. Se il parametro non viene passato il sistema operativo stesso ritorna Access Denied.

Quindi, quello che una volta era un flag assolutamente secondario, adesso diventa fondamentale, perchè sono diventati numerosi i casi in cui un processo si trova all’interno di un Job. Ad esempio, avete mai dato un’occhiata con Process Explorer alla vostra macchina.

Process Explorer, indica, di default con il colore marrone, i processi che sono parte di un Job:

Job

Su Windows 7 64 bit, di default, tutti i processi a 32 bit, come Outlook ad esempio, fanno parte di un Job chiamato “\BaseNamedObjects\PCA_{“something”}”.

Quello è il Program Compatibility Assistant di Windows 7, che mette il processo in un Job speciale per gestire evetuali eccezioni dell’applicazione e impostare su quello delle shim di compatibilità, per risolvere eventuali problemi di compatibilità noti.

Questo magnifico post di Chris Jackson spiega nel dettaglio il meccanismo usato dal PCA.

Ora, veniamo ad un caso reale…

 

WMI gira all’interno di un Job object su Windows 7.

WMI la conoscete tutti. Grazie a questi oggetti è possibile automatizzare un grande numero di task amministrativi. WMI però, è anche un modello a oggetti estensibile. Cosa vuol dire questo? Vuol dire che relaizzando un WMI Provider, si può estendere il modello a oggetti di WMI e poi usare gli stessi script che si usano tutti i giorni con i propri oggetti, in modo del tutto integrato e trasparente.

Un cliente, ha realizzato un WMI Provider nel quale doveva ad un certo punto eseguire un processo esterno che serviva a raccogliere dai dati che venivano poi elaborati dallo stesso provider. Fino ad XP tutto ok, ma installato il Provider su Vista o su Windows 7, questo non funzioanva più restituendo implacabile tutte le volte un Access Denied.

 

Esaminato il codice usato, sembrava tutto a posto. Il cliente usa la CreateProcessAsUser.

 bResult = CreateProcessAsUser(
        hTargetToken,       // client's access token
        NULL,               // name of executable module
        szCmdline,          // command line string
        NULL,               // pointer to process SECURITY_ATTRIBUTES
        NULL,               // pointer to thread SECURITY_ATTRIBUTES
        false,              // inherit handles
        NORMAL_PRIORITY_CLASS  | CREATE_NO_WINDOW | DETACHED_PROCESS |CREATE_UNICODE_ENVIRONMENT,  // creation flags
        t_Environment,      // pointer to new environment block 
        NULL,               // current directory name
        &si,                // startup information       
        &pi                 // process information      
        );

I parametri erano tutto sommato corretti ad un primo esame. Li ho usati allo stesso modo in decine di casi.. eppure, provando a riprodurre il problema, ottengo lo steso identico errore: Access Denied. Mistero.. Le ho provate un pò tutte, ma non ne venivo fuori. Anche perchè mi sono reso conto che il problema non era tanto a livello di codice utente, ma era nel kernel del sistema operativo stesso, perchè era quello che ritornava Access Denied:

 

 eax=0115e794 ebx=00000000 ecx=00000054 edx=00430170 esi=00480180 edi=02000000
eip=76ed9438 esp=0115e368 ebp=0115e978 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
ntdll!NtCreateUserProcess:
76ed9438 b87f010000      mov     eax,17Fh
0:002> t
eax=0000017f ebx=00000000 ecx=00000054 edx=00430170 esi=00480180 edi=02000000
eip=76ed943d esp=0115e368 ebp=0115e978 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
ntdll!ZwCreateUserProcess+0x5:
76ed943d ba0003fe7f      mov     edx,offset SharedUserData!SystemCallStub (7ffe0300)
0:002> t
eax=0000017f ebx=00000000 ecx=00000054 edx=7ffe0300 esi=00480180 edi=02000000
eip=76ed9442 esp=0115e368 ebp=0115e978 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
ntdll!ZwCreateUserProcess+0xa:
76ed9442 ff12            call    dword ptr [edx]      ds:0023:7ffe0300={ntdll!KiFastSystemCall (76ed9a90)}
0:002> t
eax=0000017f ebx=00000000 ecx=00000054 edx=7ffe0300 esi=00480180 edi=02000000
eip=76ed9a90 esp=0115e364 ebp=0115e978 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
ntdll!KiFastSystemCall:
76ed9a90 8bd4            mov     edx,esp
0:002> t
eax=0000017f ebx=00000000 ecx=00000054 edx=0115e364 esi=00480180 edi=02000000
eip=76ed9a92 esp=0115e364 ebp=0115e978 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
ntdll!KiFastSystemCall+0x2:
76ed9a92 0f34            sysenter
0:002> t
eax=c0000022 ebx=00000000 ecx=00000001 edx=ffffffff esi=00480180 edi=02000000
eip=76ed9444 esp=0115e368 ebp=0115e978 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
ntdll!ZwCreateUserProcess+0xc:
76ed9444 c22c00          ret     2Ch

eax=00000000 ebx=00000000 ecx=76ee5c39 edx=00000021 esi=00000005 edi=02000000
eip=75edc5a3 esp=0115e388 ebp=0115e38c iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
kernel32!BaseSetLastNTError+0x17:
75edc5a3 8bc6            mov     eax,esi
0:002> p
eax=00000005 ebx=00000000 ecx=76ee5c39 edx=00000021 esi=00480180 edi=02000000
eip=75edc5a6 esp=0115e38c ebp=0115e38c iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
kernel32!BaseSetLastNTError+0x1a:
75edc5a6 5d              pop     ebp
0:002> p
eax=00000005 ebx=00000000 ecx=76ee5c39 edx=00000021 esi=00480180 edi=02000000
eip=75ebfe90 esp=0115e398 ebp=0115e978 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
kernel32!CreateProcessInternalW+0x55c:
75ebfe90 899d2cfeffff    mov     dword ptr [ebp-1D4h],ebx ss:0023:0115e7a4=00000008

E così, armato di santa pazienza, mi sono attrezzato per fare Kernel Debugging. Un giorno questo sarà lo scopo di un altro post Wink

Alla fine di tutto, proprio nel momento in cui il processo sta partendo, viene effettuato l’ultimo controllo:

 Job = Parent->Job;
if ((Job != NULL) && ((Job->LimitFlags & JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK) == 0)) {
    if ((CreateFlags & PROCESS_CREATE_FLAGS_BREAKAWAY) != 0) {
        if ((Job->LimitFlags & JOB_OBJECT_LIMIT_BREAKAWAY_OK) == 0) {
            Status = STATUS_ACCESS_DENIED;
        } else {
            Status = STATUS_SUCCESS;
        }
    } else {
        Status = PspGetJobFromSet (Job, JobMemberLevel, &Job);
…
    if (!NT_SUCCESS (Status)) {
        goto exit_return_status;
    }
}

Se il processo parent è associato ad un Job e il flag JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK non è impostato, quindi automaticamente i processi creati non possono “andarsene”, allora viene controllato se tra i flags impostati nelle opzioni della CreateProcess, esiste per caso il CREATE_BREAKAWAY_FROM_JOB. Se esiste, ma JOB_OBJECT_LIMIT_BREAKAWAY_OK non è impostato, viene ritornato Access Denied, altrimenti Success. Se non esiste, viene ritornato il valore dell’eleborazione delle restrizioni impostate sul Job. Nel caso reale era questo che ritornava Access Denied.

Status_Access_Denied è il valore che abbiamo visto sopra c0000022 che viene poi tradotto in errore 5, Access Denied

 # for hex 0xc0000022 / decimal -1073741790
STATUS_ACCESS_DENIED ntstatus.h
# {Access Denied}
# A process has requested access to an object, but has not
# been granted those access rights.

Tutto perchè adesso su Windows 7, di default, i processi WMI sono contenuti in un Job object

WMIjob Qui potete chiaramente vedere i processi inclusi nel Job e i limiti impostati sul Job stesso.

La soluzione è stata quella di aggiungere semplicemente il flag al codice del cliente. Quello ha superato le limitazioni del Job e da quel momento in poi il processo esterno poteva essere creato con successo.

 bResult = CreateProcessAsUser(
        hTargetToken,       // client's access token
        NULL,               // name of executable module
        szCmdline,          // command line string
        NULL,               // pointer to process SECURITY_ATTRIBUTES
        NULL,               // pointer to thread SECURITY_ATTRIBUTES
        false,              // inherit handles
        NORMAL_PRIORITY_CLASS  | CREATE_NO_WINDOW | DETACHED_PROCESS |CREATE_UNICODE_ENVIRONMENT | CREATE_BREAKAWAY_FROM_JOB ,  // creation flags
        t_Environment,      // pointer to new environment block 
        NULL,               // current directory name
        &si,                // startup information       
        &pi                 // process information      
        );

 

Quindi, bottom line, a partire da Windows 7, consiglierei a tutti di aggiungere il flag CREATE_BREAKAWAY_FROM_JOB nelle chiamate CreateProcess*, in quanto a priori non è possibile sapere se il nostro programma verrà o meno inserito in un Job e quali saranno i limiti imposti su quel Job.

Nella maggior parte dei casi questa dovrebbe essere la migliore soluzione. Per casi specifici si potranno studiare le diverse opzioni, ma in generale questa adesso è una cosa di cui tenere conto e non da ignorare.

 

Alla prossima!

Mario Raccagni

Senior Support Engineer

Platform Development Support Team