>From 1786472e611f53cf5d9b61df0102480263efa06b Mon Sep 17 00:00:00 2001
From: Michael Haubenwallner <michael.haubenwallner@ssi-schaefer.com>
Date: Tue, 21 Jul 2015 16:15:05 +0200
Subject: [PATCH] Protect fork() against dll- and exe-updates.

To support in-cygwin package managers, fork() implementation must not
use .exe and .dll files from the original directories, as the package
managers' job is to update these files. Instead, keep a hardlink to the
originally binaries in /var/run/wproc/<sid>/<ntpid>/ and use them during
fork.

Creating an app.exe.local file also redirects LoadLibrary calls with
absolute file names to this directory, using the loaded dll's basename.

The hardlink for dynamically loaded dlls is created inside one single
directory, which's name is mangled from the dll's full path, while the
dll's base filename is preserved to probably serve as linked dependency
of another dynamically loaded dll.

As only a few users probably need an update-safe fork, the hardlink
creation is enabled only when /var/run/wproc/<sid>/<ntpid>/ can be
created.
That is, when /var/run/wproc/ does not exist, nothing happens - users
have to create that directory manually for now.

	* dll_init.h (enum dll_type): Define DLL_SELF.
	(struct dll): Declare member procfs_name. Declare methods
	delete_procfs_hardlink_only, delete_procfs_hardlink,
	create_procfs_hardlink, nominate_procfs_hardlink. Implement
	method forkedname, returning procfs_name when available, or
	original name otherwise.
	(struct dll_list): Declare members proc_ntpid_object__name,
	proc_ntpid_object__size, procfs_creation_ntpid, main_executable.
	Declare methods nominate_proc_ntpid_object_dir,
	create_proc_ntpid_object_dir, delete_proc_ntpid_object_dir,
	cleanup_procfs, find_by_forkedname.
	Declare static method fixup_path.
	* dll_init.cc (in_load_after_fork): Define earlier in file.
	(nt_max_path_buf): New, local to file.
	(nt_max_path_buf_dll): New, local to file.
	(struct dll): Implement delete_procfs_hardlink_only,
	delete_procfs_hardlink, create_procfs_hardlink,
	nominate_procfs_hardlink.
	(struct dll_list): Implement find_by_forkedname, fixup_path,
	nominate_proc_ntpid_object_dir, create_proc_ntpid_object_dir,
	delete_proc_ntpid_object_dir, cleanup_procfs,
	(dll_list::alloc): Move path-fixups into fixup_path. Use
	nt_max_path_buf. On first call, calculate
	proc_ntpid_object__size, allocate proc_ntpid_object__name.
	Search for registered dll using find_by_forkedname when
	in_load_after_fork. For new dll entry, reserve space for
	dll::procfs_name and call dll::nominate_procfs_hardlink.
	Do not indicate has_dtors for DLL_SELF.
	(dll_list::init): Alloc cygwin_hmodule and main_executable as
	DLL_SELF, create proc_ntpid_object_dir and procfs_hardlinks.
	(dll_list::load_after_fork): Do reload dll's parent handle only.
	Re-nominate and create proc_ntpid_object_dir, procfs_hardlinks.
	(dll_list::load_after_fork_impl): Load dlls using
	dll::forkedname.
	(dll_dllcrt0_1): Split up result-calculation, to execute
	dll::create_procfs_hardlink for the dynamically loaded dll
	before dll::init.
	* fork.cc (frok::child): Without dynamically loaded dll's, run
	dll_list::load_after_fork with null-parent handle, to get the
	procfs_hardlinks created.
	(frok::parent): Use dll_list::main_executable's forkedname for
	new CreateProcess.
	* pinfo.cc (pinfo::exit): Run dll_info::cleanup_procfs.
	* syscalls.cc (_unlink_nt): New, static, renamed from unlink_nt,
	with shareable as additional argument.
	(unlink_nt): New, wrap _unlink_nt for original behaviour.
	(unlink_nt_shareable): New, wrap _unlink_nt to keep hardlinked
	files still useable as binaries while removing their hardlinks.
---
 winsup/cygwin/dll_init.cc | 489 ++++++++++++++++++++++++++++++++++++++++++----
 winsup/cygwin/dll_init.h  |  20 ++
 winsup/cygwin/fork.cc     |  10 +-
 winsup/cygwin/pinfo.cc    |   3 +
 winsup/cygwin/syscalls.cc |  24 ++-
 5 files changed, 499 insertions(+), 47 deletions(-)

diff --git a/winsup/cygwin/dll_init.cc b/winsup/cygwin/dll_init.cc
index 8deceb9..a873751 100644
--- a/winsup/cygwin/dll_init.cc
+++ b/winsup/cygwin/dll_init.cc
@@ -22,6 +22,7 @@ details. */
 #include "child_info.h"
 #include "cygtls.h"
 #include "exception.h"
