Skip to content

Fix some vulnerabilities related to what is shared to the safe compartment. - #1522

Open
drgrice1 wants to merge 1 commit into
openwebwork:developfrom
drgrice1:vulnerability-fixes
Open

Fix some vulnerabilities related to what is shared to the safe compartment.#1522
drgrice1 wants to merge 1 commit into
openwebwork:developfrom
drgrice1:vulnerability-fixes

Conversation

@drgrice1

@drgrice1 drgrice1 commented Aug 21, 2026

Copy link
Copy Markdown
Member

First enforce that the trusted Rserve host from the WeBWorK::PG::Environment is used. The Rserve package gets this from the secure $WeBWorK::PG::IO::pg_envir variable that is not exposed to the safe compartment instead of from the $main::Rserve variable that is. The $main:::Rserve variable can be modified by the problem author.

Second, prevent PG problems from bootstrapping arbitrary shared libraries via DynaLoader. The entire DynaLoader package was shared into the safe compartment, so any .pg problem could call DynaLoader::dl_load_file on any shared library already present on disk, find and install one of its symbols as an XS sub via dl_find_symbol/dl_install_xsub, and call it directly. Doing this with POSIX.so, which ships with every Perl install, gives raw POSIX::open/read/write/close, bypassing both the 'open' opcode restriction and WeBWorK::PG::IO's permitted_read_dir restriction entirely.

The reason DynaLoader was shared is because it is in the GD package's @ISA array. The actual DynaLoader package is not needed for PG's usage of GD via WWPlot.pm, but the DynaLoader symbol can't simply be dropped as it is needed Perl's method resolution for anything that walks the inheritance chain. So the new WWSafe::share_empty_package creates an empty, disconnected stash under the compartment's own root satisfying that structural requirement without exposing any real DynaLoader functionality.

Third, prevent PG problems from reaching arbitrary file descriptors via IO::Handle. IO::Handle was shared wholesale into the safe compartment for lib/Rserve.pm's use, which means IO::Handle->new_from_fd($n, $mode) can be called directly from problem code. That constructor can wrap any file descriptor that happens to be open in the current process. In a long-lived worker process that handles many different requests over its lifetime, that can include things like a cached database connection, a shared log file, or even the Mojolicous rendering stream itself.

Sharing only the specific instance methods lib/Rserve.pm actually calls (print, flush, read, close) isn't enough on its own. Rserve.pm blesses its connection socket as an IO::Handle from code that runs nested inside a live render, and a fresh bless executing there does not resolve correctly against a stash that was merely populated with copies of those subs. It needs the class name, as seen from inside the compartment, to actually be an alias to a real package's symbol table.

So first the new WWSafe::share_package_as($name, $source_pkg) method aliases $name, as seen from inside the compartment, to a different real package's symbol table instead of whatever real package happens to be named $name outside it. The WeBWorK::PG::SafeIOHandle package then aliases only print/flush/ read/close from the real IO::Handle, and nothing else. WeBWorK::PG::Translator then aliases the compartment's view of IO::Handle to that narrow stand-in instead of sharing the real IO::Handle package.

Fourth, deny printf usage in the safe compartment. This is not denied with print. To deny it prtf must be added to the deny call. I don't know of a serious vulnerability here. The point is that print is denied, and so printf should also be denied. Otherwise you could do what print does with printf.

I will post PG problems that demonstrate the severity of the second and third issues above on slack.

…tment.

First enforce that the trusted Rserve host from the
`WeBWorK::PG::Environment` is used. The `Rserve` package gets this from
the secure `$WeBWorK::PG::IO::pg_envir` variable that is not exposed to
the safe compartment instead of from the `$main::Rserve` variable that
is. The `$main:::Rserve` variable can be modified by the problem author.

Second, prevent PG problems from bootstrapping arbitrary shared
libraries via `DynaLoader`.  The entire `DynaLoader` package was shared
into the safe compartment, so any .pg problem could call
`DynaLoader::dl_load_file` on any shared library already present on
disk, find and install one of its symbols as an XS sub via
`dl_find_symbol/dl_install_xsub`, and call it directly. Doing this with
`POSIX.so`, which ships with every Perl install, gives raw
POSIX::open/read/write/close, bypassing both the 'open' opcode
restriction and `WeBWorK::PG::IO`'s permitted_read_dir restriction
entirely.

The reason `DynaLoader` was shared is because it is in the `GD`
package's `@ISA` array.  The actual `DynaLoader` package is not needed
for PG's usage of `GD` via `WWPlot.pm`, but the `DynaLoader` symbol
can't simply be dropped as it is needed Perl's method resolution for
anything that walks the inheritance chain. So the new
`WWSafe::share_empty_package` creates an empty, disconnected stash under
the compartment's own root satisfying that structural requirement
without exposing any real DynaLoader functionality.

Third, prevent PG problems from reaching arbitrary file descriptors via
`IO::Handle`.  `IO::Handle` was shared wholesale into the safe
compartment for `lib/Rserve.pm`'s use, which means
`IO::Handle->new_from_fd($n, $mode)` can be called directly from problem
code. That constructor can wrap any file descriptor that happens to be
open in the current process. In a long-lived worker process that handles
many different requests over its lifetime, that can include things like
a cached database connection, a shared log file, or even the
`Mojolicous` rendering stream itself.

Sharing only the specific instance methods `lib/Rserve.pm` actually
calls (print, flush, read, close) isn't enough on its own. `Rserve.pm`
blesses its connection socket as an `IO::Handle` from code that runs
nested inside a live render, and a fresh `bless` executing there does
not resolve correctly against a stash that was merely populated with
copies of those subs.  It needs the class name, as seen from inside the
compartment, to actually be an alias to a real package's symbol table.

So first the new `WWSafe::share_package_as($name, $source_pkg)` method
aliases `$name`, as seen from inside the compartment, to a different
real package's symbol table instead of whatever real package happens to
be named `$name` outside it.  The `WeBWorK::PG::SafeIOHandle` package
then aliases only print/flush/ read/close from the real `IO::Handle`,
and nothing else. `WeBWorK::PG::Translator` then aliases the
compartment's view of `IO::Handle` to that narrow stand-in instead of
sharing the real `IO::Handle` package.

Fourth, deny `printf` usage in the safe compartment.  This is not denied
with `print`.  To deny it `prtf` must be added to the `deny` call. I
don't know of a serious vulnerability here.  The point is that `print`
is denied, and so `printf` should also be denied.  Otherwise you could
do what `print` does with `printf`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@drgrice1
drgrice1 force-pushed the vulnerability-fixes branch from 334645d to 60c2d24 Compare August 21, 2026 16:37
Comment thread lib/WeBWorK/PG/Translator.pm
@somiaj

somiaj commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

I have tested that it fixes the issues in the example problems you provided in slack. I don't have R setup on develop server so I cannot test if R still works. I did test that my old problems with GD images still work. Anything else we should test?

@drgrice1 drgrice1 changed the title Fix some vulenerabilitys related to what is shared to the safe compartment. Fix some vulnerabilities related to what is shared to the safe compartment. Aug 21, 2026
@drgrice1

Copy link
Copy Markdown
Member Author

Here is a docker Rserve build that you can use to test docker with. There is a README.md file that tells you how to use it. This is actually from a branch that I plan to add as a pull request at some point.

rserve-docker.zip

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.

2 participants