public inbox for cygwin@cygwin.com
 help / color / mirror / Atom feed
From: "Jason Pyeron" <jpyeron@pdinc.us>
To: <cygwin@cygwin.com>
Subject: RE: [cygwin] Re: Mandatory ASLR breaks Cygwin - Windows 10
Date: Tue, 25 Aug 2020 11:25:16 -0400	[thread overview]
Message-ID: <1b2501d67af3$efcd9e80$cf68db80$@pdinc.us> (raw)
In-Reply-To: <cd134b34-3b17-3082-4da8-879583667070@SystematicSw.ab.ca>

> -----Original Message-----
> From: Brian Inglis
> Sent: Tuesday, August 25, 2020 11:13 AM
> 
> On 2020-08-25 08:36, Alexandria Cortez wrote:
> > On Tuesday, August 25, 2020 10:35 AM, Eliot Moss wrote:>> On Aug 25, 2020, at 10:17 AM, Alexandria
> Cortez wrote:
> >>> I was experimenting with security settings this morning on windows, and
> >>> after changing Mandatory ASLR (Windows Security -> App and Browser Control
> >>> -> Exploit Protection) to default on, no Cygwin programs that rely on the
> >>> Cygwin dll would start, stating that a resource was temporarily unavailable
> >>> and could not fork. Rebasell, bash, you name it crashed and would not start.
> >>> After some investigation, turning off that setting allows Cygwin to work.
> >>>
> >>> Now the next question: why does this not work? Is this intended behavior or
> >>> a bug? Having that setting turned on seems like a good idea from a security
> >>> standpoint, and who knows it  may eventually become default.
> 
> >> It’s intentional; too long to explain in detail on phone, but fork
> >> requires each dll to load in the child at the same address as in the
> >> parent, and ASLR interferes with achieving that.
> > Is there any plans to implement a workaround in the future? Seeing as Cygwin
> > is only one of two programs I've noticed that are broken with it on, it
> > would be nice to be able to have it on from a security perspective.
> Cygwin is an all-volunteer project - Someone Has To Do It!
> Feel free to submit patches to support that in Cygwin under Windows.
> A low level understanding of details of both is required.

Some relevant reading 

https://stackoverflow.com/questions/8171298/is-address-space-layout-randomization-applied-on-a-forked-process

Is Address Space Layout Randomization applied on a forked process

Q: Say I fork a process from another process. Will Address Space Layout Randomization (ASLR) be applied on it in an OS which has ASLR set?

Note that I am talking about the case where I don't call execve function after doing fork.

A: 12

Yes.

However note that after fork both parent and child have the same randomization applied to them (they are copies of each other after all!).

If the child and parent are to call mmap(NULL, ...), then their address maps will start to diverge.

Update:

> Isn't your statement contradictory?