+#include "ntdll.h"
 #include <wchar.h>
 #include <sys/reent.h>
 #include <assert.h>
@@ -29,6 +30,12 @@ details. */
 
 extern void __stdcall check_sanity_and_sync (per_process *);
 
+extern NTSTATUS unlink_nt (path_conv &pc);
+
+/* Allow concurrent processes to use the same dll or exe
+ * via their hardlink while we delete our hardlink. */
+extern NTSTATUS unlink_nt_shareable (path_conv &pc);
+
 #define fabort fork_info->abort
 
 dll_list dlls;
@@ -37,6 +44,16 @@ muto dll_list::protect;
 
 static bool dll_global_dtors_recorded;
 
+/* We need the in_load_after_fork flag so dll_dllcrt0_1 can decide at fork
+   time if this is a linked DLL or a dynamically loaded DLL.  In either case,
+   both, cygwin_finished_initializing and in_forkee are true, so they are not
+   sufficient to discern the situation. */
+static bool NO_COPY in_load_after_fork;
+
+/* Use this buffer under loader lock conditions only. */
+static WCHAR nt_max_path_buf[NT_MAX_PATH];
+static WCHAR nt_max_path_buf_dll[NT_MAX_PATH];
+
 /* Run destructors for all DLLs on exit. */
 void
 dll_global_dtors ()
@@ -81,6 +98,162 @@ per_module::run_dtors ()
     (*pfunc) ();
 }
 
+void
+dll::delete_procfs_hardlink_only ()
+{
+  if (!*procfs_name)
+    return; /* disabled */
+
+  UNICODE_STRING fn;
+  RtlInitUnicodeString (&fn, procfs_name);
+
+  path_conv pc (&fn);
+  NTSTATUS ret = unlink_nt_shareable (pc);
+  debug_printf ("%y = unlink_nt_shareable (%W)", ret, procfs_name);
+}
+
+void
+dll::delete_procfs_hardlink ()
+{
+  if (!*procfs_name)
+    return; /* disabled */
+
+  delete_procfs_hardlink_only ();
+
+  if (type < DLL_LOAD)
+    return;
+
+  /* loaded dlls are placed inside a mangled subdirectory */
+
+  wcsncpy (nt_max_path_buf_dll, procfs_name, NT_MAX_PATH);
+
+  PWCHAR last = wcsrchr (nt_max_path_buf_dll, L'\\');
+  if (!last)
+    return;
+
+  *last = L'\0';
+
+  UNICODE_STRING dn;
+  RtlInitUnicodeString (&dn, nt_max_path_buf_dll);
+
+  path_conv pc (&dn);
+  NTSTATUS ret = unlink_nt (pc);
+  debug_printf ("%y = unlink_nt (%W)", ret, nt_max_path_buf_dll);
+}
+
+/* create hardlink to an individual DLL into /proc/<pid>/object/ */
+void
+dll::create_procfs_hardlink (PCWCHAR origin)
+{
+  if (!*procfs_name)
+    return; /* disabled */
+
+  if (!origin || !*origin)
+    origin = name;
+
+  wcsncpy (nt_max_path_buf_dll, procfs_name, NT_MAX_PATH);
+
+  PWCHAR last;
+  if (type >= DLL_LOAD && (last = wcsrchr (nt_max_path_buf_dll, L'\\')))
+    {
+      *last = L'\0';
+      BOOL ret = CreateDirectoryW (nt_max_path_buf_dll, &sec_none_nih);
+      if (!ret && GetLastError () != ERROR_ALREADY_EXISTS)
+        {
+	  debug_printf ("%d = CreateDirectoryW (%W)%s %E",
+	    ret, nt_max_path_buf_dll,
+	    in_load_after_fork ? " in load after fork" : "");
+	  return;
+	}
+      debug_printf ("%d = CreateDirectoryW (%W)%s",
+	ret, nt_max_path_buf_dll,
+	in_load_after_fork ? " in load after fork" : "");
+      *last = L'\\';
+    }
+  int ret = CreateHardLinkW (procfs_name, origin, NULL);
+  if (ret)
+    {
+      debug_printf ("%d = CreateHardLinkW (%W -> %W)%s",
+	  ret, procfs_name, origin,
+	  in_load_after_fork ? " in load after fork" : "");
+      return;
+    }
+  debug_printf ("%d = CreateHardLinkW (%W -> %W)%s %E",
+      ret, procfs_name, origin,
+      in_load_after_fork ? " in load after fork" : "");
+  if (GetLastError () == ERROR_ALREADY_EXISTS)
+    {
+      delete_procfs_hardlink_only (); /* an orphaned one */
+      ret = CreateHardLinkW (procfs_name, origin, NULL);
+      if (ret)
+	debug_printf ("%d = CreateHardLinkW (%W -> %W)%s",
+	    ret, procfs_name, origin,
+	    in_load_after_fork ? " in load after fork" : "");
+      else
+	debug_printf ("%d = CreateHardLinkW (%W -> %W)%s %E",
+	    ret, procfs_name, origin,
+	    in_load_after_fork ? " in load after fork" : "");
+    }
+  if (!ret)
+    *procfs_name = L'\0'; /* disabling */
+}
+
+void
+dll::nominate_procfs_hardlink (PCWCHAR objdir_)
+{
+  if (!objdir_ || !*objdir_)
+    {
+      /* disabling */
+      procfs_name[0] = L'\0';
+      return;
+    }
+
+  wcscpy (procfs_name, objdir_);
+
+  PCWCHAR src;
+  PWCHAR tgt, last;
+  switch (type)
+    {
+      case DLL_SELF:
+      case DLL_LINK:
+	wcscat (procfs_name, modname);
+	break;
+      case DLL_LOAD:
+	/* Avoid lots of extra directories for loaded dll's:
+	 * mangle full path into one single directory name,
+	 * but keep original filename intact. The original
+	 * filename is necessary to serve as linked
+	 * dependencies of dynamically loaded dlls. */
+	src = name;
+	tgt = procfs_name + wcslen (procfs_name);
+	last = NULL;
+	while (*src)
+	  {
+	    switch (*src)
+	      {
+		case L'\\':
+		  last = tgt;
+		  *(tgt++) = L',';
+		  break;
+		case L'?':
+		case L':':
+		  *(tgt++) = L'_';
+		  break;
+		default:
+		  *(tgt++) = *src;
+		  break;
+	      }
+	    src++;
+	  }
+	if (last)
+	  *last = L'\\';
+	*tgt = L'\0';
+	break;
+      default:
+	break;
+    }
+}
+
 /* Initialize an individual DLL */
 int
 dll::init ()
@@ -173,17 +346,26 @@ dll_list::find_by_modname (const PWCHAR modname)
   return NULL;
 }
 
-#define RETRIES 1000
-
-/* Allocate space for a dll struct. */
+/* Look for a dll based on the name used
+ * to dynamically reload in forked child. */
 dll *
-dll_list::alloc (HINSTANCE h, per_process *p, dll_type type)
+dll_list::find_by_forkedname (PCWCHAR name)
+{
+  dll *d = &start;
+  while ((d = d->next) != NULL)
+    if (!wcscasecmp (name, d->forkedname ()))
+      return d;
+
+  return NULL;
+}
+
+/* Remove some path specification starters.
+ * As the result never is longer than the input,
+ * returns the pointer into the argument buffer
+ * behind these starters. */
+PWCHAR
+dll_list::fixup_path (PWCHAR name)
 {
-  /* Called under loader lock conditions so this function can't be called
-     multiple times in parallel.  A static buffer is safe. */
-  static WCHAR buf[NT_MAX_PATH];
-  GetModuleFileNameW (h, buf, NT_MAX_PATH);
-  PWCHAR name = buf;
   if (!wcsncmp (name, L"\\\\?\\", 4))
     {
       name += 4;
@@ -193,14 +375,42 @@ dll_list::alloc (HINSTANCE h, per_process *p, dll_type type)
 	  *name = L'\\';
 	}
     }
+  return name;
+}
+
+#define RETRIES 1000
+
+/* Allocate space for a dll struct. */
+dll *
+dll_list::alloc (HINSTANCE h, per_process *p, dll_type type)
+{
+  /* Called under loader lock conditions so this function can't be called
+     multiple times in parallel.  The static buffer is safe. */
+  GetModuleFileNameW (h, nt_max_path_buf, NT_MAX_PATH);
+  PWCHAR name = fixup_path (nt_max_path_buf);
   DWORD namelen = wcslen (name);
   PWCHAR modname = wcsrchr (name, L'\\') + 1;
 
   guard (true);
+
+  if (!proc_ntpid_object__size)
+    {
+      proc_ntpid_object__size = wcslen (cygheap->installation_root);
+      /* max string length is "/var/run/wproc/<sid128>/<maxpid>/",
+       * sync format with nominate_proc_ntpid_object_dir () */
+      proc_ntpid_object__size += sizeof ("\\var\\run\\wproc\\""\\2147483647\\") + 128;
+      proc_ntpid_object__name = (PWCHAR) cmalloc (HEAP_2_DLL, proc_ntpid_object__size * sizeof (*proc_ntpid_object__name));
+      nominate_proc_ntpid_object_dir ();
+    }
+
   /* Already loaded?  For linked DLLs, only compare the basenames.  Linked
      DLLs are loaded using just the basename and the default DLL search path.
-     The Windows loader picks up the first one it finds.  */
-  dll *d = (type == DLL_LINK) ? dlls.find_by_modname (modname) : dlls[name];
+     The Windows loader picks up the first one it finds.
+     This also applies to cygwin1.dll and the main executable (DLL_SELF).
+     When in_load_after_fork, dynamically loaded dll's are reloaded
+     using their parent's procfs_name, if available. */
+  dll *d = (type <= DLL_LINK) ? dlls.find_by_modname (modname) :
+	   in_load_after_fork ? dlls.find_by_forkedname (name) : dlls[name];
   if (d)
     {
       if (!in_forkee)
@@ -228,7 +438,7 @@ dll_list::alloc (HINSTANCE h, per_process *p, dll_type type)
   else
     {
       /* FIXME: Change this to new at some point. */
-      d = (dll *) cmalloc (HEAP_2_DLL, sizeof (*d) + (namelen * sizeof (*name)));
+      d = (dll *) cmalloc (HEAP_2_DLL, sizeof (*d) + ((namelen + proc_ntpid_object__size + namelen) * sizeof (*name)));
 
       /* Now we've allocated a block of information.  Fill it in with the
 	 supplied info about this DLL. */
@@ -236,13 +446,15 @@ dll_list::alloc (HINSTANCE h, per_process *p, dll_type type)
       wcscpy (d->name, name);
       d->modname = d->name + (modname - name);
       d->handle = h;
-      d->has_dtors = true;
+      d->has_dtors = type > DLL_SELF;
       d->p = p;
       d->ndeps = 0;
       d->deps = NULL;
       d->image_size = ((pefile*)h)->optional_hdr ()->SizeOfImage;
       d->preferred_base = (void*) ((pefile*)h)->optional_hdr()->ImageBase;
       d->type = type;
+      d->procfs_name = d->name + namelen + 1;
+      d->nominate_procfs_hardlink (proc_ntpid_object__name);
       append (d);
       if (type == DLL_LOAD)
 	loaded_dlls++;
@@ -396,7 +608,7 @@ dll_list::detach (void *retaddr)
   if (!myself || in_forkee)
     return;
   guard (true);
-  if ((d = find (retaddr)))
+  if ((d = find (retaddr)) && d->type != DLL_SELF)
     {
       if (d->count <= 0)
 	system_printf ("WARNING: trying to detach an already detached dll ...");
@@ -408,6 +620,7 @@ dll_list::detach (void *retaddr)
 	  if (!exit_state)
 	    __cxa_finalize (d->handle);
 	  d->run_dtors ();
+	  d->delete_procfs_hardlink ();
 	  d->prev->next = d->next;
 	  if (d->next)
 	    d->next->prev = d->prev;
@@ -425,11 +638,30 @@ dll_list::detach (void *retaddr)
 void
 dll_list::init ()
 {
+  /* for cygwin1.dll and main executable: maintain hardlinks only */
+  alloc (cygwin_hmodule, user_data, DLL_SELF);
+  main_executable = alloc (GetModuleHandle (NULL), user_data, DLL_SELF);
+
+  bool created = create_proc_ntpid_object_dir ();
+  if (!created)
+    {
+      /* nt_max_path_buf contains last directory tried to create */
+      debug_printf ("%W not created, fork is not protected against dll-updates",
+		    nt_max_path_buf);
+      *proc_ntpid_object__name = L'\0';
+    }
+
   /* Walk the dll chain, initializing each dll */
   dll *d = &start;
   dll_global_dtors_recorded = d->next != NULL;
   while ((d = d->next))
-    d->init ();
+    {
+      if (!created) /* de-nominate */
+	d->nominate_procfs_hardlink (proc_ntpid_object__name);
+      d->create_procfs_hardlink ();
+      if (d->type > DLL_SELF) /* linked and early loaded dlls */
+	d->init ();
+    }
 }
 
 #define A64K (64 * 1024)
@@ -494,12 +726,6 @@ dll_list::reserve_space ()
 	      d->modname, d->handle);
 }
 
-/* We need the in_load_after_fork flag so dll_dllcrt0_1 can decide at fork
-   time if this is a linked DLL or a dynamically loaded DLL.  In either case,
-   both, cygwin_finished_initializing and in_forkee are true, so they are not
-   sufficient to discern the situation. */
-static bool NO_COPY in_load_after_fork;
-
 /* Reload DLLs after a fork.  Iterates over the list of dynamically loaded
    DLLs and attempts to load them in the same place as they were loaded in the
    parent. */
@@ -509,9 +735,27 @@ dll_list::load_after_fork (HANDLE parent)
   // moved to frok::child for performance reasons:
   // dll_list::reserve_space();
 
-  in_load_after_fork = true;
-  load_after_fork_impl (parent, dlls.istart (DLL_LOAD), 0);
-  in_load_after_fork = false;
+  if (parent)
+    {
+      in_load_after_fork = true;
+      load_after_fork_impl (parent, dlls.istart (DLL_LOAD), 0);
+      in_load_after_fork = false;
+    }
+
+  nominate_proc_ntpid_object_dir ();
+  if (!create_proc_ntpid_object_dir ())
+    *proc_ntpid_object__name = L'\0';
+
+  dll *d = &start;
+  while ((d = d->next))
+    {
+      /* Called under loader lock conditions so this function can't be called
+	 multiple times in parallel.  The static buffer is safe. */
+      wcsncpy (nt_max_path_buf, d->forkedname (), NT_MAX_PATH);
+      d->nominate_procfs_hardlink (proc_ntpid_object__name);
+      /* hardlink to parent's procfs_name rather than original name */
+      d->create_procfs_hardlink (nt_max_path_buf);
+    }
 }
 
 static int const DLL_RETRY_MAX = 6;
@@ -544,18 +788,19 @@ void dll_list::load_after_fork_impl (HANDLE parent, dll* d, int retries)
 	   dll's protective reservation from step 1
 	 */
 	if (!retries && !VirtualFree (d->handle, 0, MEM_RELEASE))
-	  fabort ("unable to release protective reservation for %W (%p), %E",
-		  d->modname, d->handle);
+	  fabort ("unable to release protective reservation (%p) for %W (using %W), %E",
+		  d->handle, d->name, d->forkedname ());
 
-	HMODULE h = LoadLibraryExW (d->name, NULL, DONT_RESOLVE_DLL_REFERENCES);
+	HMODULE h = LoadLibraryExW (d->forkedname (), NULL, DONT_RESOLVE_DLL_REFERENCES);
 	if (!h)
-	  fabort ("unable to create interim mapping for %W, %E", d->name);
+	  fabort ("unable to create interim mapping for %W (using %W), %E",
+		  d->name, d->forkedname ());
 	if (h != d->handle)
 	  {
-	    sigproc_printf ("%W loaded in wrong place: %p != %p",
-			    d->modname, h, d->handle);
+	    sigproc_printf ("%W (using %W) loaded in wrong place: %p != %p",
+			    d->name, d->forkedname (), h, d->handle);
 	    FreeLibrary (h);
-	    PVOID reservation = reserve_at (d->modname, h,
+	    PVOID reservation = reserve_at (d->forkedname (), h,
 					    d->handle, d->image_size);
 	    if (!reservation)
 	      fabort ("unable to block off %p to prevent %W from loading there",
@@ -565,12 +810,12 @@ void dll_list::load_after_fork_impl (HANDLE parent, dll* d, int retries)
 	      load_after_fork_impl (parent, d, retries+1);
 	    else
 	       fabort ("unable to remap %W to same address as parent (%p) - try running rebaseall",
-		       d->modname, d->handle);
+		       d->forkedname (), d->handle);
 
 	    /* once the above returns all the dlls are mapped; release
 	       the reservation and continue unwinding */
 	    sigproc_printf ("releasing blocked space at %p", reservation);
-	    release_at (d->modname, reservation);
+	    release_at (d->forkedname (), reservation);
 	    return;
 	  }
       }
@@ -586,23 +831,175 @@ void dll_list::load_after_fork_impl (HANDLE parent, dll* d, int retries)
 	{
 	  if (!VirtualFree (d->handle, 0, MEM_RELEASE))
 	    fabort ("unable to release protective reservation for %W (%p), %E",
-		    d->modname, d->handle);
+		    d->forkedname (), d->handle);
 	}
       else
 	{
 	  /* Free the library using our parent's handle: it's identical
 	     to ours or we wouldn't have gotten this far */
 	  if (!FreeLibrary (d->handle))
-	    fabort ("unable to unload interim mapping of %W, %E",
-		    d->modname);
+	    fabort ("unable to unload interim mapping of %W (using %W), %E",
+		    d->name, d->forkedname ());
 	}
