Skip to content

Commit a50a87e

Browse files
jasontedornormanmaurer
authored andcommitted
Mark initialization of unsafe as privileged
Motiviation: Preparing platform dependent code for using unsafe requires executing privileged code. The privileged code for initializing unsafe is executed in a manner that would require all code leading up to the initialization to have the requisite permissions. Yet, in a restrictive environment (e.g., under a security policy that only grants the requisite permissions the Netty common jar but not to application code triggering the Netty initialization), then initializing unsafe will not succeed even if the security policy would otherwise permit it. Modifications: This commit marks the necessary blocks as privileged. This enables access to the necessary resources for initialization unsafe. The idea is that we are saying the Netty code is trusted, and as long as the Netty code has been granted the necessary permissions, then we will allow the caller access to these resources even though the caller itself might not have the requisite permissions. Result: Unsafe can be initialized in a restrictive security environment.
1 parent 9a3576f commit a50a87e

2 files changed

Lines changed: 198 additions & 59 deletions

File tree

common/src/main/java/io/netty/util/internal/PlatformDependent.java

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.jctools.queues.atomic.MpscLinkedAtomicQueue;
2828
import org.jctools.queues.atomic.SpscLinkedAtomicQueue;
2929
import org.jctools.util.Pow2;
30+
import org.jctools.util.UnsafeAccess;
3031

3132
import java.io.BufferedReader;
3233
import java.io.File;
@@ -588,6 +589,50 @@ public static <T> AtomicLongFieldUpdater<T> newAtomicLongFieldUpdater(
588589
return null;
589590
}
590591

592+
private static final class Mpsc {
593+
private static final boolean USE_MPSC_CHUNKED_ARRAY_QUEUE;
594+
595+
private Mpsc() {
596+
}
597+
598+
static {
599+
Object unsafe = null;
600+
if (hasUnsafe()) {
601+
// jctools goes through its own process of initializing unsafe; of
602+
// course, this requires permissions which might not be granted to calling code, so we
603+
// must mark this block as privileged too
604+
unsafe = AccessController.doPrivileged(new PrivilegedAction<Object>() {
605+
@Override
606+
public Object run() {
607+
// force JCTools to initialize unsafe
608+
return UnsafeAccess.UNSAFE;
609+
}
610+
});
611+
}
612+
613+
if (unsafe == null) {
614+
logger.debug("org.jctools-core.MpscChunkedArrayQueue: unavailable");
615+
USE_MPSC_CHUNKED_ARRAY_QUEUE = false;
616+
} else {
617+
logger.debug("org.jctools-core.MpscChunkedArrayQueue: available");
618+
USE_MPSC_CHUNKED_ARRAY_QUEUE = true;
619+
}
620+
}
621+
622+
static <T> Queue<T> newMpscQueue(final int maxCapacity) {
623+
if (USE_MPSC_CHUNKED_ARRAY_QUEUE) {
624+
// Calculate the max capacity which can not be bigger then MAX_ALLOWED_MPSC_CAPACITY.
625+
// This is forced by the MpscChunkedArrayQueue implementation as will try to round it
626+
// up to the next power of two and so will overflow otherwise.
627+
final int capacity =
628+
Math.max(Math.min(maxCapacity, MAX_ALLOWED_MPSC_CAPACITY), MIN_MAX_MPSC_CAPACITY);
629+
return new MpscChunkedArrayQueue<T>(MPSC_CHUNK_SIZE, capacity, true);
630+
} else {
631+
return new MpscLinkedAtomicQueue<T>();
632+
}
633+
}
634+
}
635+
591636
/**
592637
* Create a new {@link Queue} which is safe to use for multiple producers (different threads) and a single
593638
* consumer (one thread!).
@@ -600,14 +645,8 @@ public static <T> Queue<T> newMpscQueue() {
600645
* Create a new {@link Queue} which is safe to use for multiple producers (different threads) and a single
601646
* consumer (one thread!).
602647
*/
603-
public static <T> Queue<T> newMpscQueue(int maxCapacity) {
604-
return hasUnsafe() ?
605-
new MpscChunkedArrayQueue<T>(MPSC_CHUNK_SIZE,
606-
// Calculate the max capacity which can not be bigger then MAX_ALLOWED_MPSC_CAPACITY.
607-
// This is forced by the MpscChunkedArrayQueue implementation as will try to round it
608-
// up to the next power of two and so will overflow otherwise.
609-
Math.max(Math.min(maxCapacity, MAX_ALLOWED_MPSC_CAPACITY), MIN_MAX_MPSC_CAPACITY), true)
610-
: new MpscLinkedAtomicQueue<T>();
648+
public static <T> Queue<T> newMpscQueue(final int maxCapacity) {
649+
return Mpsc.newMpscQueue(maxCapacity);
611650
}
612651

613652
/**

common/src/main/java/io/netty/util/internal/PlatformDependent0.java

Lines changed: 151 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import java.lang.reflect.Constructor;
2323
import java.lang.reflect.Field;
24+
import java.lang.reflect.InvocationTargetException;
2425
import java.lang.reflect.Method;
2526
import java.nio.Buffer;
2627
import java.nio.ByteBuffer;
@@ -52,49 +53,98 @@ final class PlatformDependent0 {
5253
private static final boolean UNALIGNED;
5354

5455
static {
55-
ByteBuffer direct = ByteBuffer.allocateDirect(1);
56-
Field addressField;
57-
try {
58-
addressField = Buffer.class.getDeclaredField("address");
59-
addressField.setAccessible(true);
60-
if (addressField.getLong(direct) == 0) {
61-
// A direct buffer must have non-zero address.
62-
addressField = null;
56+
final ByteBuffer direct = ByteBuffer.allocateDirect(1);
57+
final Field addressField;
58+
// attempt to access field Buffer#address
59+
final Object maybeAddressField = AccessController.doPrivileged(new PrivilegedAction<Object>() {
60+
@Override
61+
public Object run() {
62+
try {
63+
final Field field = Buffer.class.getDeclaredField("address");
64+
field.setAccessible(true);
65+
// if direct really is a direct buffer, address will be non-zero
66+
if (field.getLong(direct) == 0) {
67+
return null;
68+
}
69+
return field;
70+
} catch (IllegalAccessException e) {
71+
return e;
72+
} catch (NoSuchFieldException e) {
73+
return e;
74+
} catch (SecurityException e) {
75+
return e;
76+
}
6377
}
64-
} catch (Throwable t) {
65-
// Failed to access the address field.
78+
});
79+
80+
if (maybeAddressField instanceof Field) {
81+
addressField = (Field) maybeAddressField;
82+
logger.debug("java.nio.Buffer.address: available");
83+
} else {
84+
logger.debug("java.nio.Buffer.address: unavailable", (Exception) maybeAddressField);
6685
addressField = null;
6786
}
6887

69-
logger.debug("java.nio.Buffer.address: {}", addressField != null? "available" : "unavailable");
70-
7188
Unsafe unsafe;
7289
if (addressField != null) {
73-
try {
74-
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
75-
unsafeField.setAccessible(true);
76-
unsafe = (Unsafe) unsafeField.get(null);
77-
logger.debug("sun.misc.Unsafe.theUnsafe: {}", unsafe != null ? "available" : "unavailable");
78-
79-
// Ensure the unsafe supports all necessary methods to work around the mistake in the latest OpenJDK.
80-
// https://github.com/netty/netty/issues/1061
81-
// http://www.mail-archive.com/jdk6-dev@openjdk.java.net/msg00698.html
82-
try {
83-
if (unsafe != null) {
84-
unsafe.getClass().getDeclaredMethod(
85-
"copyMemory", Object.class, long.class, Object.class, long.class, long.class);
86-
logger.debug("sun.misc.Unsafe.copyMemory: available");
90+
// attempt to access field Unsafe#theUnsafe
91+
final Object maybeUnsafe = AccessController.doPrivileged(new PrivilegedAction<Object>() {
92+
@Override
93+
public Object run() {
94+
try {
95+
final Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
96+
unsafeField.setAccessible(true);
97+
// the unsafe instance
98+
return unsafeField.get(null);
99+
} catch (NoSuchFieldException e) {
100+
return e;
101+
} catch (SecurityException e) {
102+
return e;
103+
} catch (IllegalAccessException e) {
104+
return e;
87105
}
88-
} catch (NoSuchMethodError t) {
89-
logger.debug("sun.misc.Unsafe.copyMemory: unavailable");
90-
throw t;
91-
} catch (NoSuchMethodException e) {
92-
logger.debug("sun.misc.Unsafe.copyMemory: unavailable");
93-
throw e;
94106
}
95-
} catch (Throwable cause) {
96-
// Unsafe.copyMemory(Object, long, Object, long, long) unavailable.
107+
});
108+
109+
// the conditional check here can not be replaced with checking that maybeUnsafe
110+
// is an instanceof Unsafe and reversing the if and else blocks; this is because an
111+
// instanceof check against Unsafe will trigger a class load and we might not have
112+
// the runtime permission accessClassInPackage.sun.misc
113+
if (maybeUnsafe instanceof Exception) {
97114
unsafe = null;
115+
logger.debug("sun.misc.Unsafe.theUnsafe: unavailable", (Exception) maybeUnsafe);
116+
} else {
117+
unsafe = (Unsafe) maybeUnsafe;
118+
logger.debug("sun.misc.Unsafe.theUnsafe: available");
119+
}
120+
121+
// ensure the unsafe supports all necessary methods to work around the mistake in the latest OpenJDK
122+
// https://github.com/netty/netty/issues/1061
123+
// http://www.mail-archive.com/jdk6-dev@openjdk.java.net/msg00698.html
124+
if (unsafe != null) {
125+
final Unsafe finalUnsafe = unsafe;
126+
final Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
127+
@Override
128+
public Object run() {
129+
try {
130+
finalUnsafe.getClass().getDeclaredMethod(
131+
"copyMemory", Object.class, long.class, Object.class, long.class, long.class);
132+
return null;
133+
} catch (NoSuchMethodException e) {
134+
return e;
135+
} catch (SecurityException e) {
136+
return e;
137+
}
138+
}
139+
});
140+
141+
if (maybeException == null) {
142+
logger.debug("sun.misc.Unsafe.copyMemory: available");
143+
} else {
144+
// Unsafe.copyMemory(Object, long, Object, long, long) unavailable.
145+
unsafe = null;
146+
logger.debug("sun.misc.Unsafe.copyMemory: unavailable", (Exception) maybeException);
147+
}
98148
}
99149
} else {
100150
// If we cannot access the address of a direct buffer, there's no point of using unsafe.
@@ -113,14 +163,43 @@ final class PlatformDependent0 {
113163
Constructor<?> directBufferConstructor;
114164
long address = -1;
115165
try {
116-
directBufferConstructor = direct.getClass().getDeclaredConstructor(long.class, int.class);
117-
directBufferConstructor.setAccessible(true);
118-
address = UNSAFE.allocateMemory(1);
119-
120-
// Try to use the constructor now
121-
directBufferConstructor.newInstance(address, 1);
122-
} catch (Throwable t) {
123-
directBufferConstructor = null;
166+
final Object maybeDirectBufferConstructor =
167+
AccessController.doPrivileged(new PrivilegedAction<Object>() {
168+
@Override
169+
public Object run() {
170+
try {
171+
final Constructor constructor =
172+
direct.getClass().getDeclaredConstructor(long.class, int.class);
173+
constructor.setAccessible(true);
174+
return constructor;
175+
} catch (NoSuchMethodException e) {
176+
return e;
177+
} catch (SecurityException e) {
178+
return e;
179+
}
180+
}
181+
});
182+
183+
if (maybeDirectBufferConstructor instanceof Constructor<?>) {
184+
address = UNSAFE.allocateMemory(1);
185+
// try to use the constructor now
186+
try {
187+
((Constructor) maybeDirectBufferConstructor).newInstance(address, 1);
188+
directBufferConstructor = (Constructor<?>) maybeDirectBufferConstructor;
189+
logger.debug("direct buffer constructor: available");
190+
} catch (InstantiationException e) {
191+
directBufferConstructor = null;
192+
} catch (IllegalAccessException e) {
193+
directBufferConstructor = null;
194+
} catch (InvocationTargetException e) {
195+
directBufferConstructor = null;
196+
}
197+
} else {
198+
logger.debug(
199+
"direct buffer constructor: unavailable",
200+
(Exception) maybeDirectBufferConstructor);
201+
directBufferConstructor = null;
202+
}
124203
} finally {
125204
if (address != -1) {
126205
UNSAFE.freeMemory(address);
@@ -130,25 +209,46 @@ final class PlatformDependent0 {
130209

131210
ADDRESS_FIELD_OFFSET = objectFieldOffset(addressField);
132211
boolean unaligned;
133-
try {
134-
Class<?> bitsClass = Class.forName("java.nio.Bits", false, ClassLoader.getSystemClassLoader());
135-
Method unalignedMethod = bitsClass.getDeclaredMethod("unaligned");
136-
unalignedMethod.setAccessible(true);
137-
unaligned = Boolean.TRUE.equals(unalignedMethod.invoke(null));
138-
} catch (Throwable t) {
139-
// We at least know x86 and x64 support unaligned access.
212+
Object maybeUnaligned = AccessController.doPrivileged(new PrivilegedAction<Object>() {
213+
@Override
214+
public Object run() {
215+
try {
216+
Class<?> bitsClass =
217+
Class.forName("java.nio.Bits", false, PlatformDependent.getSystemClassLoader());
218+
Method unalignedMethod = bitsClass.getDeclaredMethod("unaligned");
219+
unalignedMethod.setAccessible(true);
220+
return unalignedMethod.invoke(null);
221+
} catch (ClassNotFoundException e) {
222+
return e;
223+
} catch (NoSuchMethodException e) {
224+
return e;
225+
} catch (InvocationTargetException e) {
226+
return e;
227+
} catch (IllegalAccessException e) {
228+
return e;
229+
} catch (SecurityException e) {
230+
return e;
231+
}
232+
}
233+
});
234+
235+
if (maybeUnaligned instanceof Boolean) {
236+
unaligned = (Boolean) maybeUnaligned;
237+
logger.debug("java.nio.Bits.unaligned: available, {}", unaligned);
238+
} else {
140239
String arch = SystemPropertyUtil.get("os.arch", "");
141240
//noinspection DynamicRegexReplaceableByCompiledPattern
142241
unaligned = arch.matches("^(i[3-6]86|x86(_64)?|x64|amd64)$");
242+
Exception e = (Exception) maybeUnaligned;
243+
logger.debug("java.nio.Bits.unaligned: unavailable, " + unaligned, e);
143244
}
144245

145246
UNALIGNED = unaligned;
146-
logger.debug("java.nio.Bits.unaligned: {}", UNALIGNED);
147247
BYTE_ARRAY_BASE_OFFSET = arrayBaseOffset();
148248
}
149249

150250
logger.debug("java.nio.DirectByteBuffer.<init>(long, int): {}",
151-
DIRECT_BUFFER_CONSTRUCTOR != null? "available" : "unavailable");
251+
DIRECT_BUFFER_CONSTRUCTOR != null ? "available" : "unavailable");
152252

153253
freeDirectBuffer(direct);
154254
}

0 commit comments

Comments
 (0)