Files, registry, object paths, and authority

Explain path mapping, registry state and host authority boundaries.

14 min readChapter 159 of 168Intermediate → advanced
In this chapter

Before you start#

Review path lookup and permissions, Wine prefixes, ntdll file requests, and wineserver handles. This chapter pins upstream Wine wine-10.0 to commit b073859675060c9211fcbccfd90e4e87520dc2c2. The file and registry examples are synthetic. Do not inspect an existing prefix or personal registry files for this exercise.

Learning goals#

Trace a Windows-facing path and file request into Wine's Unix and server layers, reason about sharing and pending deletion, distinguish registry views and persistence, and identify the separate host authority boundary.

Source walkthrough#

Names and handles refer to different objects
Names and handles refer to different objectsNT file path: Drive mapping and path translation → File object and host file: Access and share checks govern opens (resolve and open). Registry key path: Server namespace and selected view → Registry key object: Typed values and persistence are separate (lookup and modify)resolve and openlookup and modifyNT file pathDrive mapping and pathtranslationRegistry key pathServer namespace andselected viewFile object and host fileAccess and share checksgovern opensRegistry key objectTyped values and persistenceare separate
NT file pathDrive mapping and path translation
File object and host fileAccess and share checks govern opens
Registry key pathServer namespace and selected view
Registry key objectTyped values and persistence are separate
Read the text version
  • NT file path: Drive mapping and path translation → File object and host file: Access and share checks govern opens (resolve and open)
  • Registry key path: Server namespace and selected view → Registry key object: Typed values and persistence are separate (lookup and modify)
Conceptual model. A simplified ownership map; the host still enforces its own authority.

A Windows-facing name can be expressed in an NT object namespace, including forms such as \??\C: that relate DOS drive naming to device or file objects. A Wine prefix's dosdevices entries can map drive letters to host paths. A displayed C: path is not itself a Linux absolute pathname and is not a security boundary. Wine must interpret the Unicode name, object attributes, root handle, requested access, sharing mode, disposition and options before reaching a particular server object or Unix path. The exact mapping depends on the prefix and runtime configuration.

Windows-facing strings commonly use UTF-16 code units; Linux filesystems generally expose byte sequences interpreted according to userspace and filesystem conventions. Case-insensitive Windows-style lookup and case-sensitive host names can conflict. Two host names that differ only in case can be ambiguous under a case-insensitive lookup; Wine's actual lookup and redirection rules determine which names are found or rejected. Unicode normalization, invalid sequences and filesystem behavior require their own analysis. A directory enumeration is a time-dependent view, not a guarantee that the next open sees the same entry after another process renames or removes it.

In dlls/ntdll/unix/file.c, NtCreateFile builds a redirected name and uses nt_to_unix_file_name for the ordinary path. The following exact Wine 10.0 excerpt is from nt_to_unix_file_name; the file notice grants LGPL-2.1-or-later. Surrounding declarations and the relative-root path are omitted:

c
    if (!attr->RootDirectory)  /* without root dir fall back to normal lookup */
        return nt_to_unix_file_name_no_root( attr->ObjectName, name_ret, disposition );

If there is no root-directory handle, the function delegates to its normal lookup helper with the object name and requested disposition. That branch does not establish that a file exists or is accessible. With a root handle, the function treats the supplied name as relative, obtains a Unix fd for the root through the server interface, checks that it represents a directory, and performs lookup while holding dir_mutex around its temporary working-directory change. It restores the previous directory where possible, closes temporary descriptors when ownership requires, and frees the allocated name on error. These details matter because process working-directory state and temporary fd lifetime must not be raced or leaked.

Open, share and delete state#

NtCreateFile combines requested access, share flags, create/open disposition and options. A request to open an existing file differs from a request that may create it. Wine can resolve an ordinary Unix path, handle a special object through a server path, or return an error before host opening. On a successful ordinary path it calls open_unix_file and frees the temporary Unix name afterward. The returned NT status, output handle and I/O status block must be interpreted together; a failed request does not produce a valid handle merely because an output variable contains an old value.

Wine's server maintains file and fd-related state for operations that need Windows-compatible sharing and object lifetime. In server/fd.c, sharing checks compare new requested access with sharing permitted by existing opens, and existing access with sharing permitted by the new open. The file's close and pending-unlink paths coordinate server-owned state. A Unix process outside the relevant Wine server can access the same underlying file under host rules without participating in all Wine share-mode bookkeeping; Wine sharing is not a universal advisory lock imposed on every native Linux process.

The sharing comparison is check_sharing. In server/fd.c, Wine 10.0, LGPL-2.1-or-later, the first lines compare the new open with the other opens of the same inode. The mapping and delete-on-close checks that follow the violation test are omitted:

c
    LIST_FOR_EACH( ptr, &fd->inode->open )
    {
        struct fd *fd_ptr = LIST_ENTRY( ptr, struct fd, inode_entry );
        if (fd_ptr != fd)
        {
            /* if access mode is 0, sharing mode is ignored */
            if (fd_ptr->access & all_access) existing_sharing &= fd_ptr->sharing;
            existing_access |= fd_ptr->access;
        }
    }

    if (((access & read_access) && !(existing_sharing & FILE_SHARE_READ)) ||
        ((access & write_access) && !(existing_sharing & FILE_SHARE_WRITE)) ||
        ((access & DELETE) && !(existing_sharing & FILE_SHARE_DELETE)))
        return STATUS_SHARING_VIOLATION;

Opens that request no data access do not restrict sharing, as the comment says. Otherwise, the new request's read, write and delete bits are tested against the sharing that every other open still allows, so one existing open that omits write sharing blocks a new writer. This function works on server open records and does not test host file mode bits; those are separate checks on the host operation.

Consider two synthetic Wine handles to one ordinary file within the same relevant server context. H1 opens for read and permits read, write and delete sharing; H2 opens for write and permits the accesses already in use. Both opens can succeed if all other access and host checks succeed. If H1 had denied write sharing, H2's write request would fail the applicable sharing check rather than silently override H1. Conversely, a new handle's share flags must be compatible with existing access. These examples assume ordinary file behavior and omit special devices and races.

A second open is checked against the first
A second open is checked against the first1. Process A (H1) to Wine server (fd.c): Open for read; share read, write and delete. 2. Wine server (fd.c) to Process A (H1) (return): H1 granted. 3. Process B (H2) to Wine server (fd.c): Open for write; compare with H1 sharing. 4. Wine server (fd.c) to Process B (H2) (return): Granted only if H1 permits write sharing, otherwise a sharing violation.Process A (H1)Process B (H2)Wine server (fd.c)Open for read; share read, write and deleteH1 grantedOpen for write; comparewith H1 sharingGranted only if H1permits write sharing,otherwise a sharingviolation
  1. Process A (H1)Wine server (fd.c)

    Open for read; share read, write and delete

  2. Wine server (fd.c)Process A (H1)

    Return. H1 granted

  3. Process B (H2)Wine server (fd.c)

    Open for write; compare with H1 sharing

  4. Wine server (fd.c)Process B (H2)

    Return. Granted only if H1 permits write sharing, otherwise a sharing violation

Time runs downward. Dashed arrows show returns.

Read the text version
  1. Process A (H1) → Wine server (fd.c): Open for read; share read, write and delete
  2. Wine server (fd.c) → Process A (H1) (return): H1 granted
  3. Process B (H2) → Wine server (fd.c): Open for write; compare with H1 sharing
  4. Wine server (fd.c) → Process B (H2) (return): Granted only if H1 permits write sharing, otherwise a sharing violation
Conceptual model. Teaching model of the sharing comparison in Wine 10.0 server/fd.c; not a captured two-process run.

If a delete disposition is accepted while handles remain, the file can enter a pending-deletion lifecycle governed by Wine's implementation and the applicable Windows-facing semantics. A later open can be rejected depending on state and flags; final unlink can be delayed until relevant handles or fd state are released. The exact path depends on server/fd.c, disposition flags, filesystem and host operations. POSIX unlink typically removes a directory entry while existing open file descriptions remain usable, but its rules are not identical to Windows share and delete-pending semantics. Do not infer that every Wine delete request immediately calls unlink, or that all filesystems have identical behavior.

Pending deletion follows references
  1. Open accepted

    Sharing checks pass against existing opens; each handle holds its own reference.

  2. Delete requested

    A delete disposition can be accepted while other opens remain.

  3. Last relevant close

    Removal depends on server fd state and host filesystem behavior.

  4. Reopen by name

    A path can name a newly created file; the old object is not reached through it.

