Removing PROCESS_TERMINATE flag causes hang

This is more of a point to raise rather than question, as I couldn’t find
any previous discussion about it in the archives so thought it’d be good to
document it.
I noticed that when removing the PROCESS_TERMINATE flag on some requests, I
was occasionally seeing the process hang before it had chance to properly
startup.

A bit of background, ObRegisterCallbacks allows you to register for process
handle request callbacks, which gives us an OB_PRE_CREATE_HANDLE_INFORMATION
structure with the handle information. We can then modify the access mask
before the handle is created, resulting in a handle with reduced rights. The
docs state the following :

“An ACCESS_MASK value that specifies the access rights to grant for the
handle. By default, this member equals OriginalDesiredAccess, but the
ObjectPreCallback routine can modify this value to restrict the access that
is granted.”
and
“If the access right is listed as a modifiable flag, the access right can be
removed.”
https://msdn.microsoft.com/en-us/library/windows/hardware/ff558725(v=vs.85).
aspx

Removing the PROCESS_TERMINATE flags works well in most scenarios, however
this hang was occurring which seemed to be linked to process elevation.

Here’s what’s happening:

  • Parent process calls CreateProcess to start a child.
  • CreateProcessInternalW starts to setup the process object and then
    requests a handle to it with PROCESS_ALL_ACCESS
  • The child process being created is something we want to stop being
    terminated (for some users), so the PROCESS_TERMINATE right is removed and
    the process continues its setup.
  • The first thread is created in the child process, it does some init
    and enters a suspended state.
  • CreateProcessInternalW realises that the child process needs
    elevating, so it calls TerminateProcess to cleanup the suspended process
    that it created.
  • TerminateProcess fails because it doesn’t have the rights. However
    Windows doesn’t check the return code and just calls WaitForSingleObject
    using the process handle
  • The child process remains suspended and never terminates, the handle
    is never signalled and parent thread waits indefinitely, so we get a hang in
    the parent thread and a zombie process.

There’s no way of knowing that this handle request is coming from Windows as
part of the CreateProcess routine (without deciphering the callstack…) so
we can’t put in any special case code for this scenario. There’s therefore
no easy way to avoid this lockup without putting in more fine grain control
into the rules.

I’m just pointing this out at the docs point to this being a valid thing to
do, however CreateProcessInternal isn’t capable of handling this scenario
and just assumes that it will always have the right to terminate. The docs
are missing this piece of information, which breaks this design.

I know some people will be thinking that you should never remove this flag,
and I tend to agree, however enterprise products do occasionally have to do
things like this when large customers request it, and this is one of those
cases. It’s a shame because the design is really quite nice otherwise, and
this issue might force people down the detour route instead, which I’m
personally very much against.

Ged.

If you want processes created by an user process be non-killable by the user, YOU’RE DOING THINGS WRONG.

The proper way to create a process non-killable by an user (even though it’s running under an user’s token) is to have a service create a process with user’s token by calling CreateProcessAsUser, and set the appropriate ACL on it.

Thanks for the caps, I probably would have missed the second half of your
sentence otherwise…

I’m not trying to do what you suggest, and your suggestion is completely the
wrong solution for the problem I’m trying to solve.

-----Original Message-----
From: xxxxx@lists.osr.com
[mailto:xxxxx@lists.osr.com] On Behalf Of
xxxxx@broadcom.com
Sent: 09 October 2016 14:03
To: Windows System Software Devs Interest List
Subject: RE:[ntdev] Removing PROCESS_TERMINATE flag causes hang

If you want processes created by an user process be non-killable by the
user, YOU’RE DOING THINGS WRONG.

The proper way to create a process non-killable by an user (even though it’s
running under an user’s token) is to have a service create a process with
user’s token by calling CreateProcessAsUser, and set the appropriate ACL on
it.


NTDEV is sponsored by OSR

Visit the list online at:
http:

MONTHLY seminars on crash dump analysis, WDF, Windows internals and software
drivers!
Details at http:

To unsubscribe, visit the List Server section of OSR Online at
http:</http:></http:></http:>

