forkables: hardlink without WRITE_ATTRIBUTES first
When the current process has renamed (to bin) a readonly dll, we get STATUS_TRANSACTION_NOT_ACTIVE for unknown reason when subsequently creating the forkable hardlink. A workaround is to open the original file with FILE_WRITE_ATTRIBUTES access, but that fails with permission denied for users not owning the original file. * forkable.cc (dll::create_forkable): Retry hardlink creation using the original file's handle opened with FILE_WRITE_ATTRIBUTES access when the first attempt fails with STATUS_TRANSACTION_NOT_ACTIVE.
This commit is contained in:
		
							parent
							
								
									22d68bada3
								
							
						
					
					
						commit
						3956ddd9bf
					
				| 
						 | 
				
			
			@ -423,7 +423,14 @@ dll::nominate_forkable (PCWCHAR dirx_name)
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
/* Create the nominated hardlink for one indivitual dll,
 | 
			
		||||
   inside another subdirectory when dynamically loaded. */
 | 
			
		||||
   inside another subdirectory when dynamically loaded.
 | 
			
		||||
 | 
			
		||||
   We've not found a performant way yet to protect fork against
 | 
			
		||||
   updates to main executables and/or dlls that do not reside on
 | 
			
		||||
   the same NTFS filesystem as the <cygroot>/var/run/cygfork/
 | 
			
		||||
   directory.  But as long as the main executable can be hardlinked,
 | 
			
		||||
   dll redirection works for any other hardlink-able dll, while
 | 
			
		||||
   non-hardlink-able dlls are used from their original location. */
 | 
			
		||||
bool
 | 
			
		||||
dll::create_forkable ()
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -465,14 +472,6 @@ dll::create_forkable ()
 | 
			
		|||
  if (devhandle == INVALID_HANDLE_VALUE)
 | 
			
		||||
    return false; /* impossible */
 | 
			
		||||
 | 
			
		||||
  HANDLE fh = dll_list::ntopenfile ((PCWCHAR)&fii.IndexNumber, NULL,
 | 
			
		||||
				    FILE_OPEN_BY_FILE_ID,
 | 
			
		||||
				    FILE_WRITE_ATTRIBUTES,
 | 
			
		||||
				    devhandle);
 | 
			
		||||
  NtClose (devhandle);
 | 
			
		||||
  if (fh == INVALID_HANDLE_VALUE)
 | 
			
		||||
    return false; /* impossible */
 | 
			
		||||
 | 
			
		||||
  int ntlen = wcslen (ntname);
 | 
			
		||||
  int bufsize = sizeof (FILE_LINK_INFORMATION) + ntlen * sizeof (*ntname);
 | 
			
		||||
  PFILE_LINK_INFORMATION pfli = (PFILE_LINK_INFORMATION) alloca (bufsize);
 | 
			
		||||
| 
						 | 
				
			
			@ -483,22 +482,47 @@ dll::create_forkable ()
 | 
			
		|||
  pfli->ReplaceIfExists = FALSE; /* allow concurrency */
 | 
			
		||||
  pfli->RootDirectory = NULL;
 | 
			
		||||
 | 
			
		||||
  IO_STATUS_BLOCK iosb;
 | 
			
		||||
  NTSTATUS status = NtSetInformationFile (fh, &iosb, pfli, bufsize,
 | 
			
		||||
					  FileLinkInformation);
 | 
			
		||||
  NtClose (fh);
 | 
			
		||||
  debug_printf ("%y = NtSetInformationFile (%p, FileLink %W, iosb.Status %y)",
 | 
			
		||||
		status, fh, pfli->FileName, iosb.Status);
 | 
			
		||||
  if (NT_SUCCESS (status) || status == STATUS_OBJECT_NAME_COLLISION)
 | 
			
		||||
    /* We've not found a performant way yet to protect fork against updates
 | 
			
		||||
       to main executables and/or dlls that do not reside on the same NTFS
 | 
			
		||||
       filesystem as the <cygroot>/var/run/cygfork/ directory.
 | 
			
		||||
       But as long as the main executable can be hardlinked, dll redirection
 | 
			
		||||
       works for any other hardlink-able dll, while non-hardlink-able dlls
 | 
			
		||||
       are used from their original location. */
 | 
			
		||||
    return true;
 | 
			
		||||
  /* When we get STATUS_TRANSACTION_NOT_ACTIVE from hardlink creation,
 | 
			
		||||
     the current process has renamed the file while it had the readonly
 | 
			
		||||
     attribute.  The rename() function uses a transaction for combined
 | 
			
		||||
     writeable+rename action if possible to provide atomicity.
 | 
			
		||||
     Although the transaction is closed afterwards, creating a hardlink
 | 
			
		||||
     for this file requires the FILE_WRITE_ATTRIBUTES access, for unknown
 | 
			
		||||
     reason.  On the other hand, always requesting FILE_WRITE_ATTRIBUTES
 | 
			
		||||
     would fail for users that do not own the original file. */
 | 
			
		||||
  bool ret = false;
 | 
			
		||||
  int access = 0; /* first attempt */
 | 
			
		||||
  while (true)
 | 
			
		||||
    {
 | 
			
		||||
      HANDLE fh = dll_list::ntopenfile ((PCWCHAR)&fii.IndexNumber, NULL,
 | 
			
		||||
					FILE_OPEN_BY_FILE_ID,
 | 
			
		||||
					access,
 | 
			
		||||
					devhandle);
 | 
			
		||||
      if (fh == INVALID_HANDLE_VALUE)
 | 
			
		||||
	break; /* impossible */
 | 
			
		||||
 | 
			
		||||
  return false;
 | 
			
		||||
      IO_STATUS_BLOCK iosb;
 | 
			
		||||
      NTSTATUS status = NtSetInformationFile (fh, &iosb, pfli, bufsize,
 | 
			
		||||
					      FileLinkInformation);
 | 
			
		||||
      NtClose (fh);
 | 
			
		||||
      debug_printf ("%y = NtSetInformationFile (%p, FileLink %W, iosb.Status %y)",
 | 
			
		||||
		    status, fh, pfli->FileName, iosb.Status);
 | 
			
		||||
      if (NT_SUCCESS (status) || status == STATUS_OBJECT_NAME_COLLISION)
 | 
			
		||||
	{
 | 
			
		||||
	  ret = true;
 | 
			
		||||
	  break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
      if (status != STATUS_TRANSACTION_NOT_ACTIVE ||
 | 
			
		||||
	  access == FILE_WRITE_ATTRIBUTES)
 | 
			
		||||
	break;
 | 
			
		||||
 | 
			
		||||
      access = FILE_WRITE_ATTRIBUTES; /* second attempt */
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  NtClose (devhandle);
 | 
			
		||||
 | 
			
		||||
  return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* return the number of characters necessary to store one forkable name */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue