Skip to content

Commit 6f03ea8

Browse files
committed
Add SecurityManager support to block suspicious code #622
1 parent 058c682 commit 6f03ea8

File tree

9 files changed

+247
-9
lines changed

9 files changed

+247
-9
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
@@ -72,6 +72,7 @@ import kotlin.math.min
7272
import kotlinx.collections.immutable.persistentListOf
7373
import kotlinx.collections.immutable.persistentSetOf
7474
import org.utbot.framework.plugin.api.SYMBOLIC_NULL_ADDR
75+
import org.utbot.framework.plugin.api.UtSandboxFailure
7576
import soot.ArrayType
7677
import soot.BooleanType
7778
import soot.ByteType
@@ -88,6 +89,7 @@ import soot.SootClass
8889
import soot.SootField
8990
import soot.Type
9091
import soot.VoidType
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
@@ -106,6 +106,7 @@ import org.utbot.framework.plugin.api.UtNewInstanceInstrumentation
106106
import org.utbot.framework.plugin.api.UtNullModel
107107
import org.utbot.framework.plugin.api.UtPrimitiveModel
108108
import org.utbot.framework.plugin.api.UtReferenceModel
109+
import org.utbot.framework.plugin.api.UtSandboxFailure
109110
import org.utbot.framework.plugin.api.UtStaticMethodInstrumentation
110111
import org.utbot.framework.plugin.api.UtSymbolicExecution
111112
import org.utbot.framework.plugin.api.UtTimeoutException
@@ -141,6 +142,7 @@ import org.utbot.framework.plugin.api.util.wrapIfPrimitive
141142
import org.utbot.framework.util.isUnit
142143
import org.utbot.summary.SummarySentenceConstants.TAB
143144
import java.lang.reflect.InvocationTargetException
145+
import java.security.AccessControlException
144146
import java.lang.reflect.ParameterizedType
145147
import kotlin.reflect.jvm.javaType
146148

@@ -351,6 +353,11 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
351353
methodType = CRASH
352354
writeWarningAboutCrash()
353355
}
356+
is AccessControlException -> {
357+
methodType = CRASH
358+
writeWarningAboutFailureTest(exception)
359+
return
360+
}
354361
else -> {
355362
methodType = FAILING
356363
writeWarningAboutFailureTest(exception)
@@ -361,6 +368,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
361368
}
362369

363370
private fun shouldTestPassWithException(execution: UtExecution, exception: Throwable): Boolean {
371+
if (exception is AccessControlException) return false
364372
// tests with timeout or crash should be processed differently
365373
if (exception is TimeoutException || exception is ConcreteExecutionFailureException) return false
366374

@@ -1533,6 +1541,12 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
15331541
)
15341542
}
15351543

1544+
if (result is UtSandboxFailure) {
1545+
testFrameworkManager.disableTestMethod(
1546+
"Disabled due to sandbox"
1547+
)
1548+
}
1549+
15361550
val testMethod = buildTestMethod {
15371551
name = methodName
15381552
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
@@ -221,6 +223,9 @@ object UtExecutionInstrumentation : Instrumentation<UtConcreteExecutionResult> {
221223
if (exception is TimeoutException) {
222224
return UtTimeoutException(exception)
223225
}
226+
if (exception is AccessControlException) {
227+
return UtSandboxFailure(exception)
228+
}
224229
val instrs = traceHandler.computeInstructionList()
225230
val isNested = if (instrs.isEmpty()) {
226231
false

utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/InvokeInstrumentation.kt

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package org.utbot.instrumentation.instrumentation
22

33
import org.utbot.common.withAccessibility
44
import org.utbot.framework.plugin.api.util.signature
5+
import org.utbot.instrumentation.process.sandbox
56
import java.lang.reflect.Constructor
67
import java.lang.reflect.InvocationTargetException
78
import java.lang.reflect.Method
@@ -54,7 +55,7 @@ class InvokeInstrumentation : Instrumentation<Result<*>> {
5455
is Method ->
5556
withAccessibility {
5657
runCatching {
57-
invoke(thisObject, *realArgs.toTypedArray()).let {
58+
sandbox { invoke(thisObject, *realArgs.toTypedArray()) }.let {
5859
if (returnType != Void.TYPE) it else Unit
5960
} // invocation on method returning void will return null, so we replace it with Unit
6061
}
@@ -63,7 +64,7 @@ class InvokeInstrumentation : Instrumentation<Result<*>> {
6364
is Constructor<*> ->
6465
withAccessibility {
6566
runCatching {
66-
newInstance(*realArgs.toTypedArray())
67+
sandbox { newInstance(*realArgs.toTypedArray()) }
6768
}
6869
}
6970

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

+7
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) {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
@file:Suppress("JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE")
2+
3+
package org.utbot.instrumentation.process
4+
5+
import sun.security.provider.PolicyFile
6+
import java.net.URI
7+
import java.nio.file.Files
8+
import java.nio.file.Paths
9+
import java.security.AccessControlContext
10+
import java.security.AccessController
11+
import java.security.CodeSource
12+
import java.security.Permission
13+
import java.security.PermissionCollection
14+
import java.security.Permissions
15+
import java.security.Policy
16+
import java.security.PrivilegedAction
17+
import java.security.PrivilegedActionException
18+
import java.security.ProtectionDomain
19+
import java.security.cert.Certificate
20+
21+
internal fun permissions(block: SimplePolicy.() -> Unit) {
22+
val policy = Policy.getPolicy()
23+
if (policy !is SimplePolicy) {
24+
Policy.setPolicy(SimplePolicy(block))
25+
System.setSecurityManager(SecurityManager())
26+
} else {
27+
policy.block()
28+
}
29+
}
30+
31+
/**
32+
* Run [block] in sandbox mode.
33+
*
34+
* When running in sandbox by default only necessary to instrumentation permissions are enabled.
35+
* Other options are not enabled by default and rises [java.security.AccessControlException].
36+
*
37+
* To add new permissions create and/or edit file "{user.home}/.utbot/sandbox.policy".
38+
*
39+
* For example to enable property reading (`System.getProperty("user.home")`):
40+
*
41+
* ```
42+
* grant {
43+
* permission java.util.PropertyPermission "user.home", "read";
44+
* };
45+
* ```
46+
* Read more [about policy file and syntax](https://docs.oracle.com/javase/7/docs/technotes/guides/security/PolicyFiles.html#Examples)
47+
*/
48+
internal fun <T> sandbox(block: () -> T): T {
49+
val policyPath = Paths.get(System.getProperty("user.home"), ".utbot", "sandbox.policy")
50+
return sandbox(policyPath.toUri()) { block() }
51+
}
52+
53+
internal fun <T> sandbox(file: URI, block: () -> T): T {
54+
val path = Paths.get(file)
55+
val perms = mutableListOf<Permission>()
56+
val allCodeSource = CodeSource(null, emptyArray<Certificate>())
57+
if (Files.exists(path)) {
58+
val policyFile = PolicyFile(file.toURL())
59+
val collection = policyFile.getPermissions(allCodeSource)
60+
perms += collection.elements().toList()
61+
}
62+
return sandbox(perms, allCodeSource) { block() }
63+
}
64+
65+
internal fun <T> sandbox(permission: List<Permission>, cs: CodeSource, block: () -> T): T {
66+
val perms = permission.fold(Permissions()) { acc, p -> acc.add(p); acc }
67+
return sandbox(perms, cs) { block() }
68+
}
69+
70+
internal fun <T> sandbox(perms: PermissionCollection, cs: CodeSource, block: () -> T): T {
71+
val acc = AccessControlContext(arrayOf(ProtectionDomain(cs, perms)))
72+
return try {
73+
AccessController.doPrivileged(PrivilegedAction { block() }, acc)
74+
} catch (e: PrivilegedActionException) {
75+
throw e.exception
76+
}
77+
}
78+
79+
/**
80+
* This policy can add grant or denial rules for permissions.
81+
*
82+
* To add a grant permission use like this in any place:
83+
*
84+
* ```
85+
* permissions {
86+
* + java.security.PropertyPolicy("user.home", "read,write")
87+
* }
88+
* ```
89+
*
90+
* After first call [SecurityManager] is set with this policy
91+
*
92+
* To deny a permission:
93+
*
94+
* ```
95+
* permissions {
96+
* - java.security.PropertyPolicy("user.home", "read,write")
97+
* }
98+
* ```
99+
*
100+
* To delete all concrete permissions (if it was added before):
101+
*
102+
* ```
103+
* permissions {
104+
* ! java.security.PropertyPolicy("user.home", "read,write")
105+
* }
106+
* ```
107+
*
108+
* The last permission has priority. Enable all property read for "user.*", but forbid to read only "user.home":
109+
*
110+
* ```
111+
* permissions {
112+
* + java.security.PropertyPolicy("user.*", "read,write")
113+
* - java.security.PropertyPolicy("user.home", "read,write")
114+
* }
115+
* ```
116+
*/
117+
internal class SimplePolicy(init: SimplePolicy.() -> Unit = {}) : Policy() {
118+
sealed class Access(val permission: Permission) {
119+
class Allow(permission: Permission) : Access(permission)
120+
class Deny(permission: Permission) : Access(permission)
121+
}
122+
private var permissions = mutableListOf<Access>()
123+
124+
init { apply(init) }
125+
126+
operator fun Permission.unaryPlus() = permissions.add(Access.Allow(this))
127+
128+
operator fun Permission.unaryMinus() = permissions.add(Access.Deny(this))
129+
130+
operator fun Permission.not() = permissions.removeAll { it.permission == this }
131+
132+
override fun getPermissions(codesource: CodeSource) = UNSUPPORTED_EMPTY_COLLECTION!!
133+
override fun getPermissions(domain: ProtectionDomain) = UNSUPPORTED_EMPTY_COLLECTION!!
134+
override fun implies(domain: ProtectionDomain, permission: Permission): Boolean {
135+
// 0 means no info, < 0 is denied and > 0 is allowed
136+
val result = permissions.lastOrNull { it.permission.implies(permission) }?.let {
137+
when (it) {
138+
is Access.Allow -> 1
139+
is Access.Deny -> -1
140+
}
141+
} ?: 0
142+
return result > 0
143+
}
144+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package org.utbot.instrumentation.security
2+
3+
import org.junit.jupiter.api.Assertions.*
4+
import org.junit.jupiter.api.BeforeEach
5+
import org.junit.jupiter.api.Test
6+
import org.junit.jupiter.api.assertThrows
7+
import org.utbot.instrumentation.process.permissions
8+
import org.utbot.instrumentation.process.sandbox
9+
import java.lang.NullPointerException
10+
import java.security.AccessControlException
11+
import java.security.AllPermission
12+
import java.security.BasicPermission
13+
import java.security.CodeSource
14+
import java.security.Permission
15+
import java.security.cert.Certificate
16+
import java.util.PropertyPermission
17+
18+
class SecurityTest {
19+
20+
@BeforeEach
21+
fun init() {
22+
permissions {
23+
+AllPermission()
24+
}
25+
}
26+
27+
@Test
28+
fun `basic security works`() {
29+
sandbox {
30+
assertThrows<AccessControlException> {
31+
System.getProperty("any")
32+
}
33+
}
34+
}
35+
36+
@Test
37+
fun `basic permission works`() {
38+
sandbox(listOf(PropertyPermission("java.version", "read")), CodeSource(null, emptyArray<Certificate>())) {
39+
val result = System.getProperty("java.version")
40+
assertNotNull(result)
41+
assertThrows<AccessControlException> {
42+
System.setProperty("any_random_value_key", "random")
43+
}
44+
}
45+
}
46+
47+
@Test
48+
fun `null is ok`() {
49+
val empty = object : BasicPermission("*") {}
50+
val field = Permission::class.java.getDeclaredField("name")
51+
field.isAccessible = true
52+
field.set(empty, null)
53+
val collection = empty.newPermissionCollection()
54+
assertThrows<NullPointerException> {
55+
collection.implies(empty)
56+
}
57+
}
58+
59+
}

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)