Skip to content

Commit a32a1de

Browse files
committed
Add SecurityManager support to block suspicious code #622
1 parent 625754c commit a32a1de

File tree

7 files changed

+193
-8
lines changed

7 files changed

+193
-8
lines changed

utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/UtExecutionResult.kt

+4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ data class UtOverflowFailure(
1717
override val exception: Throwable,
1818
) : UtExecutionFailure()
1919

20+
data class UtSandboxFailure(
21+
override val exception: Throwable
22+
) : UtExecutionFailure()
23+
2024
/**
2125
* unexpectedFail (when exceptions such as NPE, IOBE, etc. appear, but not thrown by a user, applies both for function under test and nested calls )
2226
* expectedCheckedThrow (when function under test or nested call explicitly says that checked exception could be thrown and throws it)

utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt

+7-6
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ import kotlin.math.max
7171
import kotlin.math.min
7272
import kotlinx.collections.immutable.persistentListOf
7373
import kotlinx.collections.immutable.persistentSetOf
74+
import org.utbot.framework.plugin.api.UtSandboxFailure
7475
import soot.ArrayType
7576
import soot.BooleanType
7677
import soot.ByteType
@@ -88,6 +89,7 @@ import soot.SootField
8889
import soot.Type
8990
import soot.VoidType
9091
import sun.java2d.cmm.lcms.LcmsServiceProvider
92+
import java.security.AccessControlException
9193

9294
// hack
9395
const val MAX_LIST_SIZE = 10
@@ -371,12 +373,11 @@ class Resolver(
371373
return if (explicit) {
372374
UtExplicitlyThrownException(exception, inNestedMethod)
373375
} else {
374-
// TODO SAT-1561
375-
val isOverflow = exception is ArithmeticException && exception.message?.contains("overflow") == true
376-
if (isOverflow) {
377-
UtOverflowFailure(exception)
378-
} else {
379-
UtImplicitlyThrownException(exception, inNestedMethod)
376+
when {
377+
// TODO SAT-1561
378+
exception is ArithmeticException && exception.message?.contains("overflow") == true -> UtOverflowFailure(exception)
379+
exception is AccessControlException -> UtSandboxFailure(exception)
380+
else -> UtImplicitlyThrownException(exception, inNestedMethod)
380381
}
381382
}
382383
}

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt

+14
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ import org.utbot.framework.plugin.api.UtNewInstanceInstrumentation
110110
import org.utbot.framework.plugin.api.UtNullModel
111111
import org.utbot.framework.plugin.api.UtPrimitiveModel
112112
import org.utbot.framework.plugin.api.UtReferenceModel
113+
import org.utbot.framework.plugin.api.UtSandboxFailure
113114
import org.utbot.framework.plugin.api.UtStaticMethodInstrumentation
114115
import org.utbot.framework.plugin.api.UtTimeoutException
115116
import org.utbot.framework.plugin.api.UtVoidModel
@@ -147,6 +148,7 @@ import org.utbot.framework.util.isUnit
147148
import org.utbot.summary.SummarySentenceConstants.TAB
148149
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
149150
import java.lang.reflect.InvocationTargetException
151+
import java.security.AccessControlException
150152

151153
private const val DEEP_EQUALS_MAX_DEPTH = 5 // TODO move it to plugin settings?
152154

@@ -342,6 +344,11 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
342344
methodType = CRASH
343345
writeWarningAboutCrash()
344346
}
347+
is AccessControlException -> {
348+
methodType = CRASH
349+
writeWarningAboutFailureTest(exception)
350+
return
351+
}
345352
else -> {
346353
methodType = FAILING
347354
writeWarningAboutFailureTest(exception)
@@ -352,6 +359,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
352359
}
353360

354361
private fun shouldTestPassWithException(execution: UtExecution, exception: Throwable): Boolean {
362+
if (exception is AccessControlException) return false
355363
// tests with timeout or crash should be processed differently
356364
if (exception is TimeoutException || exception is ConcreteExecutionFailureException) return false
357365

@@ -1621,6 +1629,12 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
16211629
)
16221630
}
16231631

1632+
if (result is UtSandboxFailure) {
1633+
testFrameworkManager.disableTestMethod(
1634+
"Disabled due to sandbox"
1635+
)
1636+
}
1637+
16241638
val testMethod = buildTestMethod {
16251639
name = methodName
16261640
parameters = params

utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtExecutionInstrumentation.kt

+5
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import org.utbot.framework.plugin.api.UtInstrumentation
2020
import org.utbot.framework.plugin.api.UtMethod
2121
import org.utbot.framework.plugin.api.UtModel
2222
import org.utbot.framework.plugin.api.UtNewInstanceInstrumentation
23+
import org.utbot.framework.plugin.api.UtSandboxFailure
2324
import org.utbot.framework.plugin.api.UtStaticMethodInstrumentation
2425
import org.utbot.framework.plugin.api.UtTimeoutException
2526
import org.utbot.framework.plugin.api.util.UtContext
@@ -37,6 +38,7 @@ import org.utbot.instrumentation.instrumentation.et.ExplicitThrowInstruction
3738
import org.utbot.instrumentation.instrumentation.et.TraceHandler
3839
import org.utbot.instrumentation.instrumentation.instrumenter.Instrumenter
3940
import org.utbot.instrumentation.instrumentation.mock.MockClassVisitor
41+
import java.security.AccessControlException
4042
import java.security.ProtectionDomain
4143
import java.util.IdentityHashMap
4244
import kotlin.reflect.jvm.javaMethod
@@ -212,6 +214,9 @@ object UtExecutionInstrumentation : Instrumentation<UtConcreteExecutionResult> {
212214
if (exception is TimeoutException) {
213215
return UtTimeoutException(exception)
214216
}
217+
if (exception is AccessControlException) {
218+
return UtSandboxFailure(exception)
219+
}
215220
val instrs = traceHandler.computeInstructionList()
216221
val isNested = if (instrs.isEmpty()) {
217222
false

utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/ChildProcess.kt

+8-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import java.io.File
1111
import java.io.OutputStream
1212
import java.io.PrintStream
1313
import java.net.URLClassLoader
14+
import java.security.AllPermission
1415
import java.time.LocalDateTime
1516
import java.time.format.DateTimeFormatter
1617
import kotlin.system.exitProcess
@@ -51,6 +52,12 @@ private val kryoHelper: KryoHelper = KryoHelper(System.`in`, System.`out`)
5152
* It should be compiled into separate jar file (child_process.jar) and be run with an agent (agent.jar) option.
5253
*/
5354
fun main() {
55+
permissions {
56+
// Enable all permissions for instrumentation.
57+
// SecurityKt.sandbox() is used to restrict these permissions.
58+
+ AllPermission()
59+
}
60+
5461
// We don't want user code to litter the standard output, so we redirect it.
5562
val tmpStream = PrintStream(object : OutputStream() {
5663
override fun write(b: Int) {}
@@ -127,7 +134,7 @@ private fun loop(instrumentation: Instrumentation<*>) {
127134
}
128135
System.err.println("warmup finished in $time ms")
129136
}
130-
is Protocol.InvokeMethodCommand -> {
137+
is Protocol.InvokeMethodCommand -> sandbox {
131138
val resultCmd = try {
132139
val clazz = HandlerClassesLoader.loadClass(cmd.className)
133140
val res = instrumentation.invoke(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package org.utbot.instrumentation.process
2+
3+
import sun.security.provider.PolicyFile
4+
import java.lang.reflect.ReflectPermission
5+
import java.net.URI
6+
import java.nio.file.Files
7+
import java.nio.file.Paths
8+
import java.security.AccessControlContext
9+
import java.security.AccessController
10+
import java.security.CodeSource
11+
import java.security.Permission
12+
import java.security.PermissionCollection
13+
import java.security.Permissions
14+
import java.security.Policy
15+
import java.security.PrivilegedAction
16+
import java.security.ProtectionDomain
17+
import java.security.cert.Certificate
18+
import java.util.PropertyPermission
19+
20+
internal fun permissions(block: SimplePolicy.() -> Unit) {
21+
val policy = Policy.getPolicy()
22+
if (policy !is SimplePolicy) {
23+
Policy.setPolicy(SimplePolicy(block))
24+
System.setSecurityManager(SecurityManager())
25+
} else {
26+
policy.block()
27+
}
28+
}
29+
30+
/**
31+
* Run [block] in sandbox mode.
32+
*
33+
* When running in sandbox by default only necessary to instrumentation permissions are enabled.
34+
* Other options are not enabled by default and rises [java.security.AccessControlException].
35+
*
36+
* To add new permissions create and/or edit file "{user.home}/.utbot/sandbox.policy".
37+
*
38+
* For example to enable property reading (`System.getProperty("user.home")`):
39+
*
40+
* ```
41+
* grant {
42+
* permission java.util.PropertyPermission "user.home", "read";
43+
* };
44+
* ```
45+
* Read more [about policy file and syntax](https://docs.oracle.com/javase/7/docs/technotes/guides/security/PolicyFiles.html#Examples)
46+
*/
47+
internal fun <T> sandbox(block: () -> T): T {
48+
val policyPath = Paths.get(System.getProperty("user.home"), ".utbot", "sandbox.policy")
49+
return sandbox(policyPath.toUri()) { block() }
50+
}
51+
52+
internal fun <T> sandbox(file: URI, block: () -> T): T {
53+
val path = Paths.get(file)
54+
val perms = mutableListOf<Permission>(
55+
RuntimePermission("accessDeclaredMembers"),
56+
RuntimePermission("getProtectionDomain"),
57+
RuntimePermission("accessClassInPackage.*"),
58+
RuntimePermission("getClassLoader"),
59+
RuntimePermission("reflectionFactoryAccess"),
60+
PropertyPermission("org.mockito.internal.*", "read,write"),
61+
ReflectPermission("*"),
62+
)
63+
if (Files.exists(path)) {
64+
val policyFile = PolicyFile(file.toURL())
65+
val collection = policyFile.getPermissions(CodeSource(null, emptyArray<Certificate>()))
66+
perms += collection.elements().toList()
67+
}
68+
return sandbox(perms) { block() }
69+
}
70+
71+
internal fun <T> sandbox(permission: List<Permission>, block: () -> T): T {
72+
val perms = permission.fold(Permissions()) { acc, p -> acc.add(p); acc }
73+
return sandbox(perms) { block() }
74+
}
75+
76+
internal fun <T> sandbox(perms: PermissionCollection, block: () -> T): T {
77+
val acc = AccessControlContext(arrayOf(ProtectionDomain(null, perms)))
78+
return AccessController.doPrivileged(
79+
PrivilegedAction {
80+
block()
81+
},
82+
acc
83+
)
84+
}
85+
86+
/**
87+
* This policy can add grant or denial rules for permissions.
88+
*
89+
* To add a grant permission use like this in any place:
90+
*
91+
* ```
92+
* permissions {
93+
* + java.security.PropertyPolicy("user.home", "read,write")
94+
* }
95+
* ```
96+
*
97+
* After first call [SecurityManager] is set with this policy
98+
*
99+
* To deny a permission:
100+
*
101+
* ```
102+
* permissions {
103+
* - java.security.PropertyPolicy("user.home", "read,write")
104+
* }
105+
* ```
106+
*
107+
* To delete all concrete permissions (if it was added before):
108+
*
109+
* ```
110+
* permissions {
111+
* ! java.security.PropertyPolicy("user.home", "read,write")
112+
* }
113+
* ```
114+
*
115+
* The last permission has priority. Enable all property read for "user.*", but forbid to read only "user.home":
116+
*
117+
* ```
118+
* permissions {
119+
* + java.security.PropertyPolicy("user.*", "read,write")
120+
* - java.security.PropertyPolicy("user.home", "read,write")
121+
* }
122+
* ```
123+
*/
124+
internal class SimplePolicy(init: SimplePolicy.() -> Unit = {}) : Policy() {
125+
sealed class Access(val permission: Permission) {
126+
class Allow(permission: Permission) : Access(permission)
127+
class Deny(permission: Permission) : Access(permission)
128+
}
129+
private var permissions = mutableListOf<Access>()
130+
131+
init { apply(init) }
132+
133+
operator fun Permission.unaryPlus() = permissions.add(Access.Allow(this))
134+
135+
operator fun Permission.unaryMinus() = permissions.add(Access.Deny(this))
136+
137+
operator fun Permission.not() = permissions.removeAll { it.permission == this }
138+
139+
override fun getPermissions(codesource: CodeSource) = UNSUPPORTED_EMPTY_COLLECTION!!
140+
override fun getPermissions(domain: ProtectionDomain) = UNSUPPORTED_EMPTY_COLLECTION!!
141+
override fun implies(domain: ProtectionDomain, permission: Permission): Boolean {
142+
// 0 means no info, < 0 is denied and > 0 is allowed
143+
val result = permissions.lastOrNull { it.permission.implies(permission) }?.let {
144+
when (it) {
145+
is Access.Allow -> 1
146+
is Access.Deny -> -1
147+
}
148+
} ?: 0
149+
return result > 0
150+
}
151+
}

utbot-summary/src/main/kotlin/org/utbot/summary/TagGenerator.kt

+4-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import org.utbot.framework.plugin.api.UtExplicitlyThrownException
99
import org.utbot.framework.plugin.api.UtImplicitlyThrownException
1010
import org.utbot.framework.plugin.api.UtOverflowFailure
1111
import org.utbot.framework.plugin.api.UtMethodTestSet
12+
import org.utbot.framework.plugin.api.UtSandboxFailure
1213
import org.utbot.framework.plugin.api.UtTimeoutException
1314
import org.utbot.framework.plugin.api.util.isCheckedException
1415
import org.utbot.summary.UtSummarySettings.MIN_NUMBER_OF_EXECUTIONS_FOR_CLUSTERING
@@ -140,7 +141,8 @@ enum class ClusterKind {
140141
EXPLICITLY_THROWN_UNCHECKED_EXCEPTIONS,
141142
OVERFLOWS,
142143
TIMEOUTS,
143-
CRASH_SUITE;
144+
CRASH_SUITE,
145+
SECURITY;
144146

145147
val displayName: String get() = toString().replace('_', ' ')
146148
}
@@ -152,6 +154,7 @@ private fun UtExecutionResult.clusterKind() = when (this) {
152154
is UtOverflowFailure -> ClusterKind.OVERFLOWS
153155
is UtTimeoutException -> ClusterKind.TIMEOUTS
154156
is UtConcreteExecutionFailure -> ClusterKind.CRASH_SUITE
157+
is UtSandboxFailure -> ClusterKind.SECURITY
155158
}
156159

157160
/**

0 commit comments

Comments
 (0)