Fix some vulnerabilities related to what is shared to the safe compartment. - #1522
Open
drgrice1 wants to merge 1 commit into
Open
Fix some vulnerabilities related to what is shared to the safe compartment.#1522drgrice1 wants to merge 1 commit into
drgrice1 wants to merge 1 commit into
Conversation
drgrice1
force-pushed
the
vulnerability-fixes
branch
from
August 21, 2026 16:36
365221e to
334645d
Compare
…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
force-pushed
the
vulnerability-fixes
branch
from
August 21, 2026 16:37
334645d to
60c2d24
Compare
somiaj
reviewed
Aug 21, 2026
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? |
somiaj
approved these changes
Aug 21, 2026
Member
Author
|
Here is a docker Rserve build that you can use to test docker with. There is a |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First enforce that the trusted Rserve host from the
WeBWorK::PG::Environmentis used. TheRservepackage gets this from the secure$WeBWorK::PG::IO::pg_envirvariable that is not exposed to the safe compartment instead of from the$main::Rservevariable that is. The$main:::Rservevariable can be modified by the problem author.Second, prevent PG problems from bootstrapping arbitrary shared libraries via
DynaLoader. The entireDynaLoaderpackage was shared into the safe compartment, so any .pg problem could callDynaLoader::dl_load_fileon any shared library already present on disk, find and install one of its symbols as an XS sub viadl_find_symbol/dl_install_xsub, and call it directly. Doing this withPOSIX.so, which ships with every Perl install, gives raw POSIX::open/read/write/close, bypassing both the 'open' opcode restriction andWeBWorK::PG::IO's permitted_read_dir restriction entirely.The reason
DynaLoaderwas shared is because it is in theGDpackage's@ISAarray. The actualDynaLoaderpackage is not needed for PG's usage ofGDviaWWPlot.pm, but theDynaLoadersymbol can't simply be dropped as it is needed Perl's method resolution for anything that walks the inheritance chain. So the newWWSafe::share_empty_packagecreates 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::Handlewas shared wholesale into the safe compartment forlib/Rserve.pm's use, which meansIO::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 theMojolicousrendering stream itself.Sharing only the specific instance methods
lib/Rserve.pmactually calls (print, flush, read, close) isn't enough on its own.Rserve.pmblesses its connection socket as anIO::Handlefrom code that runs nested inside a live render, and a freshblessexecuting 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$nameoutside it. TheWeBWorK::PG::SafeIOHandlepackage then aliases only print/flush/ read/close from the realIO::Handle, and nothing else.WeBWorK::PG::Translatorthen aliases the compartment's view ofIO::Handleto that narrow stand-in instead of sharing the realIO::Handlepackage.Fourth, deny
printfusage in the safe compartment. This is not denied withprint. To deny itprtfmust be added to thedenycall. I don't know of a serious vulnerability here. The point is thatprintis denied, and soprintfshould also be denied. Otherwise you could do whatprintdoes withprintf.I will post PG problems that demonstrate the severity of the second and third issues above on slack.