Conceptual model. Simplified Wine 10.0 ordering for a delete-pending case; not the exact server state machine and not a captured trace.

Registry keys, values and views#

The registry is a hierarchical configuration namespace with keys, named values, types and data. A registry key handle is a Wine/server object reference with access rights, not a Unix directory fd. Values can represent strings, numbers, binary data and other defined types; the type and length determine interpretation. A registry name that resembles a filesystem path is not automatically a host pathname. Registry access can fail because of an invalid handle, missing rights, malformed request or object state.

In server/registry.c, the set_key_value request handler validates the name length against the request data, obtains a key object with KEY_SET_VALUE, calls the value update and releases the key reference. This exact Wine 10.0 excerpt is LGPL-2.1-or-later, copyright Alexandre Julliard and contributors; surrounding length calculations are omitted:

c
    if ((key = get_hkey_obj( req->hkey, KEY_SET_VALUE )))
    {
        data_size_t datalen = get_req_data_size() - req->namelen;

The lookup asks for a key object with set-value access. Only if it succeeds does the handler calculate the remaining data length and continue to the update. The prior check rejects a name length larger than the request data; the following source passes the type and data to set_value and releases the acquired key. A denied or invalid key lookup must not be treated as a successful write. The key object, request buffer and persistent file are distinct resources.

Wine's server initializes registry roots from files including system.reg, userdef.reg and user.reg in the configured environment. They are Wine registry storage, not proof of a Windows installation. Persistence and saving are implementation-specific: in the inspected v10 source the flush_key handler obtains and releases the key but comments that it does not need to do anything in that implementation. Therefore do not promise that invoking a Windows-facing flush causes an immediate host fsync of a particular registry file. A save path, server shutdown and filesystem durability have separate behavior and error handling.

On 64-bit Windows-facing environments, registry view flags and redirection can make 32-bit and 64-bit callers see different logical views for selected keys. Wine's support and exact redirected paths depend on its build and implementation. A synthetic key value written in one view is not automatically evidence that the same value exists in the other view. Some keys may be shared rather than redirected; do not generalize one example to the whole registry. Investigate the actual key, requested view, Wine version and server implementation.

Worked file and registry timeline#

Assume a private hypothetical prefix whose C drive points into its own drive_c. H1 opens C:\work\item.txt for reading with share-read/write/delete permitted. H2 opens it for writing with a compatible share mode. Both hold server/host references as required by the implementation. A third request asks for delete access with compatible sharing and marks deletion pending. The exact outcome requires the applicable access checks and filesystem support; assume success only for this teaching case. Closing H1 does not imply the underlying object is gone while H2 or another reference remains. After the final relevant close, the server may perform the pending unlink according to its actual fd path.

Change H1 so it does not share write. H2's write open conflicts with that sharing policy and can return a sharing violation; the existing H1 remains valid. Change the host directory permissions so the Unix user cannot access the file: a host permission failure can occur independently of compatible Wine share flags. A native Linux process that opens the host file outside the Wine server may not participate in Wine's sharing checks, so the two-process experiment cannot be extrapolated to every host actor.

For a separate synthetic registry case, a 32-bit client writes a typed value Mode=1 in a key under the view selected for its operation. A 64-bit client requests the other view. If that key is redirected in the tested Wine configuration, the second client may see a different value or no value; if the key is shared, it may see the same one. The example does not assert which particular key Wine redirects. A successful set request updates server-managed registry state under the implementation's rules, but persistence after abrupt termination requires separate evidence. Key handles and temporary lookup references must be released even when a later query or save fails.

Host security and path races#

Wine's Windows-facing token and ACL emulation can enforce compatibility rules inside its object model, but it does not grant Linux root, bypass an LSM or override host mount and filesystem permissions. A host process with the same Unix user can have authority outside a prefix; an emulated administrator token does not automatically change that host identity. Conversely, launching Wine as a more privileged host user increases potential host impact. A prefix organizes Wine state; it is not an operating-system isolation boundary. Do not treat the prefix as a sandbox or run unknown executables to test permissions.

Path resolution can race with rename, symlink changes and concurrent deletion. A checked string is not necessarily the same object later opened. Wine's root-relative path code uses a directory fd and a mutex around its working-directory manipulation, but this does not eliminate all filesystem races or establish a universal security policy. A robust investigation records the actual object/handle, mount and namespace context, the operation's returned status and the relevant reference lifetime. A stale path or a handle number reused for a different object is not enough evidence of identity.

Why access, sharing, and persistence are separate decisions#

Consider two applications in one Wine server opening a report. The first requests read access and allows read sharing. The second requests write access but allows both reading and writing by other handles. Its generous sharing mask does not repair the incompatibility: the first handle did not permit a new writer. Conversely, an open that requests only reading can still fail if its own sharing mask does not allow access already held by an existing writer. Check both directions against every relevant existing open, not merely whether the new mask has a particular bit. Host access denial, a missing parent directory, and a sharing violation describe different layers and should not be collapsed into a single permissions diagnosis.

A delete request also has a time dimension. A name can become marked for deletion while references remain, subject to the applicable access and sharing decisions. A later close may make removal possible, but an application that caches a path and reopens it after a race may meet a newly created file instead of the earlier object. Handle identity, path identity and content identity are therefore different facts. Linux rename or unlink performed by a native process outside the Wine server can change the host namespace without participating in Wine's server-side sharing protocol. A compatibility layer cannot make a nonparticipant obey a Windows sharing rule merely by maintaining its own table.

For registry persistence, distinguish the successful update of a key object from recovery after an abrupt process or machine failure. The source's registry server stores typed values and has persistence machinery; the observed no-op flush handler means the word “flush” alone is not evidence of immediate durable storage in this baseline. A useful test would compare a clean shutdown and a controlled failure in a disposable environment, then inspect only authored values in that environment. It should record which registry view and prefix were used. The private-file lab below exercises none of that persistence machinery and cannot establish the result of such a test.

Practice#

Private-file naming model

This own-process exercise creates only a new temporary directory and two files with distinct names. It does not run Wine or inspect a registry:

sh
python3 - <<'PY'
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory(prefix='wine-path-study-') as d:
    root = Path(d)
    (root / 'Name').write_text('first', encoding='utf-8')
    (root / 'name').write_text('second', encoding='utf-8')
    names = sorted(p.name for p in root.iterdir())
    print('created names:', names)
    print('distinct entries:', len(names) == 2)
PY

On a case-sensitive temporary filesystem the expected result lists both names. On a case-insensitive filesystem, creation or enumeration can differ; that is a legitimate environmental counterexample, not a Wine test. The context manager removes only its newly created directory. Do not apply the commands to a real prefix or personal files.

Source-only alternative: use the synthetic H1/H2 sharing timeline and set_key_value excerpt. Write which open fails when H1 denies write sharing, which references survive one close, and why missing KEY_SET_VALUE prevents the update. Add one row explaining that the 32/64 view result depends on the actual key. Deliver the table without reading a registry or running Wine.

Knowledge check#

  1. Why is a drive-letter mapping not a host sandbox?
  2. Why can a second Wine open fail even when Linux file mode bits permit writing?
  3. What happens in the registry handler when the set-value access lookup fails?
  4. Why does one handle close not necessarily complete a pending deletion?
  5. Why does the v10 flush_key handler not justify a promise of immediate host-file durability?

Answers#

  1. It maps names to host paths; the Wine process still has the host user's authority and may have other mappings or descriptors.
  2. Windows-facing share-mode compatibility is a separate check from host DAC and can reject a conflicting open within the relevant Wine server context.
  3. The conditional body is not entered, so the shown handler does not call set_value with a failed lookup. The actual error status comes from the lookup path.
  4. Other handles, fd state or references can remain. The server's pending-unlink path and object-specific rules determine when final removal occurs.
  5. The inspected handler explicitly performs no further operation after obtaining the key. Persistence and host filesystem synchronization require separate source and runtime evidence.

Checkpoint#

Submit the sharing/delete timeline and registry access/view table. Ready means Wine sharing is not presented as a universal Linux lock, registry handles are distinguished from files, and the prefix is not called a sandbox. Revisit the pinned source if a failed lookup is treated as a successful update. This is not certification.

Sources#

A little further beneath the surface.

Check your understanding with the checkpoint. Marking a chapter as read saves a bookmark, not a proficiency assessment.