Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Quick stab and limited profile runtime
This was quickest and minimal needed to work to get a profile which
can load a Ruby runtime.  It is not really correct and is meant as a
starting point.

There are some obvious problems with profile as it is defined.  Basic
fundamental things like primal Exception types need to be loaded.  Allowing
people to omit them is a footgun.

There is obvious issues with ommitting jruby/kernel.  Some of that is
required but at the same time it likely hits profile excluded types.

Regex is a major source of DOS so it must be excludable but at the same
time I suspect we call it many places internally.

Methods which use types which are excludable (like Regexp) probably should
be aware of excluded types and not bind.  This would be a MAJOR amount of
work but it would fit into idea of a dependency graph.  Likewise we could
make a much smarter type declaration where something like:

```java
        return defineClass(context, "Integer", Numeric, NOT_ALLOCATABLE_ALLOCATOR).
                reifiedClass(RubyInteger.class).
                kindOf(new RubyModule.JavaClassKindOf(RubyInteger.class)).
                classIndex(ClassIndex.INTEGER).
                defineMethods(context, RubyInteger.class).
                tap(c-> c.singletonClass(context).undefMethods(context, "new"));
```

(we are only pretending with this example as Integer is too primal to Ruby
to be allowed to be excluded) we would want to add something to this which
would know that it cannot defineClass unless "Numeric" has been defined.

```java
        return  requires("Numeric", "Fixnum", "Bignum").
                defineClass(context, "Integer", Numeric, NOT_ALLOCATABLE_ALLOCATOR).
                reifiedClass(RubyInteger.class).
                kindOf(new RubyModule.JavaClassKindOf(RubyInteger.class)).
                classIndex(ClassIndex.INTEGER).
                defineMethods(context, RubyInteger.class).
                tap(c-> c.singletonClass(context).undefMethods(context, "new"));
```

This would end up simplifying the smattering of if's in Ruby to just declare
these dependencies in the setup method for the type.
  • Loading branch information