Not at all. Immediately after fork, the parent and child address spaces are identical (that's the definition of what fork does). But the ASLR is still in effect for both the parent and the child. The randomization can't "go back in time" and randomize decisions that have already been made, but any future decisions (such as where to place next mmap) will be randomized, and will likely result in different outcome for parent and child.

> Does it have to do with basic mmap or OS writers introduce randomness in mmap as well for security?

Perhaps you don't understand what ASLR is?

In short, with ASLR on, the OS will randomize placement of main stack, and placement of any non-MAP_FIXED mmaps.

By the time you fork, the main stack placement has long been determined, so parent and child will have the same. The future mmap are the only things that can (and will be) affected by ASLR going forward.

https://cygwin.com/cygwin-ug-net/highlights.html

Process Creation
The fork call in Cygwin is particularly interesting because it does not map well on top of the Win32 API. This makes it very difficult to implement correctly. Currently, the Cygwin fork is a non-copy-on-write implementation similar to what was present in early flavors of UNIX.

As the child process is created as new process, both the main executable and all the dlls loaded either statically or dynamically have to be identical as to when the parent process has started or loaded a dll. While Windows does not allow to remove binaries in use from the file system, they still can be renamed or moved into the recycle bin, as outlined for unlink(2) in the section called “File Access related changes”. To allow an existing process to fork, the original binary files need to be available via their original file names, but they may reside in a different directory when using the DotLocal (.local) Dll Redirection feature. Since NTFS does support hardlinks, when the fork fails we try again, but create a private directory containing hardlinks to the original files as well as the .local file now. The base directory for the private hardlink directory is /var/run/cygfork/, which you have to create manually for now if you need to protect fork against updates to executables and dlls on your Cygwin instance. As hardlinks cannot be used across multiple NTFS file systems, please make sure your executable and dll replacing operations operate on the same single NTFS file system as your Cygwin instance and the /var/run/cygfork/ directory. Note that this private hardlink directory also does help for when a wrong dll is found in the parent process' current working directory. To enable creating the hardlinks, you need to stop all currently running Cygwin processes after creating this directory, once per Cygwin installation:

$ mkdir --mode=a=rwxt /var/run/cygfork

We create one hardlink directory per user, application and application age, and remove it when no more processes use that directory. To indicate whether a directory still is in use, we define a mutex name similar to the directory name. As mutexes are destroyed when no process holds a handle open any more, we can clean up even after power loss or similar: Both the parent and child process, at exit they lock the mutex with almost no timeout and close it, to get the closure promoted synchronously. If the lock succeeded before closing, directory cleanup is started: For each directory found, the corresponding mutex is created with lock. If that succeeds, the directory is removed, as it is unused now, and the corresponding mutex handle is closed.

Before fork, when about to create hardlinks for the first time, the mutex is opened and locked with infinite timeout, to wait for the cleanup that may run at the same time. Once locked, the mutex is unlocked immediately, but the mutex handle stays open until exit, and the hardlinks are created. It is fine for multiple processes to concurrently create the same hardlinks, as the result really should be identical. Once the mutex is open, we can create more hardlinks within this one directory without the need to lock the mutex again.

The first thing that happens when a parent process forks a child process is that the parent initializes a space in the Cygwin process table for the child. It then creates a suspended child process using the Win32 CreateProcess call. Next, the parent process calls setjmp to save its own context and sets a pointer to this in a Cygwin shared memory area (shared among all Cygwin tasks). It then fills in the child's .data and .bss sections by copying from its own address space into the suspended child's address space. After the child's address space is initialized, the child is run while the parent waits on a mutex. The child discovers it has been forked and longjumps using the saved jump buffer. The child then sets the mutex the parent is waiting on and blocks on another mutex. This is the signal for the parent to copy its stack and heap into the child, after which it releases the mutex the child is waiting on and returns from the fork call. Finally, the child wakes from blocking on the last mutex, recreates any memory-mapped areas passed to it via the shared area, and returns from fork itself.

While we have some ideas as to how to speed up our fork implementation by reducing the number of context switches between the parent and child process, fork will almost certainly always be inefficient under Win32. Fortunately, in most circumstances the spawn family of calls provided by Cygwin can be substituted for a fork/exec pair with only a little effort. These calls map cleanly on top of the Win32 API. As a result, they are much more efficient. Changing the compiler's driver program to call spawn instead of fork was a trivial change and increased compilation speeds by twenty to thirty percent in our tests.

However, spawn and exec present their own set of difficulties. Because there is no way to do an actual exec under Win32, Cygwin has to invent its own Process IDs (PIDs). As a result, when a process performs multiple exec calls, there will be multiple Windows PIDs associated with a single Cygwin PID. In some cases, stubs of each of these Win32 processes may linger, waiting for their exec'd Cygwin process to exit.

Problems with process creation
The semantics of fork require that a forked child process have exactly the same address space layout as its parent. However, Windows provides no native support for cloning address space between processes and several features actively undermine a reliable fork implementation. Three issues are especially prevalent:

DLL base address collisions. Unlike *nix shared libraries, which use "position-independent code", Windows shared libraries assume a fixed base address. Whenever the hard-wired address ranges of two DLLs collide (which occurs quite often), the Windows loader must "rebase" one of them to a different address. However, it may not resolve collisions consistently, and may rebase a different dll and/or move it to a different address every time. Cygwin can usually compensate for this effect when it involves libraries opened dynamically, but collisions among statically-linked dlls (dependencies known at compile time) are resolved before cygwin1.dll initializes and cannot be fixed afterward. This problem can only be solved by removing the base address conflicts which cause the problem, usually using the rebaseall tool.

Address space layout randomization (ASLR). Starting with Vista, Windows implements ASLR, which means that thread stacks, heap, memory-mapped files, and statically-linked dlls are placed at different (random) locations in each process. This behaviour interferes with a proper fork, and if an unmovable object (process heap or system dll) ends up at the wrong location, Cygwin can do nothing to compensate (though it will retry a few times automatically).

DLL injection by BLODA. Badly-behaved applications which inject dlls into other processes often manage to clobber important sections of the child's address space, leading to base address collisions which rebasing cannot fix. The only way to resolve this problem is to remove (usually uninstall) the offending app.

In summary, current Windows implementations make it impossible to implement a perfectly reliable fork, and occasional fork failures are inevitable.



  reply	other threads:[~2020-08-25 15:25 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-08-25 14:16 Alexandria Cortez
2020-08-25 14:34 ` Eliot Moss
2020-08-25 14:36   ` Alexandria Cortez
2020-08-25 15:13     ` Brian Inglis
2020-08-25 15:25       ` Jason Pyeron [this message]
2020-08-25 15:56         ` [cygwin] " Eliot Moss
2020-08-25 21:49           ` Brian Inglis

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to='1b2501d67af3$efcd9e80$cf68db80$@pdinc.us' \
    --to=jpyeron@pdinc.us \
    --cc=cygwin@cygwin.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).