To make a process truly unkillable, it’s not enough to deny TERMINATE_PROCESS right.

for the problem I’m trying to solve

I’m afraid “the problem you’re trying to solve” is you’re trying to find out how to implement a wrong solution for the feature the customers want.

> To make a process truly unkillable, it’s not enough to deny
TERMINATE_PROCESS right.

Yep, I’m well aware of that. I’ve currently listed 12 very different
‘attack’ methods, of which I’m sure there are more for anyone more
imaginative than I am.

I’m afraid “the problem you’re trying to solve” is you’re trying to find
out how to implement a wrong solution for the feature the customers want.

To be fair, you don’t know what problem I’m trying to solve, and I wasn’t
asking for any suggestions on solving anything

The solution I listed previously is a commonly accepted solution to what is
a slightly controversial topic. There’s quite a few topics on ntdev pointing
to the use of ObRegisterCallbacks to modify handles. For example this
lengthy post:
https://www.osronline.com/showThread.CFM?link=261303

Microsoft themselves also provide sample code showing this exact solution to
restrict PROCESS_TERMINATE access on a particular target process
https://github.com/Microsoft/Windows-driver-samples/tree/master/general/obca
llback/driver
If you run the sample using a target process that requires elevation, you’ll
see that it exhibits this exact problem and causes a hang in the parent
thread and a zombie process.

I’m just documenting the issues I found when researching, in the hope it
might help someone else in the future. It’s surely worth pointing out that
following Microsoft’s lead on this topic will cause problems. The WDK sample
code should probably be removed, or CreateProcessInternalW should be ‘fixed’
to avoid the hangs.

Ged.

Well, what would you propose that CreateProcessAsUser is supposed to do if it discovers that it can’t finish preparing the new process to run, but access to terminate it has been revoked silently (even though CreateProcessAsUser itself expects that it has obtained terminate access)? There are a number of reasons why this may happen, including (but not limited to) elevation required. It is not necessarily reasonable to account for them all up front, as some operations may need to be done on the new process once it is created (and which could fail due to e.g. low resources).

This is a classic, fundamental problem with filters that decide to mess with access rights, or otherwise alter the “ground rules” from their norm: You can introduce failure modes that no program expects (or even worse, that there is just no reasonable way to handle at all), because the behavior of the system that programs may rightly expect to depend on has suddenly been changed out from under them.

Filter capabilities like this are very much in a quite fragile “You break it, you buy it” sort of category if broadly used. There are a whole bunch of ways to break things in subtle cases (revoking access to terminate the process from the creator being one example). Those filtering mechanisms are best used sparingly and in very narrow circumstances, if at all.

  • S (Msft)

-----Original Message-----
From: xxxxx@lists.osr.com [mailto:xxxxx@lists.osr.com] On Behalf Of Ged Murphy
Sent: Sunday, October 09, 2016 12:06 PM
To: Windows System Software Devs Interest List
Subject: RE: [ntdev] Removing PROCESS_TERMINATE flag causes hang

> To make a process truly unkillable, it’s not enough to deny
TERMINATE_PROCESS right.

Yep, I’m well aware of that. I’ve currently listed 12 very different ‘attack’ methods, of which I’m sure there are more for anyone more imaginative than I am.

> I’m afraid “the problem you’re trying to solve” is you’re trying to
> find
out how to implement a wrong solution for the feature the customers want.

To be fair, you don’t know what problem I’m trying to solve, and I wasn’t asking for any suggestions on solving anything

The solution I listed previously is a commonly accepted solution to what is a slightly controversial topic. There’s quite a few topics on ntdev pointing to the use of ObRegisterCallbacks to modify handles. For example this lengthy post:
https://www.osronline.com/showThread.CFM?link=261303

Microsoft themselves also provide sample code showing this exact solution to restrict PROCESS_TERMINATE access on a particular target process https://github.com/Microsoft/Windows-driver-samples/tree/master/general/obca
llback/driver
If you run the sample using a target process that requires elevation, you’ll see that it exhibits this exact problem and causes a hang in the parent thread and a zombie process.

I’m just documenting the issues I found when researching, in the hope it might help someone else in the future. It’s surely worth pointing out that following Microsoft’s lead on this topic will cause problems. The WDK sample code should probably be removed, or CreateProcessInternalW should be ‘fixed’
to avoid the hangs.

Ged.


NTDEV is sponsored by OSR

Visit the list online at: http:

MONTHLY seminars on crash dump analysis, WDF, Windows internals and software drivers!
Details at http:

To unsubscribe, visit the List Server section of OSR Online at http:</http:></http:></http:>

The thing is, you’re not the first here to pop up with the question “how do I make a process unkillable”. You think you know how to make a process unkillable, but it’s just a wrong approach to the problem. The right approach is to make sure the user’s process doesn’t have “write”-type rights to the protected process object, by means of DACL. This also prevents DLL injection and hook installation. One non-bullet-proof way is to have the CreateProcess caller supply security descriptor for the new process, with restricted access for the current user. This is non-bullet-proof because an object owner can always change the security descriptor of an object. If CreateProcess caller runs under user’s account, then the process object owner is the user. To work around that, CreateProcess(AsUser) needs to be called by a privileged (service) account.

>To be fair, you don’t know what problem I’m trying to solve,

Well, let me guess…probably, just writing malware, right.This is the very first thing that gets into one’s head when they encounter the requirements like “unkillable process” or “undeletable file”, particularly if the former is backed up by the latter.

Search this list for more info and see how posters who ask questions like that are generally treated here. You will find quite a few “funny” threads where the usual suspects shout the word “MALWARE” in capitals and request these posters to disclose the names of their companies, as well as of products that require the above mentioned features.

You will see how some other posters are exclaiming “Microsoft has granted users the rights to terminate any process that happens to be running under their corresponding accounts. This has been granted to us by Microsoft!!!”, saying the certain word that appears twice in the above quotation with the same reverence that deeply religious people mention the name of their deity/prophet/idol/software license/whatever happens to be an object of adoration in their particular religion.

You will also find quite a few examples of truly yours ranting all over the place and arguing with everyone just about anything one may possibly imagine, as well as attacking MSFT and those whom he generally refers to as “MSFT Sycophant Club” (as a matter of a fact, it was the above mentioned club’s “International Anthem” that got him on the moderation list last year).

In other words, enjoy the read…

Anton Bassov

xxxxx@valhallalegends.com wrote:

Well, what would you propose that CreateProcessAsUser is supposed to do
if it discovers that it can’t finish preparing the new process to run, but
access
to terminate it has been revoked silently (even though CreateProcessAsUser
itself expects that it has obtained terminate access)?

Hi Ken,
Yeah it’s a tricky one, as you say there’s no one-stop solution. I guess a
good start would be checking the return of TerminateProcess and not calling
waiting on the process handle. At least we’d only have a suspended process
and not a hung parent thread.
However one idea could be, assuming the terminate error was ACCESS_DENIED,
to hand off the terminate job to the system process. At least that would
give the OS a chance to clean up in this scenario. Anyone blocking system
from terminating processes would be clearly have a very bad design.

The other option is to add a big note to the sample code, saying “this code
will cause hangs”, or preferably just remove it entirely from the WDK
samples.

Incidentally, the hang can be fixed by resuming the hung processes, which
gives you a halfplemented processes, but one that is killable via the GUI.
At least this resolves the hang and zombie.

xxxxx@broadcom.com wrote:

The thing is, you’re not the first here to pop up with the question
“how do I make a process unkillable”

I didn’t pop up with that question. In fact, I didn’t ask any question at
all. You’re assuming I have a problem, and you’re assuming you have the
correct fix. Neither of those assumptions are correct. For the record,
you’ll be pleased to know that if I were trying to do what you suggest, I
would likely be using DACLs (and a mini-filter to block ownership changes).

This also prevents DLL injection and hook installation
Yep, amongst other things. Interestingly, you can also prevent these things
by stripping rights.
Handle duplication, VM read/write, remote thread termination,
getting/setting thread context, assigning/terminating job objects. They can
all be avoided with stripping handle rights. I’m not saying it’s the correct
solution, and I’m not asking for help or ratification, I’m just saying it’s
possible and Microsoft provide documented APIs to help you do it :slight_smile:

xxxxx@hotmail.com wrote:

> To be fair, you don’t know what problem I’m trying to solve,
Well, let me guess…probably, just writing malware, right.

No!. If I was writing malware, I’d just make the process undetectable, not
unkillable.

Search this list for more info and see how posters who ask
questions like that are generally treated here.
I’m well aware, I’ve been reading this list (well, I’m normally on ntfsd)
daily for the last 15 years… In fact from back in the day when Ken used
to work in open source :slight_smile:

Look, I haven’t turned up asking any questions. I haven’t requested hints or
pointers on how to stop processes from being killed. I’m certainly not a
malware writer, I write enterprise software for multi-billion dollar
industries, and have done for a looong time.

I just wrote a mail to point out an issue I found when doing some research.
An issue that hadn’t been documented before on this list, and an issue that
Microsoft themselves exhibit in their own sample code. It’s a bit of a sad
day when you try to post something helpful to the list and get shot down for
being a malware writer.

Ged.

-----Original Message-----
From: xxxxx@lists.osr.com
[mailto:xxxxx@lists.osr.com] On Behalf Of Skywing
Sent: 09 October 2016 22:06
To: Windows System Software Devs Interest List
Subject: RE: [ntdev] Removing PROCESS_TERMINATE flag causes hang

Well, what would you propose that CreateProcessAsUser is supposed to do if
it discovers that it can’t finish preparing the new process to run, but
access to terminate it has been revoked silently (even though
CreateProcessAsUser itself expects that it has obtained terminate access)?
There are a number of reasons why this may happen, including (but not
limited to) elevation required. It is not necessarily reasonable to account
for them all up front, as some operations may need to be done on the new
process once it is created (and which could fail due to e.g. low resources).

This is a classic, fundamental problem with filters that decide to mess with
access rights, or otherwise alter the “ground rules” from their norm: You
can introduce failure modes that no program expects (or even worse, that
there is just no reasonable way to handle at all), because the behavior of
the system that programs may rightly expect to depend on has suddenly been
changed out from under them.

Filter capabilities like this are very much in a quite fragile “You break
it, you buy it” sort of category if broadly used. There are a whole bunch
of ways to break things in subtle cases (revoking access to terminate the
process from the creator being one example). Those filtering mechanisms are
best used sparingly and in very narrow circumstances, if at all.

- S (Msft)

-----Original Message-----
From: xxxxx@lists.osr.com
[mailto:xxxxx@lists.osr.com] On Behalf Of Ged Murphy
Sent: Sunday, October 09, 2016 12:06 PM
To: Windows System Software Devs Interest List
Subject: RE: [ntdev] Removing PROCESS_TERMINATE flag causes hang

> To make a process truly unkillable, it’s not enough to deny
TERMINATE_PROCESS right.

Yep, I’m well aware of that. I’ve currently listed 12 very different
‘attack’ methods, of which I’m sure there are more for anyone more
imaginative than I am.

> I’m afraid “the problem you’re trying to solve” is you’re trying to
> find
out how to implement a wrong solution for the feature the customers want.

To be fair, you don’t know what problem I’m trying to solve, and I wasn’t
asking for any suggestions on solving anything

The solution I listed previously is a commonly accepted solution to what is
a slightly controversial topic. There’s quite a few topics on ntdev pointing
to the use of ObRegisterCallbacks to modify handles. For example this
lengthy post:
https://www.osronline.com/showThread.CFM?link=261303

Microsoft themselves also provide sample code showing this exact solution to
restrict PROCESS_TERMINATE access on a particular target process
https://github.com/Microsoft/Windows-driver-samples/tree/master/general/obca
llback/driver
If you run the sample using a target process that requires elevation, you’ll
see that it exhibits this exact problem and causes a hang in the parent
thread and a zombie process.