enebo committed Jul 5, 2025
commit d3bde22d8f0d21f25cbe91867291b8567b0fe57b
38 changes: 25 additions & 13 deletions core/src/main/java/org/jruby/Ruby.java
Original file line number Diff line number Diff line change
Expand Up @@ -507,13 +507,21 @@ private Ruby(RubyInstanceConfig config) {
initExceptions(context);

// Thread library utilities
mutexClass = Mutex.setup(context, threadClass, objectClass);
conditionVariableClass = ConditionVariable.setup(context, threadClass, objectClass);
queueClass = Queue.setup(context, threadClass, objectClass);
closedQueueError = Queue.setupError(context, queueClass, stopIteration, objectClass);
sizedQueueClass = SizedQueue.setup(context, threadClass, queueClass, objectClass);

fiberClass = new ThreadFiberLibrary().createFiberClass(context, objectClass);
if (profile.allowClass("Thread")) {
mutexClass = Mutex.setup(context, threadClass, objectClass);
conditionVariableClass = ConditionVariable.setup(context, threadClass, objectClass);
queueClass = Queue.setup(context, threadClass, objectClass);
closedQueueError = Queue.setupError(context, queueClass, stopIteration, objectClass);
sizedQueueClass = SizedQueue.setup(context, threadClass, queueClass, objectClass);
fiberClass = new ThreadFiberLibrary().createFiberClass(context, objectClass);
} else {
mutexClass = null;
conditionVariableClass = null;
queueClass = null;
closedQueueError = null;
sizedQueueClass = null;
fiberClass = null;
}

dataClass = RubyData.createDataClass(context, objectClass);

Expand Down Expand Up @@ -1676,12 +1684,12 @@ private void initExceptions(ThreadContext context) {
ifAllowed("KeyError", (ruby) -> keyError = RubyKeyError.define(context, indexError));
ifAllowed("DomainError", (ruby) -> mathDomainError = RubyDomainError.define(context, argumentError, mathModule));

setRegexpTimeoutError(regexpClass.defineClassUnder(context, "TimeoutError", getRegexpError(), RubyRegexpError::new));
ifAllowed("Regex", (ruby) -> setRegexpTimeoutError(regexpClass.defineClassUnder(context, "TimeoutError", getRegexpError(), RubyRegexpError::new)));

RubyClass runtimeError = this.runtimeError;
ObjectAllocator runtimeErrorAllocator = runtimeError.getAllocator();

if (Options.FIBER_SCHEDULER.load()) {
if (profile.allowClass("Thread") && Options.FIBER_SCHEDULER.load()) {
ObjectAllocator runtimeErrorAllocator = runtimeError.getAllocator();
bufferLockedError = ioBufferClass.defineClassUnder(context, "LockedError", runtimeError, runtimeErrorAllocator);
bufferAllocationError = ioBufferClass.defineClassUnder(context, "AllocationError", runtimeError, runtimeErrorAllocator);
bufferAccessError = ioBufferClass.defineClassUnder(context, "AccessError", runtimeError, runtimeErrorAllocator);
Expand Down Expand Up @@ -1764,7 +1772,7 @@ private void initJavaSupport(ThreadContext context) {
// if we can't use reflection, 'jruby' and 'java' won't work; no load.
boolean reflectionWorks = doesReflectionWork();

if (reflectionWorks) {
if (reflectionWorks && profile.allowLoad("java")) {
new Java().load(context.runtime, false);
new JRubyUtilLibrary().load(context.runtime, false);

Expand All @@ -1775,15 +1783,19 @@ private void initJavaSupport(ThreadContext context) {

private void initRubyKernel() {
// load Ruby parts of core
loadService.loadFromClassLoader(getClassLoader(), "jruby/kernel.rb", false);
if (profile.allowLoad("jruby/kernel")) {
loadService.loadFromClassLoader(getClassLoader(), "jruby/kernel.rb", false);
}
}

private void initRubyPreludes() {
// We cannot load any .rb and debug new parser features
if (RubyInstanceConfig.DEBUG_PARSER) return;

// load Ruby parts of core
loadService.loadFromClassLoader(getClassLoader(), "jruby/preludes.rb", false);
if (profile.allowLoad("jruby/preludes")) {
loadService.loadFromClassLoader(getClassLoader(), "jruby/preludes.rb", false);
}
}

public IRManager getIRManager() {
Expand Down
4 changes: 3 additions & 1 deletion core/src/main/java/org/jruby/RubyGlobal.java
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,9 @@ public static RubyHash createGlobalsAndENV(ThreadContext context, GlobalVariable
runtime.defineVariable(new LastlineGlobalVariable(runtime, "$_"), FRAME);
runtime.defineVariable(new LastExitStatusVariable(runtime, "$?"), THREAD);

runtime.defineVariable(new ErrorInfoGlobalVariable(runtime, "$!", context.nil), THREAD);
if (runtime.getProfile().allowClass("Thread")) {
runtime.defineVariable(new ErrorInfoGlobalVariable(runtime, "$!", context.nil), THREAD);
}
runtime.defineVariable(new NonEffectiveGlobalVariable(runtime, "$=", context.fals), GLOBAL);

if(instanceConfig.getInputFieldSeparator() == null) {
Expand Down
44 changes: 44 additions & 0 deletions core/src/test/java/org/jruby/javasupport/JavaEmbedUtilsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import org.jruby.Profile;
import org.jruby.RubyFixnum;
import org.jruby.java.proxies.ConcreteJavaProxy;
import org.jruby.java.proxies.JavaProxy;
import org.jruby.runtime.ThreadContext;
Expand Down Expand Up @@ -64,6 +66,48 @@ public void testAddClassloaderToLoadPathOnTCCL() throws Exception {
assertEquals(result, "uri:" + url);
}

class CustomProfile implements Profile {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe call it RestrictedProfile to match the name the test has?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NinekoTheCat sure. I can change this name in the test. Ultimately this class may get very small as we continue.

private List classAllow = List.of("String", "Fixnum", "Integer", "Numeric", "Hash", "Array",
"Thread", "ThreadGroup", "RubyError", "StopIteration", "LoadError", "ArgumentError", "Encoding",
"EncodingError", "StandardError", "Exception");

@Override
public boolean allowBuiltin(String name) {
return false;
}

@Override
public boolean allowClass(String name) {
return classAllow.contains(name);
}

@Override
public boolean allowModule(String name) {
return false;
}

@Override
public boolean allowLoad(String name) {
return false;
}

@Override
public boolean allowRequire(String name) {
return false;
}
}

@Test
public void testRestrictedProfile() throws Exception {
RubyInstanceConfig config = new RubyInstanceConfig();
config.setDisableGems(true);
config.setProfile(new CustomProfile());

Ruby runtime = Ruby.newInstance(config);
assertEquals(20L, ((RubyFixnum) runtime.evalScriptlet("def double(a); a * 2; end; double(10)")).getValue());
//Ruby runtime = JavaEmbedUtils.initialize(EMPTY, config);
}

@Test
public void testAddClassloaderToLoadPathOnNoneTCCL() throws Exception {
RubyInstanceConfig config = new RubyInstanceConfig();
Expand Down
Loading