From 37fff4490694e235b1edb97dc0b94360a62873a0 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Wed, 4 Nov 2026 16:29:35 +0100 Subject: [PATCH 1/2] fix: support effective UID special variable assignment (#1972) Route `$>` writes through its special variable setter and implement the POSIX seteuid call for Unix platforms, with a Windows same-identity fallback. Keep interpreter assignments on the special setter path and add focused regression coverage. Generated with [Codex](https://openai.com/codex) Co-Authored-By: Codex --- dev/design/issue-queue.md | 3 ++- docs/about/changelog.md | 2 ++ .../perlonjava/runtime/nativ/NativeUtils.java | 8 +++++++ .../runtime/nativ/ffm/FFMPosixInterface.java | 3 +++ .../runtime/nativ/ffm/FFMPosixLinux.java | 23 +++++++++++++++++++ .../runtime/nativ/ffm/FFMPosixWindows.java | 7 ++++++ .../runtimetypes/ScalarSpecialVariable.java | 6 ++++- .../unit/special_effective_uid_assignment.t | 20 ++++++++++++++++ 8 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 src/test/resources/unit/special_effective_uid_assignment.t diff --git a/dev/design/issue-queue.md b/dev/design/issue-queue.md index 376bc326b8..c37223c72e 100644 --- a/dev/design/issue-queue.md +++ b/dev/design/issue-queue.md @@ -1,6 +1,6 @@ # Reviewed issue queues -Snapshot: 2026-11-02, from live open issues, PRs and the #1649 phase contract. +Snapshot: 2026-11-04, from live open issues, PRs and the #1649 phase contract. This is a bounded selection aid, not the complete backlog or fresh runtime validation. Follow the [triage and claim protocol](../../docs/guides/issue-triage.md) and recheck live issue comments/PRs before starting. Unlisted work is unreviewed @@ -29,6 +29,7 @@ resolved before implementation. No new pass or failure is asserted here. | Hash dereference assignment | [#1958](https://github.com/fglock/PerlOnJava/issues/1958) Undefined-reference autovivification | Normal / Active | Shared-runtime fix and focused regression pass on both backends; full `make` passes in [PR #1960](https://github.com/fglock/PerlOnJava/pull/1960). [Current claim](https://github.com/fglock/PerlOnJava/issues/1958#issuecomment-6626463414). Object-Hybrid 0.07's follow-on generated `can` dispatch failure is also addressed in that PR under #1959. | | Dynamic class method lookup | [#1959](https://github.com/fglock/PerlOnJava/issues/1959) Object::Hybrid `tieable` promotion | Normal / Active | Generated `HASH_STATIC` declares a bodyless `can`; route it through that class's `AUTOLOAD` when present. Focused reproducer and full `make` pass on both backends. The full upstream suite advances beyond this failure but later exits at `IO::File::FILENO` (separate behavior). Candidate fix is in [PR #1960](https://github.com/fglock/PerlOnJava/pull/1960). | | Scalar introspection | [#1954](https://github.com/fglock/PerlOnJava/issues/1954) `Scalar::Util::isdual` booleans | Normal / Active | Mars 0.05 passes on system Perl and failed assertions 10–11 on both PerlOnJava backends (run `20261103-123832-91742`). [Draft PR #1956](https://github.com/fglock/PerlOnJava/pull/1956) recognizes BOOLEAN values; focused boolean, explicit-dualvar and plain-string coverage plus all 13 Mars assertions pass on both backends, and full `make` passes. | +| Special-variable setters | [#1972](https://github.com/fglock/PerlOnJava/issues/1972) Writable effective UID (`$>`) | Normal / Active | `Proc-DaemonLite` 0.01 passes the full system-Perl suite but its `$> = $<` cleanup fails on both PerlOnJava backends (CPAN run `20261104-151051-20617`). Focused system-Perl coverage passes; current-master `make` confirms the regression. Implement POSIX `seteuid` and validate both backends. [Current claim](https://github.com/fglock/PerlOnJava/issues/1972#issuecomment-6633993418). | | POSIX filehandles | [#1932](https://github.com/fglock/PerlOnJava/issues/1932) `POSIX::isatty` glob handling | Normal / Active | IO-Pty-Easy 0.10 passes on system Perl; CPAN run `20261030-095654-34969` failed on both PerlOnJava backends because `*STDIN` reached a direct `RuntimeIO` cast. The helper-based fix and focused test pass on both backends; full `make` passes on `fix/posix-isatty-glob`. A fresh `jcpan -t IO::Pty::Easy` rerun timed out after `t/open-close.t` passed and fork-dependent tests skipped. | | Stash iteration | [#1963](https://github.com/fglock/PerlOnJava/issues/1963) CODE globs copied from `each` | Normal / Active | DDC::Concordance 0.50 failed on both backends when `DDC::Any::mapstash` copied module-created closures from `each %stash`; system Perl passed. The slot snapshot/alias fix in [PR #1966](https://github.com/fglock/PerlOnJava/pull/1966) and eval-based regression pass both backends; all 471 DDC tests pass on the JVM backend, and the five affected files pass on the interpreter. | | File-handle EOF state | [#1922](https://github.com/fglock/PerlOnJava/issues/1922) `IO::Handle::clearerr` | Normal / Active | `Log::Procmail` 0.14 passes on system Perl but failed on both PerlOnJava backends when reading appended data after EOF. The shared runtime fix and permanent regression are in [draft PR #1924](https://github.com/fglock/PerlOnJava/pull/1924); the full make gate and all 281 CPAN tests pass. Await review and CI. | diff --git a/docs/about/changelog.md b/docs/about/changelog.md index aaf0298604..53b731f353 100644 --- a/docs/about/changelog.md +++ b/docs/about/changelog.md @@ -32,6 +32,8 @@ priorities and future plans. - Preserve filehandle line-counter assignments made through localized `$.` scopes (#1965). +- Allow assignment to `$>` to update the process effective user ID through the platform POSIX layer (#1972). + - Resolve glob-backed filehandles in `POSIX::isatty` without assuming the glob value is a `RuntimeIO` (#1932). - Add an initial CryptX Java XS bridge for binary/base64url helpers, functional digests, and HMAC using the existing JCA and Bouncy Castle providers (#1220). diff --git a/src/main/java/org/perlonjava/runtime/nativ/NativeUtils.java b/src/main/java/org/perlonjava/runtime/nativ/NativeUtils.java index 6f24098cbc..af6ffedabc 100644 --- a/src/main/java/org/perlonjava/runtime/nativ/NativeUtils.java +++ b/src/main/java/org/perlonjava/runtime/nativ/NativeUtils.java @@ -104,6 +104,14 @@ public static RuntimeScalar geteuid(int ctx, RuntimeBase... args) { return new RuntimeScalar(posix.geteuid()); } + public static int seteuid(int uid) { + int result = posix.seteuid(uid); + if (result == -1) { + GlobalVariable.getGlobalVariable("main::!").set(posix.strerror(posix.errno())); + } + return result; + } + public static RuntimeScalar getgid(int ctx, RuntimeBase... args) { return new RuntimeScalar(posix.getgid()); } diff --git a/src/main/java/org/perlonjava/runtime/nativ/ffm/FFMPosixInterface.java b/src/main/java/org/perlonjava/runtime/nativ/ffm/FFMPosixInterface.java index 5be799edfe..5bb3858a4e 100644 --- a/src/main/java/org/perlonjava/runtime/nativ/ffm/FFMPosixInterface.java +++ b/src/main/java/org/perlonjava/runtime/nativ/ffm/FFMPosixInterface.java @@ -48,6 +48,9 @@ public interface FFMPosixInterface { * @return Effective user ID */ int geteuid(); + + /** Set the effective user ID; return 0 on success and -1 on failure. */ + int seteuid(int uid); /** * Get the real group ID. diff --git a/src/main/java/org/perlonjava/runtime/nativ/ffm/FFMPosixLinux.java b/src/main/java/org/perlonjava/runtime/nativ/ffm/FFMPosixLinux.java index ec8c854cad..562282dad4 100644 --- a/src/main/java/org/perlonjava/runtime/nativ/ffm/FFMPosixLinux.java +++ b/src/main/java/org/perlonjava/runtime/nativ/ffm/FFMPosixLinux.java @@ -34,6 +34,7 @@ public class FFMPosixLinux implements FFMPosixInterface { // Method handles for simple functions (no errno capture needed) private static MethodHandle getuidHandle; private static MethodHandle geteuidHandle; + private static MethodHandle seteuidHandle; private static MethodHandle getgidHandle; private static MethodHandle getegidHandle; private static MethodHandle getgroupsHandle; @@ -181,6 +182,12 @@ private static synchronized void ensureInitialized() { stdlib.find("geteuid").orElseThrow(), FunctionDescriptor.of(ValueLayout.JAVA_INT) ); + + seteuidHandle = linker.downcallHandle( + stdlib.find("seteuid").orElseThrow(), + FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT), + captureErrno + ); getgidHandle = linker.downcallHandle( stdlib.find("getgid").orElseThrow(), @@ -628,6 +635,22 @@ public int geteuid() { return -1; } } + + @Override + public int seteuid(int uid) { + ensureInitialized(); + try (Arena arena = Arena.ofConfined()) { + MemorySegment capturedState = arena.allocate(Linker.Option.captureStateLayout()); + int result = (int) seteuidHandle.invokeExact(capturedState, uid); + if (result == -1) { + setErrno(capturedState.get(ValueLayout.JAVA_INT, errnoOffset)); + } + return result; + } catch (Throwable e) { + setErrno(1); // EPERM + return -1; + } + } @Override public int getgid() { diff --git a/src/main/java/org/perlonjava/runtime/nativ/ffm/FFMPosixWindows.java b/src/main/java/org/perlonjava/runtime/nativ/ffm/FFMPosixWindows.java index 4ba24130d8..cb76db7ba3 100644 --- a/src/main/java/org/perlonjava/runtime/nativ/ffm/FFMPosixWindows.java +++ b/src/main/java/org/perlonjava/runtime/nativ/ffm/FFMPosixWindows.java @@ -240,6 +240,13 @@ public int getuid() { public int geteuid() { return CURRENT_UID; } + + @Override + public int seteuid(int uid) { + if (uid == CURRENT_UID) return 0; + setErrno(1); // EPERM: Windows identity changes are not emulated. + return -1; + } @Override public int getgid() { diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/ScalarSpecialVariable.java b/src/main/java/org/perlonjava/runtime/runtimetypes/ScalarSpecialVariable.java index 00c4638bdb..3ec340dedd 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/ScalarSpecialVariable.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/ScalarSpecialVariable.java @@ -67,7 +67,7 @@ public boolean isNumberedCapture() { /** Whether assignments must reach this special scalar's setter. */ public boolean hasSpecialAssignmentBehavior() { return switch (variableId) { - case INPUT_LINE_NUMBER, HINTS, WARNING_BITS, REMOVED_ENCODING -> true; + case INPUT_LINE_NUMBER, HINTS, WARNING_BITS, REMOVED_ENCODING, EFFECTIVE_UID -> true; default -> false; }; } @@ -155,6 +155,10 @@ public RuntimeScalar set(RuntimeScalar value) { } return scalarUndef; } + if (variableId == Id.EFFECTIVE_UID) { + NativeUtils.seteuid(value.getInt()); + return NativeUtils.geteuid(0); + } return super.set(value); } diff --git a/src/test/resources/unit/special_effective_uid_assignment.t b/src/test/resources/unit/special_effective_uid_assignment.t new file mode 100644 index 0000000000..7be17b4097 --- /dev/null +++ b/src/test/resources/unit/special_effective_uid_assignment.t @@ -0,0 +1,20 @@ +use strict; +use warnings; +use Test::More; + +# Assigning the current effective UID is safe and should remain a valid write +# even when real and effective IDs differ. Avoid changing this test process's +# privileges while still exercising the special variable setter. +plan skip_all => 'real and effective UIDs differ' if $< != $>; + +my $effective_uid = $>; +my $assigned = eval { + $> = $<; + 1; +}; + +ok($assigned, 'assignment to $> accepts the current real UID'); +is($@, '', 'assignment to $> does not report a read-only scalar error'); +is($>, $effective_uid, 'assignment preserves the effective UID'); + +done_testing; From ef71e623b21c4b5ac3be2c7a53f10d78185ef191 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Wed, 4 Nov 2026 16:31:22 +0100 Subject: [PATCH 2/2] docs: link effective UID issue to its PR (#1972) Record the draft implementation PR and current validation state in the reviewed issue queue. Generated with [Codex](https://openai.com/codex) Co-Authored-By: Codex --- dev/design/issue-queue.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/design/issue-queue.md b/dev/design/issue-queue.md index c37223c72e..9df988e821 100644 --- a/dev/design/issue-queue.md +++ b/dev/design/issue-queue.md @@ -29,7 +29,7 @@ resolved before implementation. No new pass or failure is asserted here. | Hash dereference assignment | [#1958](https://github.com/fglock/PerlOnJava/issues/1958) Undefined-reference autovivification | Normal / Active | Shared-runtime fix and focused regression pass on both backends; full `make` passes in [PR #1960](https://github.com/fglock/PerlOnJava/pull/1960). [Current claim](https://github.com/fglock/PerlOnJava/issues/1958#issuecomment-6626463414). Object-Hybrid 0.07's follow-on generated `can` dispatch failure is also addressed in that PR under #1959. | | Dynamic class method lookup | [#1959](https://github.com/fglock/PerlOnJava/issues/1959) Object::Hybrid `tieable` promotion | Normal / Active | Generated `HASH_STATIC` declares a bodyless `can`; route it through that class's `AUTOLOAD` when present. Focused reproducer and full `make` pass on both backends. The full upstream suite advances beyond this failure but later exits at `IO::File::FILENO` (separate behavior). Candidate fix is in [PR #1960](https://github.com/fglock/PerlOnJava/pull/1960). | | Scalar introspection | [#1954](https://github.com/fglock/PerlOnJava/issues/1954) `Scalar::Util::isdual` booleans | Normal / Active | Mars 0.05 passes on system Perl and failed assertions 10–11 on both PerlOnJava backends (run `20261103-123832-91742`). [Draft PR #1956](https://github.com/fglock/PerlOnJava/pull/1956) recognizes BOOLEAN values; focused boolean, explicit-dualvar and plain-string coverage plus all 13 Mars assertions pass on both backends, and full `make` passes. | -| Special-variable setters | [#1972](https://github.com/fglock/PerlOnJava/issues/1972) Writable effective UID (`$>`) | Normal / Active | `Proc-DaemonLite` 0.01 passes the full system-Perl suite but its `$> = $<` cleanup fails on both PerlOnJava backends (CPAN run `20261104-151051-20617`). Focused system-Perl coverage passes; current-master `make` confirms the regression. Implement POSIX `seteuid` and validate both backends. [Current claim](https://github.com/fglock/PerlOnJava/issues/1972#issuecomment-6633993418). | +| Special-variable setters | [#1972](https://github.com/fglock/PerlOnJava/issues/1972) Writable effective UID (`$>`) | Normal / Active | `Proc-DaemonLite` 0.01 passes the full system-Perl suite but its `$> = $<` cleanup fails on both PerlOnJava backends (CPAN run `20261104-151051-20617`). POSIX `seteuid`, regression coverage and passing full `make` are in [draft PR #1974](https://github.com/fglock/PerlOnJava/pull/1974); the focused regression and `t/20compile.t` pass on both backends. [Current claim](https://github.com/fglock/PerlOnJava/issues/1972#issuecomment-6633993418). | | POSIX filehandles | [#1932](https://github.com/fglock/PerlOnJava/issues/1932) `POSIX::isatty` glob handling | Normal / Active | IO-Pty-Easy 0.10 passes on system Perl; CPAN run `20261030-095654-34969` failed on both PerlOnJava backends because `*STDIN` reached a direct `RuntimeIO` cast. The helper-based fix and focused test pass on both backends; full `make` passes on `fix/posix-isatty-glob`. A fresh `jcpan -t IO::Pty::Easy` rerun timed out after `t/open-close.t` passed and fork-dependent tests skipped. | | Stash iteration | [#1963](https://github.com/fglock/PerlOnJava/issues/1963) CODE globs copied from `each` | Normal / Active | DDC::Concordance 0.50 failed on both backends when `DDC::Any::mapstash` copied module-created closures from `each %stash`; system Perl passed. The slot snapshot/alias fix in [PR #1966](https://github.com/fglock/PerlOnJava/pull/1966) and eval-based regression pass both backends; all 471 DDC tests pass on the JVM backend, and the five affected files pass on the interpreter. | | File-handle EOF state | [#1922](https://github.com/fglock/PerlOnJava/issues/1922) `IO::Handle::clearerr` | Normal / Active | `Log::Procmail` 0.14 passes on system Perl but failed on both PerlOnJava backends when reading appended data after EOF. The shared runtime fix and permanent regression are in [draft PR #1924](https://github.com/fglock/PerlOnJava/pull/1924); the full make gate and all 281 CPAN tests pass. Await review and CI. |