-      HMODULE h = LoadLibraryW (d->name);
+      HMODULE h = LoadLibraryW (d->forkedname ());
       if (!h)
-	fabort ("unable to map %W, %E", d->name);
+	fabort ("unable to map %W (using %W), %E", d->name, d->forkedname ());
       if (h != d->handle)
-	fabort ("unable to map %W to same address as parent: %p != %p",
-		d->modname, d->handle, h);
+	fabort ("unable to map %W (using %W) to same address as parent: %p != %p",
+		d->name, d->forkedname (), d->handle, h);
+    }
+}
+
+void
+dll_list::nominate_proc_ntpid_object_dir ()
+{
+  if (!proc_ntpid_object__name)
+    return;
+
+  UNICODE_STRING sid;
+  WCHAR sidbuf[128];
+  RtlInitEmptyUnicodeString (&sid, sidbuf, sizeof sidbuf);
+  RtlConvertSidToUnicodeString (&sid, cygheap->user.sid (), FALSE);
+
+  /* sync format with max string length in alloc () */
+  __small_swprintf (proc_ntpid_object__name, L"%W\\var\\run\\wproc\\%W\\%d\\",
+      cygheap->installation_root, sid.Buffer, GetCurrentProcessId ());
+
+  /* FIXME: WTF, cygheap->installation_root starts with "\??\", not "\\?\" */
+  if (!wcsncmp (proc_ntpid_object__name, L"\\??\\", 4))
+    proc_ntpid_object__name[1] = L'\\';
+
+  PWCHAR fixed = fixup_path (proc_ntpid_object__name);
+  if (fixed != proc_ntpid_object__name)
+    memmove (proc_ntpid_object__name, fixed, (wcslen (fixed) + 1) * sizeof (*fixed));
+}
+
+bool
+dll_list::create_proc_ntpid_object_dir ()
+{
+  bool valid = true;
+
+  if (!proc_ntpid_object__name || !*proc_ntpid_object__name)
+    return false; /* disabled */
+
+  /* Called under loader lock conditions so this function can't be called
+     multiple times in parallel.  The static buffer is safe. */
+  wcsncpy (nt_max_path_buf, proc_ntpid_object__name, NT_MAX_PATH);
+
+#define ABSENT_PROC_DIR_DISABLES_HARDLINKS
+#ifdef ABSENT_PROC_DIR_DISABLES_HARDLINKS
+  /* do not create "<cygroot>\\proc" if missing, disable hardlinks instead */
+  PWCHAR separators[2]; /* separators in "<ntpid>\\objects\\" */
+#else
+  /* do create "<cygroot>\\proc" if missing */
+  PWCHAR separators[3]; /* separators in "proc\\<ntpid>\\objects\\" */
+#endif
+
+  int i = sizeof (separators) / sizeof (separators[0]);
+  while (i--)
+    {
+      separators[i] = wcsrchr (nt_max_path_buf, L'\\');
+      if (!separators[i])
+	{
+	  system_printf ("missing 'proc\\<ntpid>\\object\\' in '%W'",
+	      proc_ntpid_object__name);
+	  valid = false;
+	  break;
+	}
+      *separators[i] = L'\0';
     }
+
+  procfs_creation_ntpid = GetCurrentProcessId ();
+
+  while (++i < (int)(sizeof (separators) / sizeof (separators[0])))
+    {
+      if (valid)
+	{
+	  BOOL ret = CreateDirectoryW (nt_max_path_buf, &sec_none_nih);
+	  if (ret)
+	    debug_printf ("%d = CreateDirectoryW (%W)%s",
+	      ret, nt_max_path_buf, in_load_after_fork ? " in load after fork" : "");
+	  else
+	  if (GetLastError () != ERROR_ALREADY_EXISTS)
+	    {
+	      debug_printf ("%d = CreateDirectoryW (%W [%d])%s %E",
+		ret, nt_max_path_buf, i, in_load_after_fork ? " in load after fork" : "");
+	      valid = false;
+	      break; /* leave last tried directory in nt_max_path_buf */
+	    }
+	}
+      *separators[i] = L'\\';
+    }
+
+  if (valid && main_executable)
+    {
+      wcsncat (nt_max_path_buf, main_executable->modname, NT_MAX_PATH);
+      wcsncat (nt_max_path_buf, L".local", NT_MAX_PATH);
+      HANDLE hlocal = CreateFileW (
+		nt_max_path_buf,          // lpFileName
+		GENERIC_WRITE,            // dwResiredAccess
+		FILE_SHARE_READ,          // dwShareMode
+		&sec_none_nih,            // lpSecurityAttributes
+		CREATE_ALWAYS,            // dwCreationDisposition
+		0,                        // dwFlagsAndAttributes
+		NULL);                    // hTemplateFile
+      if (hlocal != INVALID_HANDLE_VALUE)
+	{
+	  debug_printf ("%p = CreateFileW (%W)", hlocal, nt_max_path_buf);
+	  CloseHandle (hlocal);
+	}
+      else
+	debug_printf ("%p = CreateFileW (%W) %E", hlocal, nt_max_path_buf);
+    }
+
+  return valid;
+}
+
+void
+dll_list::delete_proc_ntpid_object_dir ()
+{
+  if (!proc_ntpid_object__name || !*proc_ntpid_object__name)
+    return; /* disabled */
+
+  /* Called under loader lock conditions so this function can't be called
+     multiple times in parallel.  The static buffer is safe. */
+  wcsncpy (nt_max_path_buf, proc_ntpid_object__name, NT_MAX_PATH);
+  wcsncat (nt_max_path_buf, main_executable->modname, NT_MAX_PATH);
+  wcsncat (nt_max_path_buf, L".local", NT_MAX_PATH);
+
+  PWCHAR p;
+  for (int i = 3; i && (p = wcsrchr (nt_max_path_buf, L'\\')); i--)
+    {
+      UNICODE_STRING dn;
+      RtlInitUnicodeString (&dn, nt_max_path_buf);
+
+      path_conv pc (&dn);
+      NTSTATUS ret = unlink_nt (pc);
+      debug_printf ("%y = unlink_nt %W", ret, nt_max_path_buf);
+      *p = L'\0';
+    }
+}
+
+void
+dll_list::cleanup_procfs ()
+{
+  if (!proc_ntpid_object__name || !*proc_ntpid_object__name)
+    return; /* disabled */
+
+  if (procfs_creation_ntpid != GetCurrentProcessId ())
+    {
+      /* Some child cygwin-process failed to call load_after_fork ();
+       * Do not cleanup the parent's one, just yell a little. */
+      system_printf ("skip for winpid %d, created by %d",
+	  GetCurrentProcessId (), procfs_creation_ntpid);
+      return;
+    }
+
+  dll *d = &start;
+  while ((d = d->next))
+    d->delete_procfs_hardlink ();
+
+  delete_proc_ntpid_object_dir ();
 }
 
 struct dllcrt0_info
