Skip to content

lib: windows: Keep the owner execute bit in the nova.exec stream - #6

Merged
aoinoikaz merged 3 commits into
windows-pe32-ntcallfrom
windows-chmod-exec-stream
Nov 4, 2026
Merged

aoinoikaz merged 3 commits into
windows-pe32-ntcallfrom
windows-chmod-exec-stream

Conversation

@aoinoikaz

Copy link
Copy Markdown

On Windows nova-nix reads a file's execute bit from its nova.exec alternate data stream. Mes libc's chmod did nothing and _open3 ignored its mode, so a program built on Mes libc could not mark its output.

  • malloc now aligns to a long under M2-Planet too. mes-m2's blocks could sit at odd addresses, and the kernel fails a file call whose UTF-16 object name is misaligned (STATUS_DATATYPE_MISALIGNMENT); the stream name is the first such name built with malloc.
  • chmod follows M2libc's (x86/windows/sys/stat.c at 13e57eb): it fails for a missing file, leaves a directory alone, and writes the stream holding "1" when the mode has S_IXUSR or deletes it, taking STATUS_OBJECT_NAME_NOT_FOUND as clear. The name goes on the NT path, since Win32 reads a:nova.exec as drive A.
  • _open3 marks a file it creates whose mode has S_IXUSR; Mes libc keeps no umask on Windows.
  • The Windows job has mes-m2 run scaffold/exec-bit.scm to create files with 0755 and 0644, chmod one 0755 then 0644, and read the stream after each step.

Not covered: M2libc's open; the MesCC-built libc (nova-nix's Mes job); an existing file _open3 rewrites with FILE_OVERWRITE_IF, whose named streams NTFS is reported to drop.

Refs Novavero-AI/nova-nix#366.

malloc rounds __brk up to max_align_t before it places the header, but
only for compilers other than M2-Planet, which has no uintptr_t or
max_align_t.  Built by M2-Planet, as mes-m2 is on Linux and on Windows,
it handed out __brk plus a long, so a request whose size is not a
multiple of a long left the next block misaligned, and an odd one left
it at an odd address.  Linux's syscalls take a buffer at any address,
so nothing noticed.  Windows does not: the kernel captures the object
name a file call is given with a probe that fails a UTF-16 buffer at an
odd address with STATUS_DATATYPE_MISALIGNMENT.  Every such name has
come from RtlDosPathNameToNtPathName_U, which allocates on the process
heap; one built in memory from malloc would pass or fail depending on
what was allocated before it.  M2libc's Windows malloc had the same
failure, and rounds to 8 since its commit 13e57eb.

Under M2-Planet __brk is now rounded up to the size of a long, its
widest type, which holds a pointer on every target it compiles for, so
what malloc gives back is aligned to a long, as it is for the other
compilers.  Their branch is unchanged.

* lib/linux/malloc.c (M2_MALLOC_ALIGN): New macro.
(malloc): Round __brk up to it under M2-Planet.  Say why the header
starts aligned.
lib/windows/chmod.c returned 0 having done nothing, and _open3 ignored
its mode.  nova-nix, which builds Nix derivations on Windows, takes a
file's execute bit for its Nix archive from an NTFS alternate data
stream named nova.exec, whose presence is the bit, so a program built
on Mes libc could not mark its output executable.  M2libc's Windows
chmod writes that stream from the owner execute bit, the bit upstream
Nix reads.

chmod now does the same.  It fails for a file that is not there, leaves
a directory alone, and otherwise creates the stream holding "1" when the
mode has S_IXUSR and deletes it when it does not.  The stream is named
on the NT path, since Win32 reads "a:nova.exec" as a path on drive A.
Where M2libc queries the stream before deleting it, this deletes it and
takes STATUS_OBJECT_NAME_NOT_FOUND as already clear, so chmod returns -1
for a failure other than absence.

_open3 marks a file it creates whose mode has S_IXUSR, since the mode
counts only when the open creates the file, and writes no stream for any
other file.  This C library keeps no umask on Windows
(lib/stub/umask.c), so the mode is used as given.  A file it creates
but cannot mark is closed and the open fails, and the file stays.  The
S_IXUSR test is in chmod.c because kaem.windows.run compiles _open3.c
before <sys/stat.h>.

* lib/windows/chmod.c (__exec_stream, __exec_mark, __exec_create): New
functions.
(chmod): Set or clear the nova.exec stream from the owner execute bit.
* lib/windows/_open3.c (_open3): Mark a file it creates with
__exec_create.
* include/windows/ntdll.h (__exec_create): Declare.
On windows-latest and windows-11-arm, scaffold/exec-bit.scm has mes-m2
make a file with open-output-file and mode 0755, make one with 0644,
then chmod the second to 0755 and back to 0644.  After each step the
job reads the file's nova.exec stream through its Win32 name, as
nova-nix does, and fails unless the stream is there, holding "1",
exactly when the mode had S_IXUSR.  The files have one-letter names, so
the check also fails if the stream is named on the path chmod was
given, which Win32 reads as a drive.

* .github/workflows/windows.yml (mes-m2): Add the check.
* scaffold/exec-bit.scm: New file.
@aoinoikaz
aoinoikaz merged commit d9ab4ea into windows-pe32-ntcall Nov 4, 2026
3 checks passed
@aoinoikaz
aoinoikaz deleted the windows-chmod-exec-stream branch November 4, 2026 01:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant