8010319: Implementation of JEP 181: Nest-Based Access Control

Co-authored-by: Alex Buckley <alex.buckley@oracle.com>
Co-authored-by: Maurizio Mimadamore <maurizio.mimadamore@oracle.com>
Co-authored-by: Mandy Chung <mandy.chung@oracle.com>
Co-authored-by: Tobias Hartmann <tobias.hartmann@oracle.com>
Co-authored-by: Vlaidmir Ivanov <vladimir.x.ivanov@oracle.com>
Co-authored-by: Karen Kinnear <karen.kinnear@oracle.com>
Co-authored-by: Vladimir Kozlov <vladimir.kozlov@oracle.com>
Co-authored-by: John Rose <john.r.rose@oracle.com>
Co-authored-by: Daniel Smith <daniel.smith@oracle.com>
Co-authored-by: Serguei Spitsyn <serguei.spitsyn@oracle.com>
Co-authored-by: Kumar Srinivasan <kumardotsrinivasan@gmail.com>
Co-authored-by: Boris Ulasevich <boris.ulasevich@bell-sw.com>
Reviewed-by: alanb, psandoz, mchung, coleenp, acorn, mcimadamore, forax, jlahoda, sspitsyn, abuckley
This commit is contained in:
David Holmes 2018-06-23 01:32:41 -04:00
parent 6e0bd36f42
commit 95bf19563b
259 changed files with 21354 additions and 890 deletions

View file

@ -145,6 +145,198 @@ static inline bool is_class_loader(const Symbol* class_name,
return false;
}
// called to verify that k is a member of this nest
bool InstanceKlass::has_nest_member(InstanceKlass* k, TRAPS) const {
if (_nest_members == NULL || _nest_members == Universe::the_empty_short_array()) {
if (log_is_enabled(Trace, class, nestmates)) {
ResourceMark rm(THREAD);
log_trace(class, nestmates)("Checked nest membership of %s in non-nest-host class %s",
k->external_name(), this->external_name());
}
return false;
}
if (log_is_enabled(Trace, class, nestmates)) {
ResourceMark rm(THREAD);
log_trace(class, nestmates)("Checking nest membership of %s in %s",
k->external_name(), this->external_name());
}
// Check names first and if they match then check actual klass. This avoids
// resolving anything unnecessarily.
for (int i = 0; i < _nest_members->length(); i++) {
int cp_index = _nest_members->at(i);
Symbol* name = _constants->klass_name_at(cp_index);
if (name == k->name()) {
log_trace(class, nestmates)("- Found it at nest_members[%d] => cp[%d]", i, cp_index);
// names match so check actual klass - this may trigger class loading if
// it doesn't match (but that should be impossible)
Klass* k2 = _constants->klass_at(cp_index, CHECK_false);
if (k2 == k) {
log_trace(class, nestmates)("- class is listed as a nest member");
return true;
} else {
// same name but different klass!
log_trace(class, nestmates)(" - klass comparison failed!");
// can't have different classes for the same name, so we're done
return false;
}
}
}
log_trace(class, nestmates)("- class is NOT a nest member!");
return false;
}
// Return nest-host class, resolving, validating and saving it if needed.
// In cases where this is called from a thread that can not do classloading
// (such as a native JIT thread) then we simply return NULL, which in turn
// causes the access check to return false. Such code will retry the access
// from a more suitable environment later.
InstanceKlass* InstanceKlass::nest_host(Symbol* validationException, TRAPS) {
InstanceKlass* nest_host_k = _nest_host;
if (nest_host_k == NULL) {
// need to resolve and save our nest-host class. This could be attempted
// concurrently but as the result is idempotent and we don't use the class
// then we do not need any synchronization beyond what is implicitly used
// during class loading.
if (_nest_host_index != 0) { // we have a real nest_host
// Before trying to resolve check if we're in a suitable context
if (!THREAD->can_call_java() && !_constants->tag_at(_nest_host_index).is_klass()) {
if (log_is_enabled(Trace, class, nestmates)) {
ResourceMark rm(THREAD);
log_trace(class, nestmates)("Rejected resolution of nest-host of %s in unsuitable thread",
this->external_name());
}
return NULL;
}
if (log_is_enabled(Trace, class, nestmates)) {
ResourceMark rm(THREAD);
log_trace(class, nestmates)("Resolving nest-host of %s using cp entry for %s",
this->external_name(),
_constants->klass_name_at(_nest_host_index)->as_C_string());
}
Klass* k = _constants->klass_at(_nest_host_index, THREAD);
if (HAS_PENDING_EXCEPTION) {
Handle exc_h = Handle(THREAD, PENDING_EXCEPTION);
if (exc_h->is_a(SystemDictionary::NoClassDefFoundError_klass())) {
// throw a new CDNFE with the original as its cause, and a clear msg
ResourceMark rm(THREAD);
char buf[200];
CLEAR_PENDING_EXCEPTION;
jio_snprintf(buf, sizeof(buf),
"Unable to load nest-host class (%s) of %s",
_constants->klass_name_at(_nest_host_index)->as_C_string(),
this->external_name());
log_trace(class, nestmates)("%s - NoClassDefFoundError", buf);
THROW_MSG_CAUSE_NULL(vmSymbols::java_lang_NoClassDefFoundError(), buf, exc_h);
}
// All other exceptions pass through (OOME, StackOverflowError, LinkageErrors etc).
return NULL;
}
// A valid nest-host is an instance class in the current package that lists this
// class as a nest member. If any of these conditions are not met we post the
// requested exception type (if any) and return NULL
const char* error = NULL;
// JVMS 5.4.4 indicates package check comes first
if (is_same_class_package(k)) {
// Now check actual membership. We can't be a member if our "host" is
// not an instance class.
if (k->is_instance_klass()) {
nest_host_k = InstanceKlass::cast(k);
bool is_member = nest_host_k->has_nest_member(this, CHECK_NULL);
if (is_member) {
// save resolved nest-host value
_nest_host = nest_host_k;
if (log_is_enabled(Trace, class, nestmates)) {
ResourceMark rm(THREAD);
log_trace(class, nestmates)("Resolved nest-host of %s to %s",
this->external_name(), k->external_name());
}
return nest_host_k;
}
}
error = "current type is not listed as a nest member";
} else {
error = "types are in different packages";
}
if (log_is_enabled(Trace, class, nestmates)) {
ResourceMark rm(THREAD);
log_trace(class, nestmates)("Type %s is not a nest member of resolved type %s: %s",
this->external_name(),
k->external_name(),
error);
}
if (validationException != NULL) {
ResourceMark rm(THREAD);
Exceptions::fthrow(THREAD_AND_LOCATION,
validationException,
"Type %s is not a nest member of %s: %s",
this->external_name(),
k->external_name(),
error
);
}
return NULL;
} else {
if (log_is_enabled(Trace, class, nestmates)) {
ResourceMark rm(THREAD);
log_trace(class, nestmates)("Type %s is not part of a nest: setting nest-host to self",
this->external_name());
}
// save resolved nest-host value
return (_nest_host = this);
}
}
return nest_host_k;
}
// check if 'this' and k are nestmates (same nest_host), or k is our nest_host,
// or we are k's nest_host - all of which is covered by comparing the two
// resolved_nest_hosts
bool InstanceKlass::has_nestmate_access_to(InstanceKlass* k, TRAPS) {
assert(this != k, "this should be handled by higher-level code");
// Per JVMS 5.4.4 we first resolve and validate the current class, then
// the target class k. Resolution exceptions will be passed on by upper
// layers. IncompatibleClassChangeErrors from membership validation failures
// will also be passed through.
Symbol* icce = vmSymbols::java_lang_IncompatibleClassChangeError();
InstanceKlass* cur_host = nest_host(icce, CHECK_false);
if (cur_host == NULL) {
return false;
}
Klass* k_nest_host = k->nest_host(icce, CHECK_false);
if (k_nest_host == NULL) {
return false;
}
bool access = (cur_host == k_nest_host);
if (log_is_enabled(Trace, class, nestmates)) {
ResourceMark rm(THREAD);
log_trace(class, nestmates)("Class %s does %shave nestmate access to %s",
this->external_name(),
access ? "" : "NOT ",
k->external_name());
}
return access;
}
InstanceKlass* InstanceKlass::allocate_instance_klass(const ClassFileParser& parser, TRAPS) {
const int size = InstanceKlass::size(parser.vtable_size(),
parser.itable_size(),
@ -169,13 +361,11 @@ InstanceKlass* InstanceKlass::allocate_instance_klass(const ClassFileParser& par
else if (is_class_loader(class_name, parser)) {
// class loader
ik = new (loader_data, size, THREAD) InstanceClassLoaderKlass(parser);
}
else {
} else {
// normal
ik = new (loader_data, size, THREAD) InstanceKlass(parser, InstanceKlass::_misc_kind_other);
}
}
else {
} else {
// reference
ik = new (loader_data, size, THREAD) InstanceRefKlass(parser);
}
@ -215,7 +405,10 @@ InstanceKlass::InstanceKlass(const ClassFileParser& parser, unsigned kind) :
_static_field_size(parser.static_field_size()),
_nonstatic_oop_map_size(nonstatic_oop_map_size(parser.total_oop_map_count())),
_itable_len(parser.itable_size()),
_reference_type(parser.reference_type()) {
_reference_type(parser.reference_type()),
_nest_members(NULL),
_nest_host_index(0),
_nest_host(NULL) {
set_vtable_length(parser.vtable_size());
set_kind(kind);
set_access_flags(parser.access_flags());
@ -359,6 +552,13 @@ void InstanceKlass::deallocate_contents(ClassLoaderData* loader_data) {
}
set_inner_classes(NULL);
if (nest_members() != NULL &&
nest_members() != Universe::the_empty_short_array() &&
!nest_members()->is_shared()) {
MetadataFactory::free_array<jushort>(loader_data, nest_members());
}
set_nest_members(NULL);
// We should deallocate the Annotations instance if it's not in shared spaces.
if (annotations() != NULL && !annotations()->is_shared()) {
MetadataFactory::free_metadata(loader_data, annotations());
@ -643,7 +843,6 @@ bool InstanceKlass::link_class_impl(bool throw_verifyerror, TRAPS) {
return true;
}
// Rewrite the byte codes of all of the methods of a class.
// The rewriter must be called exactly once. Rewriting must happen after
// verification but before the first method of the class is executed.
@ -1359,13 +1558,14 @@ Method* InstanceKlass::find_method_impl(const Symbol* name,
// and skips over static methods
Method* InstanceKlass::find_instance_method(const Array<Method*>* methods,
const Symbol* name,
const Symbol* signature) {
const Symbol* signature,
PrivateLookupMode private_mode) {
Method* const meth = InstanceKlass::find_method_impl(methods,
name,
signature,
find_overpass,
skip_static,
find_private);
private_mode);
assert(((meth == NULL) || !meth->is_static()),
"find_instance_method should have skipped statics");
return meth;
@ -1373,8 +1573,10 @@ Method* InstanceKlass::find_instance_method(const Array<Method*>* methods,
// find_instance_method looks up the name/signature in the local methods array
// and skips over static methods
Method* InstanceKlass::find_instance_method(const Symbol* name, const Symbol* signature) const {
return InstanceKlass::find_instance_method(methods(), name, signature);
Method* InstanceKlass::find_instance_method(const Symbol* name,
const Symbol* signature,
PrivateLookupMode private_mode) const {
return InstanceKlass::find_instance_method(methods(), name, signature, private_mode);
}
// Find looks up the name/signature in the local methods array
@ -1475,7 +1677,7 @@ int InstanceKlass::find_method_index(const Array<Method*>* methods,
// Do linear search to find matching signature. First, quick check
// for common case, ignoring overpasses if requested.
if (method_matches(m, signature, skipping_overpass, skipping_static, skipping_private)) {
return hit;
return hit;
}
// search downwards through overloaded methods
@ -1531,10 +1733,12 @@ int InstanceKlass::find_method_by_name(const Array<Method*>* methods,
}
// uncached_lookup_method searches both the local class methods array and all
// superclasses methods arrays, skipping any overpass methods in superclasses.
// superclasses methods arrays, skipping any overpass methods in superclasses,
// and possibly skipping private methods.
Method* InstanceKlass::uncached_lookup_method(const Symbol* name,
const Symbol* signature,
OverpassLookupMode overpass_mode) const {
OverpassLookupMode overpass_mode,
PrivateLookupMode private_mode) const {
OverpassLookupMode overpass_local_mode = overpass_mode;
const Klass* klass = this;
while (klass != NULL) {
@ -1542,7 +1746,7 @@ Method* InstanceKlass::uncached_lookup_method(const Symbol* name,
signature,
overpass_local_mode,
find_static,
find_private);
private_mode);
if (method != NULL) {
return method;
}
@ -2044,6 +2248,8 @@ void InstanceKlass::metaspace_pointers_do(MetaspaceClosure* it) {
}
}
}
it->push(&_nest_members);
}
void InstanceKlass::remove_unshareable_info() {
@ -2087,10 +2293,12 @@ void InstanceKlass::remove_unshareable_info() {
guarantee(_previous_versions == NULL, "must be");
#endif
_init_thread = NULL;
_methods_jmethod_ids = NULL;
_jni_ids = NULL;
_oop_map_cache = NULL;
_init_thread = NULL;
_methods_jmethod_ids = NULL;
_jni_ids = NULL;
_oop_map_cache = NULL;
// clear _nest_host to ensure re-load at runtime
_nest_host = NULL;
}
void InstanceKlass::remove_java_mirror() {
@ -2946,6 +3154,7 @@ void InstanceKlass::print_on(outputStream* st) const {
st->cr();
}
st->print(BULLET"inner classes: "); inner_classes()->print_value_on(st); st->cr();
st->print(BULLET"nest members: "); nest_members()->print_value_on(st); st->cr();
st->print(BULLET"java mirror: "); java_mirror()->print_value_on(st); st->cr();
st->print(BULLET"vtable length %d (start addr: " INTPTR_FORMAT ")", vtable_length(), p2i(start_of_vtable())); st->cr();
if (vtable_length() > 0 && (Verbose || WizardMode)) print_vtable(start_of_vtable(), vtable_length(), st);
@ -3188,6 +3397,7 @@ void InstanceKlass::collect_statistics(KlassSizeStats *sz) const {
n += (sz->_transitive_interfaces_bytes = sz->count_array(transitive_interfaces()));
n += (sz->_fields_bytes = sz->count_array(fields()));
n += (sz->_inner_classes_bytes = sz->count_array(inner_classes()));
n += (sz->_nest_members_bytes = sz->count_array(nest_members()));
sz->_ro_bytes += n;
const ConstantPool* cp = constants();