@@ -684,10 +1081,20 @@ dll_dllcrt0_1 (VOID *x)
      initialize the DLL.  If we haven't finished initializing,
      it may not be safe to call the dll's "main" since not
      all of cygwin's internal structures may have been set up. */
-  if (!d || (!linked && !d->init ()))
+  if (!d)
     res = (PVOID) -1;
   else
+  if (linked)
     res = (PVOID) d;
+  else
+    {
+      if (!in_load_after_fork)
+        d->create_procfs_hardlink ();
+      if (!d->init ())
+	res = (PVOID) -1;
+      else
+	res = (PVOID) d;
+    }
 }
 
 #ifndef __x86_64__
diff --git a/winsup/cygwin/dll_init.h b/winsup/cygwin/dll_init.h
index 8127d0b..e25c15a 100644
--- a/winsup/cygwin/dll_init.h
+++ b/winsup/cygwin/dll_init.h
@@ -43,6 +43,7 @@ struct per_module
 typedef enum
 {
   DLL_NONE,
+  DLL_SELF, /* anyprogram.exe, cygwin1.dll */
   DLL_LINK,
   DLL_LOAD,
   DLL_ANY
@@ -61,8 +62,13 @@ struct dll
   DWORD image_size;
   void* preferred_base;
   PWCHAR modname;
+  PWCHAR procfs_name; /* "<cygroot>\\proc\\<ntpid>\\object\\*.dll" */
   WCHAR name[1];
   void detach ();
+  void delete_procfs_hardlink_only ();
+  void delete_procfs_hardlink ();
+  void create_procfs_hardlink (PCWCHAR origin = NULL);
+  void nominate_procfs_hardlink (PCWCHAR);
   int init ();
   void run_dtors ()
   {
@@ -72,6 +78,10 @@ struct dll
 	p.run_dtors ();
       }
   }
+  PWCHAR forkedname ()
+  {
+    return procfs_name && *procfs_name ? procfs_name : name;
+  }
 };
 
 #define MAX_DLL_BEFORE_INIT     100
@@ -82,10 +92,15 @@ class dll_list
   dll *hold;
   dll_type hold_type;
   static muto protect;
+  PWCHAR proc_ntpid_object__name; /* "<cygroot>\\proc\\<ntpid>\\object\\" */
+  DWORD proc_ntpid_object__size;
+  DWORD procfs_creation_ntpid;
 public:
+  static PWCHAR fixup_path (PWCHAR);
   dll start;
   int loaded_dlls;
   int reload_on_fork;
+  dll *main_executable;
   dll *operator [] (const PWCHAR name);
   dll *alloc (HINSTANCE, per_process *, dll_type);
   dll *find (void *);
@@ -94,7 +109,12 @@ public:
   void load_after_fork (HANDLE);
   void reserve_space ();
   void load_after_fork_impl (HANDLE, dll* which, int retries);
+  void nominate_proc_ntpid_object_dir ();
+  bool create_proc_ntpid_object_dir ();
+  void delete_proc_ntpid_object_dir ();
+  void cleanup_procfs ();
   dll *find_by_modname (const PWCHAR name);
+  dll *find_by_forkedname (PCWCHAR name);
   void populate_deps (dll* d);
   void topsort ();
   void topsort_visit (dll* d, bool goto_tail);
diff --git a/winsup/cygwin/fork.cc b/winsup/cygwin/fork.cc
index 084f8f0..8c4cb8c 100644
--- a/winsup/cygwin/fork.cc
+++ b/winsup/cygwin/fork.cc
@@ -194,6 +194,7 @@ frok::child (volatile char * volatile here)
       loaded dlls' data/bss. */
   if (!load_dlls)
     {
+      dlls.load_after_fork (0); /* create child's dll hardlinks */
       cygheap->fdtab.fixup_after_fork (hParent);
       sync_with_parent ("performed fork fixup", false);
     }
@@ -337,6 +338,9 @@ frok::parent (volatile char * volatile stack_here)
 
   PROCESS_INFORMATION pi;
   STARTUPINFOW si;
+  PWCHAR forking_progname = dlls.main_executable->forkedname();
+  if (!forking_progname || !*forking_progname)
+    forking_progname = myself->progname;
 
   memset (&si, 0, sizeof (si));
   si.cb = sizeof si;