I’m just documenting the issues I found when researching, in the hope it
might help someone else in the future. It’s surely worth pointing out that
following Microsoft’s lead on this topic will cause problems. The WDK sample
code should probably be removed, or CreateProcessInternalW should be ‘fixed’
to avoid the hangs.

Ged.


NTDEV is sponsored by OSR

Visit the list online at:
http:

MONTHLY seminars on crash dump analysis, WDF, Windows internals and software
drivers!
Details at http:

To unsubscribe, visit the List Server section of OSR Online at
http:


NTDEV is sponsored by OSR

Visit the list online at:
http:

MONTHLY seminars on crash dump analysis, WDF, Windows internals and software
drivers!
Details at http:

To unsubscribe, visit the List Server section of OSR Online at
http:</http:></http:></http:></http:></http:></http:>

>> This also prevents DLL injection and hook installation

Yep, amongst other things. Interestingly, you can also prevent these things
by stripping rights.

That’s what DACLs were designed to do. What you’re trying to do is a kludge.

My guess is that TerminateProcess calls ZwTerminateProcess. The process handle is used to obtain a referenced pointer to the process object with ObReferenceObjectByHandle. But this latter call should fail with STATUS_ACCESS_DENIED if you removed the TERMINATE_PROCESS access right. So, TerminateProcess should return quickly with an error like ERROR_ACCESS_DENIED.

>>> This also prevents DLL injection and hook installation

> Yep, amongst other things. Interestingly, you can also prevent these
things
> by stripping rights.

That’s what DACLs were designed to do. What you’re trying to do is a
kludge.

I’m not trying to do it.
I give up…

Yeah, that’s exactly what happens.

I suppose the obvious solution is to update the WDK sample to exclude
parents from having terminate rights removed to their children (it currently
does for handles to its own process).
The sample already monitors process creates / deletes, so it’s not much
extra work to keep a list of parent -> child relationships and update child
entries if the parent processes closes (making the pid invalid).

I’ve just tried this locally with the WDK sample and it works well.

Ged.

-----Original Message-----
From: xxxxx@lists.osr.com
[mailto:xxxxx@lists.osr.com] On Behalf Of
xxxxx@gmail.com
Sent: 10 October 2016 17:52
To: Windows System Software Devs Interest List
Subject: RE:[ntdev] Removing PROCESS_TERMINATE flag causes hang

My guess is that TerminateProcess calls ZwTerminateProcess. The process
handle is used to obtain a referenced pointer to the process object with
ObReferenceObjectByHandle. But this latter call should fail with
STATUS_ACCESS_DENIED if you removed the TERMINATE_PROCESS access right. So,
TerminateProcess should return quickly with an error like
ERROR_ACCESS_DENIED.


NTDEV is sponsored by OSR

Visit the list online at:
http:

MONTHLY seminars on crash dump analysis, WDF, Windows internals and software
drivers!
Details at http:

To unsubscribe, visit the List Server section of OSR Online at
http:</http:></http:></http:>

1 Like

Don’t be confused. CreateProcessInternalW runs in the context of the creator that may not be the parent process.

I think you should use the protected process feature of the OS.

> That’s what DACLs were designed to do.

I can easily foresee your usual “don’t let users run as admins” mantra, but still,
assuming that we are speaking about the admin account, can’t other processes undo your
“unbreakable protection” of a target process with SetEntriesInAcl() and SetSecurityInfo() calls?

Anton Bassov

>can’t other processes undo your “unbreakable protection” of a target process with SetEntriesInAcl() and SetSecurityInfo() calls?

Only if DACLs let them do it. If it’s an admin account, the IT doesn’t know what they’re doing.

> If it’s an admin account, the IT doesn’t know what they’re doing.

As I can see, my statement about your usual “mantra” turned into some kind of a “self-fulfilling
prophecy”. In fact, it was obvious to me from the very beginning that it was about to happen anyway.

In either case, this whole discussion may make sense only if we assume the admin account. Otherwise, all that one has to do here is to run the target process under the admin account, and the whole problem is going to get automatically reduced to a complete non-existence, at least as far as termination by a “regular” user is concerned…

Anton Bassov