@@ -345,7 +349,7 @@ frok::parent (volatile char * volatile stack_here)
   si.cbReserved2 = sizeof (ch);
 
   syscall_printf ("CreateProcessW (%W, %W, 0, 0, 1, %y, 0, 0, %p, %p)",
-		  myself->progname, myself->progname, c_flags, &si, &pi);
+		  forking_progname, myself->progname, c_flags, &si, &pi);
   bool locked = __malloc_lock ();
 
   /* Remove impersonation */
@@ -357,7 +361,7 @@ frok::parent (volatile char * volatile stack_here)
   while (1)
     {
       hchild = NULL;
-      rc = CreateProcessW (myself->progname,	/* image to run */
+      rc = CreateProcessW (forking_progname,	/* image to run */
 			   GetCommandLineW (),	/* Take same space for command
 						   line as in parent to make
 						   sure child stack is allocated
@@ -377,7 +381,7 @@ frok::parent (volatile char * volatile stack_here)
       else
 	{
 	  this_errno = geterrno_from_win_error ();
-	  error ("CreateProcessW failed for '%W'", myself->progname);
+	  error ("CreateProcessW failed for '%W' (using '%W')", myself->progname, forking_progname);
 	  memset (&pi, 0, sizeof (pi));
 	  goto cleanup;
 	}
diff --git a/winsup/cygwin/pinfo.cc b/winsup/cygwin/pinfo.cc
index d0b4cd9..6f86a7f 100644
--- a/winsup/cygwin/pinfo.cc
+++ b/winsup/cygwin/pinfo.cc
@@ -28,6 +28,7 @@ details. */
 #include "cygtls.h"
 #include "tls_pbuf.h"
 #include "child_info.h"
+#include "dll_init.h"
 
 class pinfo_basic: public _pinfo
 {
@@ -225,6 +226,8 @@ pinfo::exit (DWORD n)
   int exitcode = self->exitcode & 0xffff;
   if (!self->cygstarted)
     exitcode = ((exitcode & 0xff) << 8) | ((exitcode >> 8) & 0xff);
+  sigproc_printf ("Calling dlls.cleanup_procfs n %y, exitcode %y", n, exitcode);
+  dlls.cleanup_procfs ();
   sigproc_printf ("Calling ExitProcess n %y, exitcode %y", n, exitcode);
   if (!TerminateProcess (GetCurrentProcess (), exitcode))
     system_printf ("TerminateProcess failed, %E");
diff --git a/winsup/cygwin/syscalls.cc b/winsup/cygwin/syscalls.cc
index c08d12f..a2da7e3 100644
--- a/winsup/cygwin/syscalls.cc
+++ b/winsup/cygwin/syscalls.cc
@@ -689,8 +689,8 @@ check_dir_not_empty (HANDLE dir, path_conv &pc)
   return STATUS_SUCCESS;
 }
 
-NTSTATUS
-unlink_nt (path_conv &pc)
+static NTSTATUS
+_unlink_nt (path_conv &pc, bool shareable)
 {
   NTSTATUS status;
   HANDLE fh, fh_ro = NULL;
@@ -771,6 +771,9 @@ retry_open:
      bin so that it actually disappears from its directory even though its
      in use.  Otherwise, if opening doesn't fail, the file is not in use and
      we can go straight to setting the delete disposition flag.
+     However, while we have the file open with FILE_SHARE_DELETE, using
+     this file via another hardlink for anything other than DELETE by
+     concurrent processes fails. The 'shareable' argument is to prevent this.
 
      NOTE: The missing sharing modes FILE_SHARE_READ and FILE_SHARE_WRITE do
 	   NOT result in a STATUS_SHARING_VIOLATION, if another handle is
@@ -780,7 +783,10 @@ retry_open:
 	   will succeed.  So, apparently there is no reliable way to find out
 	   if a file is already open elsewhere for other purposes than
 	   reading and writing data.  */
-  status = NtOpenFile (&fh, access, &attr, &io, FILE_SHARE_DELETE, flags);
+  if (shareable)
+    status = STATUS_SHARING_VIOLATION;
+  else
+    status = NtOpenFile (&fh, access, &attr, &io, FILE_SHARE_DELETE, flags);
   /* STATUS_SHARING_VIOLATION is what we expect. STATUS_LOCK_NOT_GRANTED can
      be generated under not quite clear circumstances when trying to open a
      file on NFS with FILE_SHARE_DELETE only.  This has been observed with
@@ -1026,6 +1032,18 @@ out:
   return status;
 }
 
+NTSTATUS
+unlink_nt (path_conv &pc)
+{
+  return _unlink_nt (pc, false);
+}
+
+NTSTATUS
+unlink_nt_shareable (path_conv &pc)
+{
+  return _unlink_nt (pc, true);
+}
+
 extern "C" int
 unlink (const char *ourname)
 {
-- 
2.0.5

