Merge remote-tracking branch 'origin/GP-1_ghidragon_test_fixes_4_22_26'

This commit is contained in:
ghidra1
2026-05-04 14:53:08 -04:00
45 changed files with 1976 additions and 688 deletions
@@ -20,7 +20,9 @@ import java.util.List;
import generic.jar.ResourceFile;
import ghidra.framework.*;
import ghidra.framework.remote.GhidraObjectInputFilter;
import ghidra.net.DefaultTrustManagerFactory;
import ghidra.util.Msg;
import ghidra.util.classfinder.ClassSearcher;
public class HeadlessBSimApplicationConfiguration extends ApplicationConfiguration {
@@ -29,11 +31,20 @@ public class HeadlessBSimApplicationConfiguration extends ApplicationConfigurati
protected void initializeApplication() {
super.initializeApplication();
// Locate certs if found (must be done before module initialization)
locateCACertsFile();
try {
// Install client-side deserialization filters (data/*.serial.filter)
GhidraObjectInputFilter.configureClientSerialFilter();
monitor.setMessage("Performing module initialization...");
performModuleInitialization();
// Locate certs if found (must be done before module initialization)
locateCACertsFile();
monitor.setMessage("Performing module initialization...");
performModuleInitialization();
}
catch (Throwable t) {
Msg.error(this, "Ghidra encountered a severe error during initialization", t);
System.exit(-1);
}
monitor.setMessage("Done initializing");
}
@@ -21,6 +21,7 @@ import java.util.List;
import generic.jar.ResourceFile;
import ghidra.GhidraClassLoader;
import ghidra.framework.preferences.Preferences;
import ghidra.framework.remote.GhidraObjectInputFilter;
import ghidra.net.DefaultTrustManagerFactory;
import ghidra.util.Msg;
import ghidra.util.classfinder.ClassSearcher;
@@ -31,19 +32,28 @@ public class HeadlessGhidraApplicationConfiguration extends ApplicationConfigura
@Override
protected void initializeApplication() {
super.initializeApplication();
try {
// Install client-side deserialization filters (data/*.serial.filter)
GhidraObjectInputFilter.configureClientSerialFilter();
// Now that preferences are accessible, finalize classpath by adding user plugin paths.
// This must be done before class searching.
addUserJarAndPluginPathsToClasspath();
// Now that preferences are accessible, finalize classpath by adding user plugin paths.
// This must be done before class searching.
addUserJarAndPluginPathsToClasspath();
monitor.setMessage("Performing class searching...");
performClassSearching();
monitor.setMessage("Performing class searching...");
performClassSearching();
// Locate certs if found (must be done before module initialization)
locateCACertsFile();
// Locate certs if found (must be done before module initialization)
locateCACertsFile();
monitor.setMessage("Performing module initialization...");
performModuleInitialization();
monitor.setMessage("Performing module initialization...");
performModuleInitialization();
}
catch (Throwable t) {
Msg.error(this, "Ghidra encountered a severe error during initialization", t);
System.exit(-1);
}
monitor.setMessage("Done initializing");
}
+58 -13
View File
@@ -1,22 +1,67 @@
# Ghidra Server serialization filter patterns
# See java.io.ObjectInputFilter.Config#createFilter(String)
#
# This file establishes allowed and disallowed inbound class de-serialization
# rules for the Ghidra Server. If not specifically allowed or disallowed a
# de-serialized class will be subject to an internal filter which allows all
# primitive and primitive array classes while rejecting all other classes.
#
java.base/java.lang.*;
java.base/java.security.**;
java.base/javax.security.**;
java.base/sun.security.**;
java.base/java.util.**;
ghidra.framework.remote.GhidraPrincipal;
ghidra.framework.remote.AnonymousCallback;
ghidra.framework.remote.SSHSignatureCallback;
ghidra.framework.remote.SignatureCallback;
ghidra.framework.remote.User;
ghidra.framework.remote.User[];
[Lghidra.framework.remote.User;
ghidra.framework.store.CheckoutType;
java.lang.Object;
java.lang.String;
java.lang.Class;
java.lang.Enum;
java.util.Collections$SynchronizedSet;
java.util.Collections$SynchronizedCollection;
java.util.LinkedList;
java.security.cert.Certificate$CertificateRep;
java.security.cert.X509Certificate;
[Ljava.security.cert.X509Certificate;
javax.security.auth.Subject;
javax.security.auth.Subject$*;
javax.security.auth.x500.X500Principal;
[Ljavax.security.auth.x500.X500Principal;
javax.security.auth.callback.Callback;
[Ljavax.security.auth.callback.Callback;
javax.security.auth.callback.NameCallback;
javax.security.auth.callback.PasswordCallback;
# TODO: Server Remote API needs to be revised serialize certificates in PEM form.
# This affects ghidra.framework.remote.SignatureCallback implementation.
sun.security.x509.X509CertImpl;
# RMI related classes
java.rmi.server.UID;
java.rmi.server.ObjID;
java.rmi.dgc.DGC;
java.rmi.dgc.Lease;
java.rmi.dgc.VMID;
java.rmi.RemoteException;
java.rmi.AccessException;
java.rmi.ConnectException;
java.rmi.ConnectIOException;
java.rmi.MarshalException;
java.rmi.UnmarshalException;
java.rmi.NoSuchObjectException;
java.rmi.ServerException;
java.rmi.ServerRuntimeException;
java.rmi.UnexpectedException;
java.rmi.UnknownHostException;
# The following additional entries may be required if using JMX over RMI for remote
# profiling (e.g., VisualVM).
#javax.management.remote.rmi.RMIConnectionImpl_Stub;
#javax.management.remote.rmi.RMIServerImpl_Stub;
#java.rmi.server.RemoteObjectInvocationHandler;
#sun.rmi.server.UnicastRef;
#sun.rmi.transport.LiveRef;
@@ -25,6 +25,7 @@ import ghidra.framework.remote.*;
import ghidra.framework.store.FileSystem;
import ghidra.framework.store.FileSystemListener;
import ghidra.framework.store.local.*;
import ghidra.server.remote.RemoteLoggingUtil;
import ghidra.server.remote.RepositoryHandleImpl;
import ghidra.server.store.RepositoryFile;
import ghidra.server.store.RepositoryFolder;
@@ -327,9 +328,8 @@ public class Repository implements FileSystemListener, RepositoryLogger {
* defined to the repository user manager.
* @param currentUser user performing request
* @return list of user names.
* @throws IOException if an IO error occurs
*/
public String[] getServerUserList(String currentUser) throws IOException {
public String[] getServerUserList(String currentUser) {
if (UserManager.ANONYMOUS_USERNAME.equals(currentUser)) {
return new String[0];
}
@@ -779,7 +779,7 @@ public class Repository implements FileSystemListener, RepositoryLogger {
RepositoryFolder folder = getFolder(null, parentPath, false);
if (folder == null || folder.getFolder(folderName) == null) {
RepositoryManager.log(name, RepositoryFolder.makePathname(parentPath, folderName),
"ERROR! folder not found", null);
"ERROR! folder not found");
return;
}
}
@@ -788,7 +788,7 @@ public class Repository implements FileSystemListener, RepositoryLogger {
}
catch (IOException e) {
RepositoryManager.log(name, RepositoryFolder.makePathname(parentPath, folderName),
"ERROR! " + e.getMessage(), null);
"ERROR! " + e.getMessage());
}
RepositoryChangeEvent event = new RepositoryChangeEvent(
@@ -808,7 +808,7 @@ public class Repository implements FileSystemListener, RepositoryLogger {
RepositoryFolder folder = getFolder(null, parentPath, false);
if (folder == null || folder.getFile(itemName) == null) {
RepositoryManager.log(name, RepositoryFolder.makePathname(parentPath, itemName),
"file not found", null);
"file not found");
return;
}
}
@@ -817,7 +817,7 @@ public class Repository implements FileSystemListener, RepositoryLogger {
}
catch (IOException e) {
RepositoryManager.log(name, RepositoryFolder.makePathname(parentPath, itemName),
"ERROR! " + e.getMessage(), null);
"ERROR! " + e.getMessage());
}
RepositoryChangeEvent event = new RepositoryChangeEvent(
@@ -844,7 +844,7 @@ public class Repository implements FileSystemListener, RepositoryLogger {
throw new AssertException();
}
catch (IOException e) {
RepositoryManager.log(name, parentPath, "ERROR! " + e.getMessage(), null);
RepositoryManager.log(name, parentPath, "ERROR! " + e.getMessage());
}
RepositoryChangeEvent event = new RepositoryChangeEvent(
@@ -933,11 +933,10 @@ public class Repository implements FileSystemListener, RepositoryLogger {
}
catch (IOException e) {
RepositoryManager.log(name, RepositoryFolder.makePathname(parentPath, itemName),
"ERROR! " + e.getMessage(), null);
"ERROR! " + e.getMessage());
}
if (syncErr) {
RepositoryManager.log(name, null, "ERROR! Repository instance may be out-of-sync",
null);
RepositoryManager.log(name, null, "ERROR! Repository instance may be out-of-sync");
return;
}
@@ -956,7 +955,7 @@ public class Repository implements FileSystemListener, RepositoryLogger {
@Override
public void log(String path, String msg, String user) {
RepositoryManager.log(name, path, msg, user);
RemoteLoggingUtil.log(name, path, msg, user, false);
}
static boolean markRepositoryForIndexMigration(File serverDir, String repositoryName,
@@ -119,10 +119,12 @@ public class RepositoryManager {
* given name
* @throws UserAccessException if the user does not exist in
* the list of known users for this manager
* @throws UserAccessException if the currentUser does not have
* ability to create a repository
* @throws IOException if there was an error creating the repository
*/
public synchronized Repository createRepository(String currentUser, String name)
throws IOException, DuplicateFileException {
throws UserAccessException, IOException, DuplicateFileException {
if (isAnonymousUser(currentUser)) {
throw new UserAccessException("Anonymous user not permitted to create repository");
@@ -147,7 +149,6 @@ public class RepositoryManager {
}
Repository rep = new Repository(this, currentUser, f, name);
log(name, null, "repository created", currentUser);
repositoryMap.put(name, rep);
return rep;
}
@@ -187,9 +188,11 @@ public class RepositoryManager {
* Delete a specified repository.
* @param currentUser current user
* @param name repository name
* @throws UserAccessException if currentUser does not have Admin priviledge
* @throws IOException if error occurs while removing repository
*/
public synchronized void deleteRepository(String currentUser, String name) throws IOException {
public synchronized void deleteRepository(String currentUser, String name)
throws UserAccessException, IOException {
if (isAnonymousUser(currentUser)) {
throw new UserAccessException("Anonymous user not permitted to delete repository");
@@ -433,23 +436,13 @@ public class RepositoryManager {
return host;
}
public static void log(String repositoryName, String path, String msg, String user) {
static void log(String repositoryName, String path, String msg) {
StringBuffer buf = new StringBuffer();
if (repositoryName != null) {
buf.append("[");
buf.append(repositoryName);
buf.append("]");
}
String host = RepositoryManager.getRMIClient();
String userStr = user;
if (userStr != null) {
if (host != null) {
userStr += "@" + host;
}
}
else {
userStr = host;
}
if (path != null) {
buf.append(path);
}
@@ -457,11 +450,6 @@ public class RepositoryManager {
buf.append(": ");
}
buf.append(msg);
if (userStr != null) {
buf.append(" (");
buf.append(userStr);
buf.append(")");
}
log.info(buf.toString());
}
@@ -70,9 +70,6 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
private static final String TLS_SERVER_PROTOCOLS_PROPERTY = "ghidra.tls.server.protocols";
private static final String TLS_ENABLED_CIPHERS_PROPERTY = "jdk.tls.server.cipherSuites";
private static final String SERIALIZATION_FILTER_DISABLED_PROPERTY =
"ghidra.server.serialization.filter.disabled";
private static SslRMIServerSocketFactory serverSocketFactory;
private static SslRMIClientSocketFactory clientSocketFactory;
private static InetAddress bindAddress;
@@ -210,9 +207,6 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
GhidraServer.server = this;
// Establish serialization filter to address deserialization vulnerabity concerns.
setGlobalSerializationFilter();
// Start block stream server - use RMI serverSocketFactory
blockStreamServer = BlockStreamServer.getBlockStreamServer();
ServerSocket streamServerSocket;
@@ -244,7 +238,7 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
}
catch (Throwable t) {
log.error("Failed to generate authentication callbacks", t);
throw new RemoteException("Failed to generate authentication callbacks", t);
throw new RemoteException("Failed to generate authentication callbacks");
}
}
@@ -263,7 +257,7 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
@Override
public RemoteRepositoryServerHandle getRepositoryServer(Subject user, Callback[] authCallbacks)
throws LoginException, RemoteException {
throws FailedLoginException, RemoteException {
System.gc();
@@ -278,29 +272,29 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
anonymousAuthModule.anonymousAccessRequested(authCallbacks)) {
username = UserManager.ANONYMOUS_USERNAME;
anonymousAccess = true;
RepositoryManager.log(null, null, "Anonymous access allowed", principal.getName());
RemoteLoggingUtil.log("Anonymous access allowed", principal.getName());
}
else if (authModule != null) {
NameCallback nameCb =
AuthenticationModule.getFirstCallbackOfType(NameCallback.class, authCallbacks);
if (nameCb != null) {
if (!authModule.isNameCallbackAllowed()) {
RepositoryManager.log(null, null,
RemoteLoggingUtil.log(
"Illegal authentication callback: NameCallback not permitted", username);
throw new LoginException("Illegal authentication callback");
throw new FailedLoginException("Illegal authentication callback");
}
String name = nameCb.getName();
if (name == null) {
RepositoryManager.log(null, null,
RemoteLoggingUtil.log(
"Illegal authentication callback: NameCallback must specify login name",
username);
throw new LoginException("Illegal authentication callback");
throw new FailedLoginException("Illegal authentication callback");
}
username = name;
}
}
RepositoryManager.log(null, null, "Repository server handle requested", username);
RemoteLoggingUtil.log("Repository server handle requested", username);
boolean supportPasswordChange = false;
if (!anonymousAccess) {
@@ -310,10 +304,11 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
username =
sshAuthModule.authenticate(mgr.getUserManager(), user, authCallbacks);
}
catch (LoginException e) {
RepositoryManager.log(null, null,
"SSH Authentication failed (" + e.getMessage() + ")", username);
throw e;
catch (FailedLoginException e) {
RemoteLoggingUtil.log("SSH Authentication failed (" + e.getMessage() + ")",
username);
// Create new exceptions so we don't leak config info to the client.
throw new FailedLoginException("SSH authentication failed");
}
}
else if (authModule != null) {
@@ -325,14 +320,13 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
if (autoProvisionAuthedUsers) {
try {
mgr.getUserManager().addUser(username);
RepositoryManager.log(null, null,
RemoteLoggingUtil.log(
"User '" + username + "' successful auto provision",
username);
}
catch (DuplicateNameException | IOException e) {
RepositoryManager.log(
null, null, "User '" + username +
"' auto provision failed. Cause: " + e.getMessage(),
RemoteLoggingUtil.log("User '" + username +
"' auto provision failed. Cause: " + e.getMessage(),
username);
throw new LoginException(
"Error when trying to auto provision successfully authenticated user: " +
@@ -340,7 +334,7 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
}
}
else {
RepositoryManager.log(null, null,
RemoteLoggingUtil.log(
"User successfully authenticated, but does not exist in Ghidra user list: " +
username,
null);
@@ -351,36 +345,42 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
throw new LoginException("Unknown user: " + username);
}
}
RepositoryManager.log(null, null, "User '" + username + "' authenticated",
RemoteLoggingUtil.log("User '" + username + "' authenticated",
principal.getName());
}
}
catch (LoginException e) {
RepositoryManager.log(null, null, "Login failed (" + e.getMessage() + ")",
RemoteLoggingUtil.log("Login failed (" + e.getMessage() + ")",
username);
// Create new exceptions so we don't leak config info to the client.
if (e instanceof FailedLoginException) {
throw new FailedLoginException("User authentication failed");
}
throw new LoginException("User login system failure");
throw new FailedLoginException("Authentication failed");
}
if (authModule instanceof PasswordFileAuthenticationModule) {
supportPasswordChange = true;
}
}
else if (!mgr.getUserManager().isValidUser(username)) {
FailedLoginException e = new FailedLoginException("Unknown user: " + username);
RepositoryManager.log(null, null, "Login failed (" + e.getMessage() + ")",
RemoteLoggingUtil.log("Login failed (Unknown user: " + username + ")",
username);
throw e;
// Create new exceptions so we don't leak config info to the client.
throw new FailedLoginException("Authentication failed");
}
}
if (anonymousAccess) {
RepositoryManager.log(null, null, "Anonymous server access granted", null);
RemoteLoggingUtil.log("Anonymous server access granted", null);
}
return new RepositoryServerHandleImpl(username, anonymousAccess, mgr,
supportPasswordChange);
try {
return new RepositoryServerHandleImpl(username, anonymousAccess, mgr,
supportPasswordChange);
}
catch (RemoteException e) {
RemoteLoggingUtil.log(
"Failed to instantiate RepositoryServerHandleImpl: " + e.getMessage(),
username);
e.printStackTrace();
throw new RemoteException("Remote server handle error (see server log)");
}
}
/**
@@ -550,8 +550,9 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
configuration.setInitializeLogging(false);
Application.initializeApplication(layout, configuration);
}
catch (IOException e) {
catch (Throwable t) {
System.err.println("Failed to initialize the application!");
t.printStackTrace();
System.exit(-1);
}
@@ -729,11 +730,22 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
File serverLogFile = new File(serverRoot, "server.log");
Application.initializeLogging(serverLogFile, serverLogFile);
log = LogManager.getLogger(GhidraServer.class); // init log *after* initializing log system
// Establish serialization filter to address deserialization vulnerabity concerns
try {
ResourceFile serialFilterFile = Application.getModuleDataFile(SERIAL_FILTER_FILE);
GhidraObjectInputFilter.configureServerSerialFilter(serialFilterFile,
() -> RepositoryManager.getRMIClient());
}
catch (Throwable t) {
log.fatal("Failed to initialize serialization filter", t);
System.exit(-1);
}
// In the absence of module initialization - we must invoke directly
DefaultSSLContextInitializer.initialize();
log = LogManager.getLogger(GhidraServer.class); // init log *after* initializing log system
ServerPortFactory.setBasePort(basePort);
Runtime.getRuntime().addShutdownHook(new Thread((Runnable) () -> {
@@ -806,7 +818,6 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
// localhost.getCanonicalHostName() + ":" + classSvrPort + "/";
// System.setProperty(RMI_CODEBASE_PROPERTY, codeBaseProp);
log.info(" RMI Registry port: " + ServerPortFactory.getRMIRegistryPort());
log.info(" RMI SSL port: " + ServerPortFactory.getRMISSLPort());
log.info(" Block Stream port: " + ServerPortFactory.getStreamPort());
@@ -865,11 +876,6 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
log.info("Registered Ghidra Server.");
}
catch (IOException e) {
e.printStackTrace();
log.error(e.getMessage());
System.exit(-1);
}
catch (Throwable t) {
log.fatal("Server error: " + t.getMessage(), t);
System.exit(-1);
@@ -898,130 +904,12 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
server.dispose();
}
public static RMIServerSocketFactory getRMIServerSocketFactory() {
static RMIServerSocketFactory getRMIServerSocketFactory() {
return serverSocketFactory;
}
public static RMIClientSocketFactory getRMIClientSocketFactory() {
static RMIClientSocketFactory getRMIClientSocketFactory() {
return clientSocketFactory;
}
private static void setGlobalSerializationFilter() throws IOException {
// NOTE: Serialization filter may need to be disabled when profiling with VisualVM
String disabledStr = System.getProperty(SERIALIZATION_FILTER_DISABLED_PROPERTY);
if (Boolean.valueOf(disabledStr)) {
return;
}
ObjectInputFilter patternFilter = readSerialFilterPatternFile();
ObjectInputFilter filter = new ObjectInputFilter() {
@Override
public Status checkInput(FilterInfo info) {
Class<?> clazz = info.serialClass();
// Give serial filter patterns first shot
Status status = patternFilter.checkInput(info);
if (status != Status.UNDECIDED) {
if (status == Status.REJECTED) {
return serialReject(info, "failed by serial.filter pattern");
}
return status;
}
if (clazz == null) {
return Status.ALLOWED;
}
Class<?> componentType = clazz.getComponentType();
if (componentType != null && componentType.isPrimitive()) {
return Status.ALLOWED; // allow all primitive arrays
}
return serialReject(info, "not allowed");
}
private Status serialReject(FilterInfo info, String reason) {
String clientHost = RepositoryManager.getRMIClient();
StringBuilder buf = new StringBuilder();
buf.append("Rejected class serialization");
if (clientHost != null) {
buf.append(" from ");
buf.append(clientHost);
}
buf.append("(");
buf.append(reason);
buf.append(")");
Class<?> serialClass = info.serialClass();
if (serialClass != null) {
buf.append(": ");
buf.append(serialClass.getCanonicalName());
buf.append(" ");
if (serialClass.getComponentType() != null) {
buf.append("(");
buf.append("array-length=");
buf.append(info.arrayLength());
buf.append(")");
}
}
log.error(buf.toString());
return Status.REJECTED;
}
};
// Install global serial class filter
ObjectInputFilter.Config.setSerialFilter(filter);
}
/**
* Read serial.filter file content removing any comments and newlines and generate
* corresponding {@link ObjectInputFilter}. See {@link java.io.ObjectInputFilter.Config#createFilter(String)}
* for filter syntax.
* @return serial filter content
* @throws IOException if file error occurs
*/
private static ObjectInputFilter readSerialFilterPatternFile() throws IOException {
File serialFilterFile = Application.getModuleDataFile(SERIAL_FILTER_FILE).getFile(false);
if (serialFilterFile == null) {
// jar mode not supported
throw new FileNotFoundException(SERIAL_FILTER_FILE + " not found");
}
try {
StringBuilder buf = new StringBuilder();
try (FileReader fr = new FileReader(serialFilterFile);
BufferedReader r = new BufferedReader(fr)) {
for (String line = r.readLine(); line != null; line = r.readLine()) {
int ix = line.indexOf('#');
if (ix >= 0) {
// strip comment
line = line.substring(0, ix);
}
line = line.trim();
if (line.length() == 0) {
continue;
}
if (!line.endsWith(";")) {
throw new IllegalArgumentException(
"all filter statements must end with `;`");
}
if (line.length() != 0) {
buf.append(line);
}
}
}
return ObjectInputFilter.Config.createFilter(buf.toString());
}
catch (Exception e) {
throw new IOException("Failed to parse " + SERIAL_FILTER_FILE, e);
}
}
}
@@ -4,16 +4,16 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package db.buffers;
package ghidra.server.remote;
import java.io.IOException;
import java.rmi.NoSuchObjectException;
@@ -22,9 +22,9 @@ import java.rmi.server.UnicastRemoteObject;
import java.rmi.server.Unreferenced;
import java.util.*;
import db.buffers.*;
import ghidra.framework.remote.RemoteRepositoryHandle;
import ghidra.server.RepositoryManager;
import ghidra.server.remote.*;
import ghidra.server.stream.*;
/**
@@ -44,6 +44,7 @@ public class RemoteBufferFileImpl extends UnicastRemoteObject
new HashMap<>();
protected final RepositoryHandleImpl owner;
protected final String user;
protected final String associatedFilePath;
private final String clientHost;
@@ -56,7 +57,7 @@ public class RemoteBufferFileImpl extends UnicastRemoteObject
* @param bufferFile buffer file
* @param owner owner object to which this instance should be associated.
* @param associatedFilePath repository path of file item associated with this buffer file
* @throws RemoteException
* @throws RemoteException if failed to instantiate remote object
*/
public RemoteBufferFileImpl(LocalBufferFile bufferFile, RepositoryHandleImpl owner,
String associatedFilePath) throws RemoteException {
@@ -68,6 +69,7 @@ public class RemoteBufferFileImpl extends UnicastRemoteObject
if (owner == null || associatedFilePath == null) {
throw new IllegalArgumentException("Missing one or more required arguments");
}
this.user = owner.getUser().getName();
this.clientHost = RepositoryManager.getRMIClient();
addInstance(this);
}
@@ -155,7 +157,7 @@ public class RemoteBufferFileImpl extends UnicastRemoteObject
}
/**
* Return user name@host associated with open file handle.
* {@return username@host associated with open file handle}
*/
public String getUserClient() {
if (clientHost != null) {
@@ -166,7 +168,9 @@ public class RemoteBufferFileImpl extends UnicastRemoteObject
/**
* Returns list of users with open handles associated with the specified filePath.
* @param filePath file path
* @param repoName repository name
* @param filePath repository file path
* @return users with open file handles
*/
public static String[] getOpenFileUsers(String repoName, String filePath) {
String filePathKey = getFilePathKey(repoName, filePath);
@@ -218,63 +222,75 @@ public class RemoteBufferFileImpl extends UnicastRemoteObject
}
@Override
public int getParameter(String name) throws NoSuchElementException, IOException {
public int getParameter(String name) throws NoSuchElementException {
// NOTE: NoSuchElementException will get encapsulated within a RemoteException
return bufferFile.getParameter(name);
}
@Override
public void setParameter(String name, int value) throws IOException {
public void setParameter(String name, int value) {
bufferFile.setParameter(name, value);
}
@Override
public void clearParameters() throws IOException {
public void clearParameters() {
bufferFile.clearParameters();
}
@Override
public String[] getParameterNames() throws IOException {
public String[] getParameterNames() {
return bufferFile.getParameterNames();
}
@Override
public int getBufferSize() throws IOException {
public int getBufferSize() {
return bufferFile.getBufferSize();
}
@Override
public int getIndexCount() throws IOException {
public int getIndexCount() {
return bufferFile.getIndexCount();
}
@Override
public int[] getFreeIndexes() throws IOException {
public int[] getFreeIndexes() {
return bufferFile.getFreeIndexes();
}
@Override
public void setFreeIndexes(int[] indexes) throws IOException {
public void setFreeIndexes(int[] indexes) {
bufferFile.setFreeIndexes(indexes);
}
@Override
public boolean isReadOnly() throws IOException {
public boolean isReadOnly() {
return bufferFile.isReadOnly();
}
@Override
public boolean setReadOnly() throws IOException {
return bufferFile.setReadOnly();
try {
return bufferFile.setReadOnly();
}
catch (Throwable t) {
throw RemoteExceptionUtil.dispatchIOException(t, "setReadOnly: " + associatedFilePath,
user);
}
}
@Override
public void close() throws IOException {
bufferFile.close();
dispose();
try {
bufferFile.close();
dispose();
}
catch (Throwable t) {
throw RemoteExceptionUtil.dispatchIOException(t, "close: " + associatedFilePath, user);
}
}
@Override
public boolean delete() throws IOException {
public boolean delete() {
boolean rc = false;
try {
rc = bufferFile.delete();
@@ -287,12 +303,24 @@ public class RemoteBufferFileImpl extends UnicastRemoteObject
@Override
public DataBuffer get(int index) throws IOException {
return bufferFile.get(new DataBuffer(), index);
try {
return bufferFile.get(new DataBuffer(), index);
}
catch (Throwable t) {
throw RemoteExceptionUtil.dispatchIOException(t,
"get(" + index + "): " + associatedFilePath, user);
}
}
@Override
public void put(DataBuffer buf, int index) throws IOException {
bufferFile.put(buf, index);
try {
bufferFile.put(buf, index);
}
catch (Throwable t) {
throw RemoteExceptionUtil.dispatchIOException(t,
"put(" + index + "): " + associatedFilePath, user);
}
}
@Override
@@ -307,27 +335,39 @@ public class RemoteBufferFileImpl extends UnicastRemoteObject
@Override
public BlockStreamHandle<InputBlockStream> getInputBlockStreamHandle() throws IOException {
BlockStreamServer blockStreamServer = BlockStreamServer.getBlockStreamServer();
InputBlockStream inputBlockStream = bufferFile.getInputBlockStream();
RemoteInputBlockStreamHandle streamHandle =
new RemoteInputBlockStreamHandle(blockStreamServer, inputBlockStream);
if (!blockStreamServer.registerBlockStream(streamHandle, inputBlockStream)) {
throw new IOException("request failed: block stream server not running");
try {
BlockStreamServer blockStreamServer = BlockStreamServer.getBlockStreamServer();
InputBlockStream inputBlockStream = bufferFile.getInputBlockStream();
RemoteInputBlockStreamHandle streamHandle =
new RemoteInputBlockStreamHandle(blockStreamServer, inputBlockStream);
if (!blockStreamServer.registerBlockStream(streamHandle, inputBlockStream)) {
throw new IOException("request failed: block stream server not running");
}
return streamHandle;
}
catch (Throwable t) {
throw RemoteExceptionUtil.dispatchIOException(t,
"getInputBlockStreamHandle: " + associatedFilePath, user);
}
return streamHandle;
}
@Override
public BlockStreamHandle<OutputBlockStream> getOutputBlockStreamHandle(int blockCount)
throws IOException {
BlockStreamServer blockStreamServer = BlockStreamServer.getBlockStreamServer();
OutputBlockStream outputBlockStream = bufferFile.getOutputBlockStream(blockCount);
RemoteOutputBlockStreamHandle streamHandle = new RemoteOutputBlockStreamHandle(
blockStreamServer, blockCount, outputBlockStream.getBlockSize());
if (!blockStreamServer.registerBlockStream(streamHandle, outputBlockStream)) {
throw new IOException("request failed: block stream server not running");
try {
BlockStreamServer blockStreamServer = BlockStreamServer.getBlockStreamServer();
OutputBlockStream outputBlockStream = bufferFile.getOutputBlockStream(blockCount);
RemoteOutputBlockStreamHandle streamHandle = new RemoteOutputBlockStreamHandle(
blockStreamServer, blockCount, outputBlockStream.getBlockSize());
if (!blockStreamServer.registerBlockStream(streamHandle, outputBlockStream)) {
throw new IOException("request failed: block stream server not running");
}
return streamHandle;
}
catch (Throwable t) {
throw RemoteExceptionUtil.dispatchIOException(t,
"getOutputBlockStreamHandle: " + associatedFilePath, user);
}
return streamHandle;
}
}
@@ -0,0 +1,118 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.server.remote;
import java.io.IOException;
import java.rmi.RemoteException;
import java.util.Set;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class RemoteExceptionUtil {
private static final Logger log = LogManager.getLogger(RemoteExceptionUtil.class);
/**
* Allowed IOExceptions (without cause) that are also defined by
* {@code ghidra/Ghidra/Framework/FileSystem/data/client.rmi.serial.filter}.
*/
private static final Set<Class<? extends IOException>> allowedIOExceptionClassSet = Set.of(
java.io.IOException.class,
java.io.FileNotFoundException.class,
ghidra.framework.store.ExclusiveCheckoutException.class,
ghidra.util.exception.UserAccessException.class,
ghidra.util.exception.DuplicateFileException.class,
ghidra.util.exception.FileInUseException.class,
ghidra.util.ReadOnlyException.class);
/**
* Sanitize, log and dispatch exceptions to client and comply with client serialization
* requirements. Any IOException with a cause will be simplified to an IOException without
* a cause. Any other checked exception, {@link RuntimeException}, {@link Throwable} or
* {@link Error} will be logged and produce a simplified {@link RemoteException} without cause.
*
* @param t original exception/error (expected non-IOExceptions which are explicitly thrown should
* be caught and conveyed by called instead of passing to this method).
* @param logDetail operation descipription (required)
* @param user user if known else null
* @return IOException to be thrown
*/
static IOException dispatchIOException(Throwable t, String logDetail, String user) {
return dispatchIOException(t, null, null, logDetail, user);
}
/**
* Sanitize, log and dispatch exceptions to client and comply with client serialization
* requirements. Any IOException with a cause will be simplified to an IOException without
* a cause. Any other checked exception, {@link RuntimeException}, {@link Throwable} or
* {@link Error} will be logged and produce a simplified {@link RemoteException} without cause.
*
* @param t original exception/error (expected non-IOExceptions which are explicitly thrown should
* be caught and conveyed by called instead of passing to this method).
* @param repositoryName repository name or null
* @param path repository folder/item path or null
* @param logDetail operation descipription (required)
* @param user user if known else null
* @return IOException to be thrown
*/
static IOException dispatchIOException(Throwable t, String repositoryName, String path,
String logDetail, String user) {
if (t instanceof RemoteException re) {
// Assume this was triggered by a failed remote object instantiation
return re;
}
Class<?> excClass = t.getClass();
Throwable cause = t.getCause();
String excKind;
if (t instanceof IOException ioe) {
// Only return allowed IOException class which has no cause
if (cause == null && allowedIOExceptionClassSet.contains(excClass)) {
return ioe;
}
// Log any IOException which has a cause or is not in the allowed set.
// Return as simple IOException without a cause.
log.error(excClass.getName() + ": " + t.getMessage(), t);
return new IOException(ioe.getMessage());
}
if (t instanceof RuntimeException rte) {
excKind = "Runtime Exception";
}
else if (t instanceof Exception) {
// Unexpected condition: exception should have been caught and handled by caller
excKind = "Checked Exception";
}
else {
excKind = "Error";
}
// Log all non-IOExceptions and return as a RemoteException without cause.
RemoteLoggingUtil.log(repositoryName, path, "ERROR: " + logDetail, user, true);
log.error(excKind + ": " + t, t);
return new RemoteException("Unexpected Server " + excKind);
}
private RemoteExceptionUtil() {
// No instantiation
}
}
@@ -0,0 +1,111 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.server.remote;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import ghidra.server.RepositoryManager;
public class RemoteLoggingUtil {
private static Logger log = LogManager.getLogger(GhidraServer.class);
/**
* Generate log message that contains inforamtion message.
*
* General format where client host may be omitted if unable to determine:
* <pre>
* msg (host)
* </pre>
* @param msg log message (required)
* @param user user name or null
*/
public static void log(String msg) {
log(null, null, msg, null, false);
}
/**
* Generate log message that contains information message and user details.
*
* General format where some portions may be omitted if null:
* <pre>
* msg (user@host)
* </pre>
* @param msg log message (required)
* @param user user name or null
*/
public static void log(String msg, String user) {
log(null, null, msg, user, false);
}
/**
* Generate information or error log message that contains repository, path, message
* and user details.
*
* General format where some portions may be omitted if null:
* <pre>
* [repositoryName]path: msg (user@host)
* </pre>
* @param repositoryName repository name or null
* @param path repository file path or null
* @param msg log message (required)
* @param user user name or null
* @param error true if error log else info
*/
public static void log(String repositoryName, String path, String msg, String user,
boolean error) {
StringBuilder buf = new StringBuilder();
if (repositoryName != null) {
buf.append("[");
buf.append(repositoryName);
buf.append("]");
}
String host = RepositoryManager.getRMIClient();
String userStr = user;
if (userStr != null) {
if (host != null) {
userStr += "@" + host;
}
}
else {
userStr = host;
}
if (path != null) {
buf.append(path);
}
if (repositoryName != null || path != null) {
buf.append(": ");
}
buf.append(msg);
if (userStr != null) {
buf.append(" (");
buf.append(userStr);
buf.append(")");
}
if (error) {
log.error(buf.toString());
}
else {
log.info(buf.toString());
}
}
private RemoteLoggingUtil() {
// no instantiation
}
}
@@ -4,21 +4,21 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package db.buffers;
package ghidra.server.remote;
import java.io.IOException;
import java.rmi.RemoteException;
import ghidra.server.remote.RepositoryHandleImpl;
import db.buffers.*;
import ghidra.server.stream.BlockStreamServer;
import ghidra.server.stream.RemoteInputBlockStreamHandle;
@@ -38,7 +38,7 @@ public class RemoteManagedBufferFileImpl extends RemoteBufferFileImpl
* @param managedBufferFile underlying managed buffer file
* @param owner associated repository handle instance
* @param associatedFilePath associated file path for logging
* @throws RemoteException
* @throws RemoteException if failed to instantiate remote object
*/
public RemoteManagedBufferFileImpl(LocalManagedBufferFile managedBufferFile,
RepositoryHandleImpl owner, String associatedFilePath) throws RemoteException {
@@ -48,12 +48,18 @@ public class RemoteManagedBufferFileImpl extends RemoteBufferFileImpl
@Override
public RemoteManagedBufferFileHandle getSaveFile() throws IOException {
LocalManagedBufferFile sf = (LocalManagedBufferFile) managedBufferFile.getSaveFile();
return sf != null ? new RemoteManagedBufferFileImpl(sf, owner, associatedFilePath) : null;
try {
LocalManagedBufferFile sf = (LocalManagedBufferFile) managedBufferFile.getSaveFile();
return sf != null ? new RemoteManagedBufferFileImpl(sf, owner, associatedFilePath) : null;
}
catch (Throwable t) {
throw RemoteExceptionUtil.dispatchIOException(t, "getSaveFile: " + associatedFilePath,
user);
}
}
@Override
public boolean delete() throws IOException {
public boolean delete() {
if (managedBufferFile.getVersion() == 1) {
owner.getRepository().log(associatedFilePath, "aborting file creation",
owner.getUserName());
@@ -63,44 +69,69 @@ public class RemoteManagedBufferFileImpl extends RemoteBufferFileImpl
@Override
public void saveCompleted(boolean commit) throws IOException {
if (!commit) {
int version = managedBufferFile.getVersion();
owner.getRepository().log(associatedFilePath,
"aborting file version " + version + " creation", owner.getUserName());
try {
if (!commit) {
int version = managedBufferFile.getVersion();
owner.getRepository().log(associatedFilePath,
"aborting file version " + version + " creation", owner.getUserName());
}
managedBufferFile.saveCompleted(commit);
}
catch (Throwable t) {
throw RemoteExceptionUtil.dispatchIOException(t, "saveCompleted: " + associatedFilePath,
user);
}
managedBufferFile.saveCompleted(commit);
}
@Override
public boolean canSave() throws IOException {
public boolean canSave() {
return managedBufferFile.canSave();
}
@Override
public void setVersionComment(String comment) throws IOException {
public void setVersionComment(String comment) {
managedBufferFile.setVersionComment(comment);
}
@Override
public RemoteBufferFileHandle getNextChangeDataFile(boolean getFirst) throws IOException {
LocalBufferFile cf = (LocalBufferFile) managedBufferFile.getNextChangeDataFile(getFirst);
return cf != null ? new RemoteBufferFileImpl(cf, owner, associatedFilePath) : null;
try {
LocalBufferFile cf =
(LocalBufferFile) managedBufferFile.getNextChangeDataFile(getFirst);
return cf != null ? new RemoteBufferFileImpl(cf, owner, associatedFilePath) : null;
}
catch (Throwable t) {
throw RemoteExceptionUtil.dispatchIOException(t,
"getNextChangeDataFile: " + associatedFilePath, user);
}
}
@Override
public RemoteBufferFileHandle getSaveChangeDataFile() throws IOException {
LocalBufferFile cf = (LocalBufferFile) managedBufferFile.getSaveChangeDataFile();
return cf != null ? new RemoteBufferFileImpl(cf, owner, associatedFilePath) : null;
try {
LocalBufferFile cf = (LocalBufferFile) managedBufferFile.getSaveChangeDataFile();
return cf != null ? new RemoteBufferFileImpl(cf, owner, associatedFilePath) : null;
}
catch (Throwable t) {
throw RemoteExceptionUtil.dispatchIOException(t,
"getSaveChangeDataFile: " + associatedFilePath, user);
}
}
@Override
public long getCheckinID() throws IOException {
public long getCheckinID() {
return managedBufferFile.getCheckinID();
}
@Override
public byte[] getForwardModMapData(int oldVersion) throws IOException {
return managedBufferFile.getForwardModMapData(oldVersion);
try {
return managedBufferFile.getForwardModMapData(oldVersion);
}
catch (Throwable t) {
throw RemoteExceptionUtil.dispatchIOException(t,
"getForwardModMapData: " + associatedFilePath, user);
}
}
@Override
@@ -111,14 +142,21 @@ public class RemoteManagedBufferFileImpl extends RemoteBufferFileImpl
@Override
public BlockStreamHandle<InputBlockStream> getInputBlockStreamHandle(byte[] changeMapData)
throws IOException {
BlockStreamServer blockStreamServer = BlockStreamServer.getBlockStreamServer();
InputBlockStream inputBlockStream = managedBufferFile.getInputBlockStream(changeMapData);
RemoteInputBlockStreamHandle streamHandle =
new RemoteInputBlockStreamHandle(blockStreamServer, inputBlockStream);
if (!blockStreamServer.registerBlockStream(streamHandle, inputBlockStream)) {
throw new IOException("request failed: block stream server not running");
try {
BlockStreamServer blockStreamServer = BlockStreamServer.getBlockStreamServer();
InputBlockStream inputBlockStream =
managedBufferFile.getInputBlockStream(changeMapData);
RemoteInputBlockStreamHandle streamHandle =
new RemoteInputBlockStreamHandle(blockStreamServer, inputBlockStream);
if (!blockStreamServer.registerBlockStream(streamHandle, inputBlockStream)) {
throw new IOException("request failed: block stream server not running");
}
return streamHandle;
}
catch (Throwable t) {
throw RemoteExceptionUtil.dispatchIOException(t,
"getInputBlockStream with map: " + associatedFilePath, user);
}
return streamHandle;
}
}
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -37,27 +37,13 @@ public class RepositoryServerHandleImpl extends UnicastRemoteObject
private final boolean supportPasswordChange;
private final boolean readOnly;
/*
* @see ghidra.framework.remote.RepositoryServerHandle#anonymousAccessAllowed()
*/
@Override
public boolean anonymousAccessAllowed() {
return mgr.anonymousAccessAllowed();
}
/*
* @see ghidra.framework.remote.RepositoryServerHandle#isReadOnly()
*/
@Override
public boolean isReadOnly() {
return readOnly;
}
/**
* Construct a repository server handle for a specific user.
* @param user remote user
* @param readOnly true if restricted to read-only use
* @param mgr repository manager
* @throws RemoteException
* @param supportPasswordChange true if password change is allowed
* @throws RemoteException if failed to instantiate remote object
*/
public RepositoryServerHandleImpl(String user, boolean readOnly, RepositoryManager mgr,
boolean supportPasswordChange) throws RemoteException {
@@ -77,98 +63,103 @@ public class RepositoryServerHandleImpl extends UnicastRemoteObject
mgr.dropHandle(this);
}
/*
* @see rmitest.RepositoryServerHandle#createRepository(java.lang.String)
*/
@Override
public RemoteRepositoryHandle createRepository(String name) throws IOException {
Repository repository = mgr.createRepository(currentUser, name);
return new RepositoryHandleImpl(currentUser, repository);
public boolean anonymousAccessAllowed() {
return mgr.anonymousAccessAllowed();
}
/*
* @see rmitest.RepositoryServerHandle#getRepository(java.lang.String)
*/
@Override
public RemoteRepositoryHandle getRepository(String name) throws IOException {
public boolean isReadOnly() {
return readOnly;
}
@Override
public RemoteRepositoryHandle createRepository(String name) throws IOException {
try {
Repository repository = mgr.createRepository(currentUser, name);
RemoteLoggingUtil.log(name, null, "repository created", currentUser, false);
return new RepositoryHandleImpl(currentUser, repository);
}
catch (Throwable t) {
throw RemoteExceptionUtil.dispatchIOException(t, name,
null, "Create repository", currentUser);
}
}
@Override
public RemoteRepositoryHandle getRepository(String name)
throws UserAccessException, IOException {
System.gc();
Repository repository = mgr.getRepository(currentUser, name);
if (repository == null) {
return null;
try {
Repository repository = mgr.getRepository(currentUser, name);
if (repository == null) {
return null;
}
return new RepositoryHandleImpl(currentUser, repository);
}
catch (Throwable t) {
throw RemoteExceptionUtil.dispatchIOException(t, name,
null, "Get repository", currentUser);
}
return new RepositoryHandleImpl(currentUser, repository);
}
/*
* @see ghidra.framework.remote.RepositoryServerHandle#deleteRepository(java.lang.String)
*/
@Override
public void deleteRepository(String name) throws UserAccessException, IOException {
mgr.deleteRepository(currentUser, name);
try {
mgr.deleteRepository(currentUser, name);
}
catch (Throwable t) {
throw RemoteExceptionUtil.dispatchIOException(t, name,
null, "Delete repository", currentUser);
}
}
/*
* @see rmitest.RepositoryServerHandle#getRepositoryNames()
*/
@Override
public String[] getRepositoryNames() {
return mgr.getRepositoryNames(currentUser);
}
/*
* @see ghidra.framework.remote.RepositoryServerHandle#getUser()
*/
@Override
public String getUser() throws IOException {
public String getUser() {
return currentUser;
}
/*
* @see ghidra.framework.remote.RepositoryServerHandle#getAllUsers()
*/
@Override
public String[] getAllUsers() throws IOException {
public String[] getAllUsers() {
if (readOnly) {
return new String[0];
}
return mgr.getAllUsers(currentUser);
}
/*
* @see ghidra.framework.remote.RepositoryServerHandle#canSetPassword()
*/
@Override
public boolean canSetPassword() throws RemoteException {
public boolean canSetPassword() {
return supportPasswordChange && mgr.getUserManager().canSetPassword(currentUser);
}
/*
* @see ghidra.framework.remote.RepositoryServerHandle#getPasswordExpiration()
*/
@Override
public long getPasswordExpiration() throws IOException {
public long getPasswordExpiration() {
if (canSetPassword()) {
return mgr.getUserManager().getPasswordExpiration(currentUser);
}
return -1;
}
/*
* @see ghidra.framework.remote.RepositoryServerHandle#setPassword(char[])
*/
@Override
public boolean setPassword(char[] saltedSHA256PasswordHash) throws IOException {
if (!canSetPassword()) {
return false;
try {
if (!canSetPassword()) {
return false;
}
return mgr.getUserManager().setPassword(currentUser, saltedSHA256PasswordHash, false);
}
catch (Throwable t) {
throw RemoteExceptionUtil.dispatchIOException(t, "Set password", currentUser);
}
return mgr.getUserManager().setPassword(currentUser, saltedSHA256PasswordHash, false);
}
/*
* @see ghidra.framework.remote.RepositoryServerHandle#connected()
*/
@Override
public void connected() {
// do nothing
@@ -1,7 +1,8 @@
Ghidra server startup parameters.
Command line parameters:
[-ip <hostname>] [-i #.#.#.#] [-p#] [-n]
[-a#] [-d<ad_domain>] [-e<days>] [-jaas <config_file>] [-u] [-autoProvision] [-anonymous] [-ssh]
[-a#] [-d<ad_domain>] [-e<days>] [-jaas <config_file>] [-u] [-autoProvision]
[-anonymous] [-ssh]
<repository_path>
@@ -31,8 +31,8 @@ import org.apache.logging.log4j.*;
import ghidra.framework.remote.GhidraPrincipal;
import ghidra.framework.remote.SignatureCallback;
import ghidra.net.*;
import ghidra.server.RepositoryManager;
import ghidra.server.UserManager;
import ghidra.server.remote.RemoteLoggingUtil;
/**
* <code>PKIAuthenticationModule</code> performs client authentication through the
@@ -203,7 +203,7 @@ public class PKIAuthenticationModule implements AuthenticationModule {
}
if (UserManager.ANONYMOUS_USERNAME.equals(username)) {
RepositoryManager.log(null, null, "Anonymous access allowed for: " +
RemoteLoggingUtil.log("Anonymous access allowed for: " +
certChain[0].getSubjectX500Principal().toString(), user.getName());
}
@@ -140,10 +140,10 @@ public class SSHAuthenticationModule {
* @param subject unauthenticated user ID (must be used if name callback not provided/allowed)
* @param callbacks authentication callbacks
* @return authenticated user ID (may come from callbacks)
* @throws LoginException if authentication failure occurs
* @throws FailedLoginException if authentication failure occurs
*/
public String authenticate(UserManager userMgr, Subject subject, Callback[] callbacks)
throws LoginException {
throws FailedLoginException {
GhidraPrincipal user = GhidraPrincipal.getGhidraPrincipal(subject);
if (user == null) {
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -30,7 +30,7 @@ import javax.security.auth.spi.LoginModule;
import com.sun.security.auth.UserPrincipal;
import generic.concurrent.io.ProcessConsumer;
import ghidra.server.RepositoryManager;
import ghidra.server.remote.RemoteLoggingUtil;
import ghidra.util.DateUtils;
import ghidra.util.timer.Watchdog;
@@ -209,11 +209,11 @@ public class ExternalProgramLoginModule implements LoginModule {
process.set(p);
ProcessConsumer.consume(p.getInputStream(), stdOutStr -> {
RepositoryManager.log(null, null, extProgramName + " STDOUT: " + stdOutStr, null);
RemoteLoggingUtil.log(extProgramName + " STDOUT: " + stdOutStr);
});
ProcessConsumer.consume(p.getErrorStream(), errStr -> {
RepositoryManager.log(null, null, extProgramName + " STDERR: " + errStr, null);
RemoteLoggingUtil.log(extProgramName + " STDERR: " + errStr);
});
PrintWriter outputWriter = new PrintWriter(p.getOutputStream());
@@ -230,8 +230,8 @@ public class ExternalProgramLoginModule implements LoginModule {
}
}
catch (IOException | InterruptedException e) {
RepositoryManager.log(null, null,
"Exception when executing " + extProgramName + ":" + e.getMessage(), null);
RemoteLoggingUtil
.log("Exception when executing " + extProgramName + ": " + e.getMessage());
throw new LoginException("Error executing external program");
}
finally {
@@ -24,7 +24,6 @@ import ghidra.framework.remote.User;
import ghidra.framework.store.*;
import ghidra.framework.store.local.*;
import ghidra.server.Repository;
import ghidra.server.RepositoryManager;
import ghidra.util.InvalidNameException;
import ghidra.util.exception.UserAccessException;
@@ -79,8 +78,7 @@ public class RepositoryFile {
pathname += "/";
}
pathname += name;
RepositoryManager.log(repository.getName(), pathname,
"file is corrupt or unsupported", null);
repository.log(pathname, "file is corrupt or unsupported", null);
throw new FileNotFoundException(pathname + " is corrupt or unsupported");
}
}
@@ -177,8 +175,7 @@ public class RepositoryFile {
"Unsupported operation for " + folderItem.getClass().getSimpleName());
}
LocalManagedBufferFile bf = databaseItem.open(version, minChangeDataVer);
repository.log(
getPathname(), "version " +
repository.log(getPathname(), "version " +
(version < 0 ? folderItem.getCurrentVersion() : version) + " opened read-only",
user);
return bf;
@@ -316,7 +313,7 @@ public class RepositoryFile {
parent.fileMoved(this, oldName, newParent);
parent = newParent;
pathChanged();
RepositoryManager.log(repository.getName(), oldPath, "file moved to " + getPathname(),
repository.log(oldPath, "file moved to " + getPathname(),
user);
}
}
@@ -27,7 +27,6 @@ import ghidra.framework.store.*;
import ghidra.framework.store.local.LocalFileSystem;
import ghidra.framework.store.local.LocalFolderItem;
import ghidra.server.Repository;
import ghidra.server.RepositoryManager;
import ghidra.util.InvalidNameException;
import ghidra.util.exception.DuplicateFileException;
import ghidra.util.exception.FileInUseException;
@@ -257,7 +256,7 @@ public class RepositoryFolder {
// Folder created notification causes RepositoryFolder instance to be added
RepositoryFolder rf = getFolder(folderName);
RepositoryManager.log(repository.getName(), rf.getPathname(), "folder created", user);
repository.log(rf.getPathname(), "folder created", user);
return rf;
}
}
@@ -289,7 +288,7 @@ public class RepositoryFolder {
RepositoryFile rf = new RepositoryFile(repository, fileSystem, this, itemName);
fileMap.put(itemName, rf);
RepositoryManager.log(repository.getName(), makePathname(getPathname(), itemName),
repository.log(makePathname(getPathname(), itemName),
"file created", user);
}
}
@@ -320,7 +319,7 @@ public class RepositoryFolder {
// Buffer file does not yet exist - too early to get folder item needed for RepositoryFile
LocalManagedBufferFile bf = fileSystem.createDatabase(getPathname(), itemName, fileID,
contentType, bufferSize, user, projectPath);
RepositoryManager.log(repository.getName(), makePathname(getPathname(), itemName),
repository.log(makePathname(getPathname(), itemName),
"file created", user);
return bf;
}
@@ -434,8 +433,7 @@ public class RepositoryFolder {
throw new IOException("Folder can not be renamed and moved");
}
pathChanged();
RepositoryManager.log(repository.getName(), oldPath,
"folder moved to " + getPathname(), user);
repository.log(oldPath, "folder moved to " + getPathname(), user);
}
finally {
repository.flushChangeEvents();
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -27,7 +27,7 @@ import ghidra.util.Msg;
*/
public class BufferFileAdapter implements BufferFile {
private BufferFileHandle bufferFileHandle;
private final BufferFileHandle bufferFileHandle;
/**
* Constructor.
@@ -39,7 +39,16 @@ public class BufferFileAdapter implements BufferFile {
@Override
public int getParameter(String name) throws NoSuchElementException, IOException {
return bufferFileHandle.getParameter(name);
try {
return bufferFileHandle.getParameter(name);
}
catch (RemoteException e) {
Throwable cause = e.getCause();
if (cause instanceof NoSuchElementException nse) {
throw nse;
}
throw e;
}
}
@Override
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -34,6 +34,7 @@ public interface BufferFileHandle {
public boolean setReadOnly() throws IOException;
/**
* NOTE: NoSuchElementException is runtime so must be handled if wrapped in RemoteException
* @see BufferFile#getParameter(java.lang.String)
*/
public int getParameter(String name) throws NoSuchElementException, IOException;
@@ -274,7 +274,7 @@ public class LocalManagedBufferFile extends LocalBufferFile implements ManagedBu
/**
* @return version associated with this buffer file
*/
int getVersion() {
public int getVersion() {
return version;
}
@@ -284,7 +284,7 @@ public class LocalManagedBufferFile extends LocalBufferFile implements ManagedBu
}
@Override
public void setVersionComment(String comment) throws IOException {
public void setVersionComment(String comment) {
this.comment = comment;
}
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -17,54 +17,55 @@ package db.buffers;
import java.io.IOException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.NoSuchElementException;
/**
* <code>RemoteBufferFileHandle</code> facilitates access to a remote BufferFile
* via RMI.
* <p>
* Methods from {@link BufferFileHandle} <b>must</b> be re-declared here
* IMPORTANT: Methods from {@link BufferFileHandle} <b>must</b> be re-declared here
* so they may be properly marshalled for remote invocation via RMI.
* This became neccessary with an OpenJDK 11.0.6 change made to
* {@link RemoteObjectInvocationHandler}.
*/
public interface RemoteBufferFileHandle extends BufferFileHandle, Remote {
@Override
public boolean isReadOnly() throws IOException;
public boolean isReadOnly() throws RemoteException;
@Override
public boolean setReadOnly() throws IOException;
// NoSuchElementException will get wrapped within RemoteException
@Override
public int getParameter(String name) throws NoSuchElementException, IOException;
public int getParameter(String name) throws RemoteException;
@Override
public void setParameter(String name, int value) throws IOException;
public void setParameter(String name, int value) throws RemoteException;
@Override
public void clearParameters() throws IOException;
public void clearParameters() throws RemoteException;
@Override
public String[] getParameterNames() throws IOException;
public String[] getParameterNames() throws RemoteException;
@Override
public int getBufferSize() throws IOException;
public int getBufferSize() throws RemoteException;
@Override
public int getIndexCount() throws IOException;
public int getIndexCount() throws RemoteException;
@Override
public int[] getFreeIndexes() throws IOException;
public int[] getFreeIndexes() throws RemoteException;
@Override
public void setFreeIndexes(int[] indexes) throws IOException;
public void setFreeIndexes(int[] indexes) throws RemoteException;
@Override
public void close() throws IOException;
@Override
public boolean delete() throws IOException;
public boolean delete() throws RemoteException;
@Override
public DataBuffer get(int index) throws IOException;
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -17,14 +17,14 @@ package db.buffers;
import java.io.IOException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.NoSuchElementException;
/**
* <code>RemoteManagedBufferFileHandle</code> facilitates access to a ManagedBufferFile
* via RMI.
* <p>
* Methods from {@link BufferFileHandle} and {@link ManagedBufferFile} <b>must</b>
* IMPORTANT: Methods from {@link BufferFileHandle} and {@link ManagedBufferFile} <b>must</b>
* be re-declared here so they may be properly marshalled for remote invocation via RMI.
* This became neccessary with an OpenJDK 11.0.6 change made to
* {@link RemoteObjectInvocationHandler}.
@@ -35,40 +35,41 @@ public interface RemoteManagedBufferFileHandle extends ManagedBufferFileHandle,
// BufferFileHandle methods
//--------------------------------------------------------------------------
@Override
public boolean isReadOnly() throws IOException;
public boolean isReadOnly() throws RemoteException;
@Override
public boolean setReadOnly() throws IOException;
// NoSuchElementException will get wrapped within RemoteException
@Override
public int getParameter(String name) throws NoSuchElementException, IOException;
public int getParameter(String name) throws RemoteException;
@Override
public void setParameter(String name, int value) throws IOException;
public void setParameter(String name, int value) throws RemoteException;
@Override
public void clearParameters() throws IOException;
public void clearParameters() throws RemoteException;
@Override
public String[] getParameterNames() throws IOException;
public String[] getParameterNames() throws RemoteException;
@Override
public int getBufferSize() throws IOException;
public int getBufferSize() throws RemoteException;
@Override
public int getIndexCount() throws IOException;
public int getIndexCount() throws RemoteException;
@Override
public int[] getFreeIndexes() throws IOException;
public int[] getFreeIndexes() throws RemoteException;
@Override
public void setFreeIndexes(int[] indexes) throws IOException;
public void setFreeIndexes(int[] indexes) throws RemoteException;
@Override
public void close() throws IOException;
@Override
public boolean delete() throws IOException;
public boolean delete() throws RemoteException;
@Override
public DataBuffer get(int index) throws IOException;
@@ -103,10 +104,10 @@ public interface RemoteManagedBufferFileHandle extends ManagedBufferFileHandle,
public void saveCompleted(boolean commit) throws IOException;
@Override
public boolean canSave() throws IOException;
public boolean canSave() throws RemoteException;
@Override
public void setVersionComment(String comment) throws IOException;
public void setVersionComment(String comment) throws RemoteException;
@Override
public BufferFileHandle getNextChangeDataFile(boolean getFirst) throws IOException;
@@ -115,7 +116,7 @@ public interface RemoteManagedBufferFileHandle extends ManagedBufferFileHandle,
public BufferFileHandle getSaveChangeDataFile() throws IOException;
@Override
public long getCheckinID() throws IOException;
public long getCheckinID() throws RemoteException;
@Override
public byte[] getForwardModMapData(int oldVersion) throws IOException;
@@ -1,6 +1,8 @@
##VERSION: 2.0
Module.manifest||GHIDRA||||END|
README.md||GHIDRA||||END|
data/client.rmi.serial.filter||GHIDRA||||END|
data/serialFilterREADME.md||GHIDRA||||END|
src/main/java/ghidra/framework/client/package.html||GHIDRA||reviewed||END|
src/main/java/ghidra/framework/store/db/package.html||GHIDRA||reviewed||END|
src/main/java/ghidra/framework/store/local/package.html||GHIDRA||reviewed||END|
@@ -0,0 +1,108 @@
# Ghidra Server RMI client deserialization filter patterns.
# See GhidraObjectInputFilter javadoc.
remoteIf=ghidra.framework.remote.GhidraServerHandle;
remoteIf=ghidra.framework.remote.RemoteRepositoryServerHandle;
remoteIf=ghidra.framework.remote.RemoteRepositoryHandle;
remoteIf=db.buffers.RemoteBufferFileHandle;
remoteIf=db.buffers.RemoteManagedBufferFileHandle;
ghidra.framework.remote.*;
ghidra.framework.store.*;
ghidra.server.stream.*;
[Lghidra.framework.remote.RepositoryChangeEvent;
[Lghidra.framework.remote.RepositoryItem;
[Lghidra.framework.store.ItemCheckoutStatus;
[Lghidra.framework.store.Version;
db.buffers.*;
java.rmi.Remote;
java.rmi.dgc.Lease;
java.rmi.dgc.VMID;
java.rmi.server.ObjID;
java.rmi.server.RemoteObject;
java.rmi.server.RemoteObjectInvocationHandler;
java.rmi.server.UID;
# RMI exception serialized from server to client
java.rmi.AccessException;
java.rmi.MarshalException;
java.rmi.NoSuchObjectException;
java.rmi.NotBoundException;
java.rmi.RemoteException;
java.rmi.ServerException;
java.rmi.ServerError;
java.rmi.ServerRuntimeException;
java.rmi.server.SocketSecurityException;
java.rmi.UnexpectedException;
java.rmi.UnmarshalException;
javax.rmi.ssl.SslRMIClientSocketFactory;
java.lang.reflect.Proxy;
java.lang.String;
[Ljava.lang.String;
java.util.Collections$EmptyList;
javax.security.auth.callback.NameCallback;
javax.security.auth.callback.PasswordCallback;
javax.security.auth.x500.X500Principal;
javax.security.auth.callback.Callback;
[Ljavax.security.auth.callback.Callback;
javax.security.auth.x500.X500Principal;
[Ljavax.security.auth.x500.X500Principal;
#
# Exceptions thrown by Ghidra Server remote objects
# (see above for various RMI remote exception implementations)
#
java.lang.StackTraceElement;
[Ljava.lang.StackTraceElement; # Throwable stackTrace array field
java.lang.Throwable;
java.lang.Error;
java.lang.Exception;
java.lang.RuntimeException;
java.security.GeneralSecurityException;
# Exceptions explicitly thrown by Ghidra Server remote objects
# as declared by corresponding Remote interface or encapsulated
# by a java.rmi.RemoteException.
javax.security.auth.login.FailedLoginException;
javax.security.auth.login.LoginException;
java.lang.UnsupportedOperationException;
java.util.NoSuchElementException;
ghidra.util.exception.DuplicateNameException;
ghidra.util.exception.UsrException;
ghidra.util.InvalidNameException;
#
# IOExceptions
#
# NOTE: Any exception thrown on server with a cause will be logged on server and conveyed back
# to client as a simple IOException without a cause.
#
# NOTE: All IOExceptions added below must be included within RemoteExceptionUtil.allowedIOExceptionClassSet
#
java.io.IOException;
java.io.FileNotFoundException;
ghidra.framework.store.ExclusiveCheckoutException;
ghidra.util.exception.UserAccessException;
ghidra.util.exception.DuplicateFileException;
ghidra.util.exception.FileInUseException;
ghidra.util.ReadOnlyException;
@@ -0,0 +1,100 @@
# Ghidra Serialization Filter
## Overview
As of version 12.0.5, Ghidra employs serialization input filters to address concerns about potential
serialization vulnerabilities in relation to the use of Java RMI (e.g., Ghidra Server). Filters
are employed by both Ghidra Server and client applications. The consequqnce of this filtering is
that all Java Object deserialization is subject to the filter even when it corresponds to purely
local functionality. This can occur with certain code that relies on serialization to facilitate
object cloning (e.g., `org.apache.commons.collections4.functors.PrototypeFactory`). When such cases occur
it may be neccessary to add allowed classes to a client-side serial input filter.
The Ghidra application discovers serial input filter specifications (`*.serial.filter`) files within
each Ghidra module's data directory (e.g., `Ghidra/Framework/FileSystem/data`) at startup. The
combined filter set is used to establish a global input serialization filter for Ghidra.
When adding functionality to Ghidra it may be neccessary to adjust the defined serial filter
specifications. When the filter rejects a class deserialization an `InvalidClassException` will be
thrown and the rejected class name will be logged. The log will need to be consulted since the
exception itself does not convey the name of the offending class.
## Reference Information
- [Java Serialization Filtering](https://docs.oracle.com/javase/8/docs/technotes/guides/serialization/filters/serialization-filtering.html)
## Serial Input Filter Format
By default, the filter implementation will allow all primitive types (e.g., `int`, `char`, etc.)
and primitive arrays. Filter files need to specify all other Java classes that should allow
deserialization. It is important to remember that all filter specifications will be combined into
a single global filter.
**IMPORTANT:** Although supported by Java's serial input filter specification, Ghidra does not
support the class rejection pattern starting with the `!` prefix. This restriction stems from
Ghidra combining all serial filters into a single unordered filter specification.
The serial input filters (`*.serial.filter`) support the following entry types where each entry
must end with a semicolon `;`. End of line comments may be specified with a leading `#` character.
- Allowed class name. A class is specified by its full classname including package path:
```
java.lang.String;
```
- Allowed inner class, anonymous class, or compilergenerated synthetic class:
```
ghidra.myplugin.Foo$MyInnerClass;
```
- Allowed class array (single dimension). Uses a `[L` prefix before the full classname.
NOTE: Anytime an array is allowed the base class must also be allowed.
```
[Ljava.lang.String;
````
- Allowed class array (two dimension). Uses a `[[L` prefix before the full classname.
NOTE: Anytime an array is allowed the base class must also be allowed.
```
[[Ljava.lang.Integer;
````
- Wildcard class name specification can be used very carefully. The `*` wildcard only spans a single
package, while the `**` wildcard will include all subpackages. Do not allow "Gadget classes" that
can be exploited.
```
ghidra.myplugin.*;
[Lghidra.myplugin.*;
ghidra.my*;
[Lghidra.my*;
```
- Allowed remote interface that employ a dynamic Proxy classes (e.g., Java RMI Remote interface).
```
remoteIf=ghidra.remote.MyRemoteIf;
```
- Maximum number of array elements (default: `32000`). The maximum specified by any filter will be
used. A specified value will be ignore if less than the default.
```
maxarray=200000;
```
- Maximum number of bytes in a serialization stream (default: `33554432` / 32MB). The maximum
specified by any filter will be used. A specified value will be ignore if less than the default.
```
maxbytes=100000000;
```
- Maximum references in a graph between objects (default: `10000`). The maximum specified by any
filter will be used. A specified value will be ignore if less than the default.
```
maxrefs=15000;
```
- Maximum depth of an object graph. (default: `50`). The maximum specified by any filter will be used.
A specified value will be ignore if less than the default.
```
maxdepth=75;
```
**NOTE:** Default values shown above may be adjusted in the future. Please report any filter failures
associated with standard Ghidra features.
@@ -31,7 +31,8 @@ import ghidra.framework.model.ServerInfo;
import ghidra.framework.remote.*;
import ghidra.framework.remote.security.SSHKeyManager;
import ghidra.net.*;
import ghidra.util.*;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
import ghidra.util.exception.*;
import ghidra.util.task.TaskLauncher;
import ghidra.util.task.TaskMonitor;
@@ -207,6 +208,9 @@ public class ClientUtil {
Msg.showError(ClientUtil.class, parent, title,
"Access denied: " + repository + "\n" + exc.getMessage());
}
// FIXME: Verify presence and source of ServerException and ServerError which
// both originate from UnicastServerRef.dispatch method and are both forms
// of RemoteException
else if ((exc instanceof ServerException) || (exc instanceof ServerError)) {
Msg.showError(ClientUtil.class, parent, title,
"Exception occurred on the Ghidra Server.", exc.getCause());
@@ -576,6 +576,9 @@ public class RepositoryAdapter implements RemoteAdapterListener {
if (t instanceof UnmarshalException) {
throw new UnsupportedOperationException(operation);
}
if (t instanceof UnsupportedOperationException uoe) {
throw uoe;
}
}
/*
@@ -0,0 +1,93 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.remote;
import java.io.ObjectInputFilter;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BinaryOperator;
/**
* {@link GhidraSerialFilterFactory} provides the serial filter factory which imposes
* {@link GhidraObjectInputFilter} as a global serial input filter.
* <p>
* NOTE: With the use of Gradle test JVM instances it may be neccessary for those instances
* to specify this class as the serial filter factory and rely on lazy initialization of the
* {@link GhidraObjectInputFilter global serial filter}.
* <pre>
* -Djdk.serialFilterFactory=ghidra.framework.remote.GhidraSerialFilterFactory
* </pre>
*/
public class GhidraSerialFilterFactory implements BinaryOperator<ObjectInputFilter> {
private static final AtomicReference<GhidraSerialFilterFactory> filterFactoryRef =
new AtomicReference<>();
private final GhidraObjectInputFilter globalFilter;
/**
* Constructor. Caller is reposponsible for installation.
* See {@link ObjectInputFilter.Config#setSerialFilterFactory}.
*/
public GhidraSerialFilterFactory() {
if (!filterFactoryRef.compareAndSet(null, this)) {
throw new IllegalStateException(
"Serial filter factory has previously been instantiated");
}
globalFilter = new GhidraObjectInputFilter();
}
GhidraObjectInputFilter getSerialFilter() {
return globalFilter;
}
@Override
public ObjectInputFilter apply(ObjectInputFilter current, ObjectInputFilter requested) {
// Merge any existing/requested filter with our strict global filter.
// Our filter is always applied.
if (current == null && requested == null) {
return globalFilter;
}
if (current == null) {
return ObjectInputFilter.merge(requested, globalFilter);
}
if (requested == null) {
return ObjectInputFilter.merge(current, globalFilter);
}
return ObjectInputFilter.merge(ObjectInputFilter.merge(current, requested),
globalFilter);
}
/**
* Get, and install if neccessary, the serial filter factory instance. If a new factory is
* installed it will have an uninitialized {@link GhidraObjectInputFilter} instance.
* <p>
* See {@link java.io.ObjectInputFilter.Config#setSerialFilterFactory(java.util.function.BinaryOperator)}.
*
* @return serial filter factory singleton instance
* @throws IllegalStateException if the serial input factory has already been established and
* cannot be updated.
*/
static synchronized GhidraSerialFilterFactory getOrInstallInstance()
throws IllegalStateException {
GhidraSerialFilterFactory factory = filterFactoryRef.get();
if (factory != null) {
return factory;
}
GhidraSerialFilterFactory newFactory = new GhidraSerialFilterFactory();
ObjectInputFilter.Config.setSerialFilterFactory(newFactory);
return newFactory;
}
}
@@ -20,7 +20,7 @@ import java.rmi.RemoteException;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.login.LoginException;
import javax.security.auth.login.FailedLoginException;
/**
* <code>GhidraServerHandle</code> provides access to a remote server.
@@ -51,7 +51,7 @@ public interface GhidraServerHandle extends Remote {
* older clients the ability to connect to the server. Remote interface remained
* unchanged allowing 9.1 clients to connect to 9.0 server.
* 12: Revised RepositoryFile serialization to facilitate support for text-data used
* for link-file storage.
* for link-file storage (12.0).
*/
/**
@@ -99,17 +99,17 @@ public interface GhidraServerHandle extends Remote {
* @param authCallbacks valid authentication callback objects which have been satisfied, or
* null if server does not require authentication.
* @return repository server handle.
* @throws LoginException if user authentication fails
* @throws RemoteException
* @throws FailedLoginException if user authentication fails
* @throws RemoteException failed to create remote handle
* @see #getAuthenticationCallbacks()
*/
RemoteRepositoryServerHandle getRepositoryServer(Subject user, Callback[] authCallbacks)
throws LoginException, RemoteException;
throws FailedLoginException, RemoteException;
/**
* Check server interface compatibility
* @param serverInterfaceVersion client/server interface version
* @throws RemoteException
* @throws RemoteException if requested server interface version not available
* @see #INTERFACE_VERSION
*/
void checkCompatibility(int serverInterfaceVersion) throws RemoteException;
@@ -17,6 +17,7 @@ package ghidra.framework.remote;
import java.io.IOException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.server.RemoteObjectInvocationHandler;
import db.buffers.ManagedBufferFileHandle;
@@ -33,10 +34,10 @@ import ghidra.util.InvalidNameException;
*/
public interface RemoteRepositoryHandle extends RepositoryHandle, Remote {
@Override
String getName() throws IOException;
String getName() throws RemoteException;
@Override
User getUser() throws IOException;
User getUser() throws RemoteException;
@Override
User[] getUserList() throws IOException;
@@ -45,7 +46,7 @@ public interface RemoteRepositoryHandle extends RepositoryHandle, Remote {
boolean anonymousAccessAllowed() throws IOException;
@Override
String[] getServerUserList() throws IOException;
String[] getServerUserList() throws RemoteException;
@Override
void setUserList(User[] users, boolean anonymousAccessAllowed) throws IOException;
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -17,12 +17,13 @@ package ghidra.framework.remote;
import java.io.IOException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.server.RemoteObjectInvocationHandler;
/**
* <code>RepositoryServerHandle</code> provides access to a remote repository server via RMI.
* <p>
* Methods from {@link RepositoryServerHandle} <b>must</b> be re-declared here
* IMPORTANT: Methods from {@link RepositoryServerHandle} <b>must</b> be re-declared here
* so they may be properly marshalled for remote invocation via RMI.
* This became neccessary with an OpenJDK 11.0.6 change made to
* {@link RemoteObjectInvocationHandler}.
@@ -30,10 +31,10 @@ import java.rmi.server.RemoteObjectInvocationHandler;
public interface RemoteRepositoryServerHandle extends RepositoryServerHandle, Remote {
@Override
boolean anonymousAccessAllowed() throws IOException;
boolean anonymousAccessAllowed() throws RemoteException;
@Override
boolean isReadOnly() throws IOException;
boolean isReadOnly() throws RemoteException;
@Override
RepositoryHandle createRepository(String name) throws IOException;
@@ -45,24 +46,24 @@ public interface RemoteRepositoryServerHandle extends RepositoryServerHandle, Re
void deleteRepository(String name) throws IOException;
@Override
String[] getRepositoryNames() throws IOException;
String[] getRepositoryNames() throws RemoteException;
@Override
String getUser() throws IOException;
String getUser() throws RemoteException;
@Override
String[] getAllUsers() throws IOException;
String[] getAllUsers() throws RemoteException;
@Override
boolean canSetPassword() throws IOException;
boolean canSetPassword() throws RemoteException;
@Override
long getPasswordExpiration() throws IOException;
long getPasswordExpiration() throws RemoteException;
@Override
boolean setPassword(char[] saltedSHA256PasswordHash) throws IOException;
@Override
void connected() throws IOException;
void connected() throws RemoteException;
}
@@ -1,13 +1,12 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -109,39 +108,4 @@ public class SignatureCallback implements Callback, Serializable {
return null;
}
// private void writeObject(java.io.ObjectOutputStream out) throws IOException {
//
// out.defaultWriteObject();
//
// try {
// out.writeInt(certChain == null ? -1 : certChain.length);
// if (certChain != null) {
// for (int i = 0; i < certChain.length; i++) {
// out.writeObject(certChain[i].getEncoded());
// }
// }
// } catch (CertificateEncodingException e) {
// throw new IOException("Can not serialize certificate chain");
// }
// }
//
// private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
//
// in.defaultReadObject();
//
// try {
// int cnt = in.readInt();
// if (cnt >= 0) {
// CertificateFactory cf = CertificateFactory.getInstance("X509");
// certChain = new X509Certificate[cnt];
// for (int i = 0; i < cnt; i++) {
// byte[] bytes = (byte[]) in.readObject();
// certChain[i] = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(bytes));
// }
// }
// } catch (CertificateException e) {
// throw new IOException("Can not de-serialize certificate chain");
// }
// }
}
@@ -23,6 +23,7 @@ import org.jdom2.input.SAXBuilder;
import org.jdom2.output.XMLOutputter;
import ghidra.framework.store.*;
import ghidra.util.Msg;
import ghidra.util.xml.GenericXMLOutputter;
import ghidra.util.xml.XmlUtilities;
@@ -47,7 +48,7 @@ class CheckoutManager {
* @param item folder item
* @param create if true an empty checkout data file is written, else the
* initial data is read from the file.
* @throws IOException
* @throws IOException if create is true and checkouts data file creation failed
*/
CheckoutManager(LocalFolderItem item, boolean create) throws IOException {
this.item = item;
@@ -70,6 +71,7 @@ class CheckoutManager {
* @param checkoutType type of checkout
* @param user name of user requesting checkout
* @param version item version to be checked-out
* @param projectPath client-side project and path
* @return checkout data or null if exclusive checkout denied due to
* existing checkouts.
* @throws IOException if checkout fails
@@ -109,6 +111,7 @@ class CheckoutManager {
*
* @param checkoutId checkout ID to be updated
* @param version item version to be associated with checkout
* @throws IOException if item validation fails
*/
synchronized void updateCheckout(long checkoutId, int version) throws IOException {
validate();
@@ -133,7 +136,7 @@ class CheckoutManager {
* Terminate the specified checkout
*
* @param checkoutId checkout ID
* @throws IOException
* @throws IOException @throws IOException if item validation fails or data update fails
*/
synchronized void endCheckout(long checkoutId) throws IOException {
validate();
@@ -156,10 +159,11 @@ class CheckoutManager {
}
/**
* Returns true if the specified version of the associated item is
* checked-out.
* {@return true if the specified version of the associated item has
* one or more checkedouts}
*
* @param version the specific version to check for checkouts.
* @throws IOException if item validation fails
*/
synchronized boolean isCheckedOut(int version) throws IOException {
validate();
@@ -173,7 +177,8 @@ class CheckoutManager {
}
/**
* Returns true if the any version of the associated item is checked-out.
* {@return true if one or more checkouts exist for the associated item}
* @throws IOException if item validation fails
*/
synchronized boolean isCheckedOut() throws IOException {
validate();
@@ -181,10 +186,11 @@ class CheckoutManager {
}
/**
* Returns the checkout data corresponding to the specified checkout ID.
* Null is returned if checkout ID is not found.
* {@return the checkout data corresponding to the specified checkout ID.
* Null is returned if checkout ID is not found.}
*
* @param checkoutId checkout ID
* @throws IOException if item validation fails
*/
synchronized ItemCheckoutStatus getCheckout(long checkoutId) throws IOException {
validate();
@@ -192,8 +198,10 @@ class CheckoutManager {
}
/**
* Returns the checkout data for all existing checkouts of the associated
* item.
* {@return the checkout data for all existing checkouts of the associated
* item.}
*
* @throws IOException if item validation fails
*/
synchronized ItemCheckoutStatus[] getAllCheckouts() throws IOException {
validate();
@@ -207,6 +215,8 @@ class CheckoutManager {
* updated, the checkout data will be re-initialized from the file. This is
* undesirable and is only required when multiple instances of a
* LocalFolderItem are used for a specific item path (e.g., unit testing).
*
* @throws IOException if failed to read checkouts file
*/
private void validate() throws IOException {
if (LocalFileSystem.isRefreshRequired()) {
@@ -219,6 +229,11 @@ class CheckoutManager {
readCheckoutsFile();
success = true;
}
catch (IOException e) {
String msg = "Item validation failed: " + item.getPathName();
Msg.error(this, msg, e);
throw new IOException(msg);
}
finally {
if (!success) {
nextCheckoutId = oldNextCheckoutId;
@@ -231,9 +246,8 @@ class CheckoutManager {
/**
* Read data from checkout file.
*
* @throws IOException
* @throws IOException if reading checkout data fails
*/
@SuppressWarnings("unchecked")
private void readCheckoutsFile() throws IOException {
checkouts = new HashMap<>();
@@ -266,7 +280,7 @@ class CheckoutManager {
}
}
catch (org.jdom2.JDOMException je) {
throw new InvalidObjectException("Invalid checkouts file: " + checkoutsFile);
throw new IOException("Invalid checkouts file: " + checkoutsFile, je);
}
finally {
istream.close();
@@ -278,7 +292,7 @@ class CheckoutManager {
*
* @param coElement checkout data element
* @return checkout data for specified element
* @throws JDOMException
* @throws JDOMException if checkout data parse fails
*/
ItemCheckoutStatus parseCheckoutElement(Element coElement) throws JDOMException {
try {
@@ -302,7 +316,7 @@ class CheckoutManager {
/**
* Write checkout data file.
*
* @throws IOException
* @throws IOException if error writing checkout data file occurs
*/
private void writeCheckoutsFile() throws IOException {
@@ -341,14 +355,14 @@ class CheckoutManager {
oldFile = new File(checkoutsFile.getParentFile(), checkoutsFile.getName() + ".bak");
oldFile.delete();
if (!checkoutsFile.renameTo(oldFile)) {
throw new IOException("Failed to update checkouts: " + item.getPathName());
throw new IOException("Failed to update checkout file: " + checkoutsFile);
}
}
if (!tmpFile.renameTo(checkoutsFile)) {
if (oldFile != null) {
oldFile.renameTo(checkoutsFile);
}
throw new IOException("Failed to update checkouts: " + item.getPathName());
throw new IOException("Failed to update checkout file: " + checkoutsFile);
}
if (oldFile != null) {
oldFile.delete();
@@ -21,7 +21,6 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import ghidra.framework.store.*;
import ghidra.util.Msg;
import ghidra.util.ReadOnlyException;
import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
@@ -155,7 +154,7 @@ public abstract class LocalFolderItem implements FolderItem {
}
/**
* Returns hidden data directory.
* {@return data storage directory}
* NOTE: Even if a data directory is not required this method will still return one to
* allow removal of an unknown item type that may or may not use it.
*/
@@ -170,7 +169,7 @@ public abstract class LocalFolderItem implements FolderItem {
}
/**
* Return the oldest/minimum version.
* {@return the oldest/minimum version}
* @throws IOException thrown if an IO error occurs.
*/
abstract int getMinimumVersion() throws IOException;
@@ -178,7 +177,7 @@ public abstract class LocalFolderItem implements FolderItem {
/**
* Verify that the specified version of this item is not in use.
* @param version the specific version to check for versioned items.
* @throws FileInUseException
* @throws FileInUseException if specified item version is in use or unable to determine
*/
void checkInUse(int version) throws FileInUseException {
synchronized (fileSystem) {
@@ -188,7 +187,7 @@ public abstract class LocalFolderItem implements FolderItem {
isCheckedOut = checkoutMgr.isCheckedOut(version);
}
catch (IOException e) {
throw new FileInUseException(getName() + " versioning error", e);
throw new FileInUseException(getName() + " versioning error");
}
if (isCheckedOut) {
throw new FileInUseException(
@@ -203,7 +202,7 @@ public abstract class LocalFolderItem implements FolderItem {
/**
* Verify that this item is not in use.
* @throws FileInUseException
* @throws FileInUseException if item is in use or unable to determine
*/
void checkInUse() throws FileInUseException {
synchronized (fileSystem) {
@@ -216,7 +215,8 @@ public abstract class LocalFolderItem implements FolderItem {
isCheckedOut = checkoutMgr.isCheckedOut();
}
catch (IOException e) {
throw new FileInUseException(getName() + " versioning error", e);
// A bad checkouts file can cause this (check log)
throw new FileInUseException(getName() + " versioning error");
}
if (isCheckedOut) {
throw new FileInUseException(getName() + " is checked out");
@@ -231,7 +231,7 @@ public abstract class LocalFolderItem implements FolderItem {
/**
* Begin the check-in process for a versioned item.
* @param checkoutId assigned at time of checkout, becomes the check-in ID.
* @throws FileInUseException
* @throws FileInUseException if specified item version is in use or unable to determine
*/
void beginCheckin(long checkoutId) throws FileInUseException {
synchronized (fileSystem) {
@@ -244,7 +244,7 @@ public abstract class LocalFolderItem implements FolderItem {
status = checkoutMgr.getCheckout(checkinId);
}
catch (IOException e) {
throw new FileInUseException(getName() + " versioning error", e);
throw new FileInUseException(getName() + " versioning error");
}
String byMsg = status != null ? (" by: " + status.getUser()) : "";
throw new FileInUseException("Another checkin is in progress" + byMsg);
@@ -401,7 +401,7 @@ public abstract class LocalFolderItem implements FolderItem {
* never be the only version (i.e., minVersion will always be less
* than the currentVersion).
* @param user user name
* @throws IOException
* @throws IOException if item update failure occurs
*/
abstract void deleteMinimumVersion(String user) throws IOException;
@@ -411,7 +411,7 @@ public abstract class LocalFolderItem implements FolderItem {
* never be the only version (i.e., minVersion will always be less
* than the currentVersion).
* @param user user name
* @throws IOException
* @throws IOException if item update failure occurs
*/
abstract void deleteCurrentVersion(String user) throws IOException;
@@ -419,10 +419,11 @@ public abstract class LocalFolderItem implements FolderItem {
* Move this item into a newFolder which has a path of newPath.
* @param newFolder new parent directory/folder
* @param newStorageName new storage name
* @param newPath new parent path
* @throws DuplicateFileException
* @throws FileInUseException
* @throws IOException
* @param newFolderPath new parent path
* @param newName new item name
* @throws DuplicateFileException if detsination item already exists
* @throws FileInUseException if items appears to be in use
* @throws IOException if item update failure occurs
* @see ghidra.framework.store.FileSystem#moveItem
*/
void moveTo(File newFolder, String newStorageName, String newFolderPath, String newName)
@@ -831,8 +832,7 @@ public abstract class LocalFolderItem implements FolderItem {
return checkoutMgr != null && checkoutMgr.isCheckedOut();
}
catch (IOException e) {
Msg.error(getName() + " versioning error", e);
return true;
return true; // error already logged
}
}
return false;
@@ -879,8 +879,8 @@ public abstract class LocalFolderItem implements FolderItem {
* Update this non-versioned item with the contents of the specified item which must be
* within the same non-versioned fileSystem. If successful, the specified item will be
* removed after its content has been moved into this item.
* @param item
* @param checkoutVersion
* @param item source item for update of this item
* @param checkoutVersion version of current checkout
* @throws IOException if this file is not a checked-out non-versioned file
* or an IO error occurs.
*/
@@ -1,13 +1,12 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -18,6 +17,12 @@ package ghidra.framework.store.local;
public interface RepositoryLogger {
/**
* Append log with information related to specified folder/item path.
* @param path folder or item path
* @param msg message
* @param user associated user or null
*/
void log(String path, String msg, String user);
}
@@ -93,7 +93,7 @@ public class ApplicationKeyManagerFactory {
}
}
catch (IOException e) {
throw new KeyStoreException("Failed to examine keystore: " + keystorePath, e);
throw new KeyStoreException("Failed to open keystore: " + keystorePath, e);
}
int tryCount = 0;
@@ -1,13 +1,12 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -34,13 +33,4 @@ public class FileInUseException extends IOException {
super(msg);
}
/**
* Create a new FileInUseException with the given message and cause.
*
* @param msg the exception message.
*/
public FileInUseException(String msg, Throwable cause) {
super(msg, cause);
}
}
@@ -87,17 +87,15 @@ wrapper.java.additional.11=-Ddb.buffers.DataBuffer.compressedOutput=true
# timeouts to their maximum values.
#wrapper.java.debug.port=18200
# Uncomment to allow VisualVM Profiling to avoid "Rejected class serialization..." errors
# NOTE: A Java class serialization filter is added for RMI security assurance and should remain
# enabled during normal use.
#wrapper.java.additional.16=-Dghidra.server.serialization.filter.disabled=true
# Uncomment to enable remote use of VisualVM for profiling
# See JMX documentation for more information: http://docs.oracle.com/javase/8/docs/technotes/guides/management/agent.html
#wrapper.java.additional.17=-Dcom.sun.management.jmxremote.port=9010
#wrapper.java.additional.18=-Dcom.sun.management.jmxremote.local.only=false
#wrapper.java.additional.19=-Dcom.sun.management.jmxremote.authenticate=false
#wrapper.java.additional.20=-Dcom.sun.management.jmxremote.ssl=false
# Uncomment additional java properties below to enable remote use of VisualVM for profiling.
# See JMX documentation for more information:
# http://docs.oracle.com/javase/8/docs/technotes/guides/management/agent.html
# When JMX over RMI is in use the Ghidra Server serialization filters defined by
# file Ghidra/GhidraServer/data/serial.filter may need adjustment.
#wrapper.java.additional.16=-Dcom.sun.management.jmxremote.port=9010
#wrapper.java.additional.17=-Dcom.sun.management.jmxremote.local.only=false
#wrapper.java.additional.18=-Dcom.sun.management.jmxremote.authenticate=false
#wrapper.java.additional.19=-Dcom.sun.management.jmxremote.ssl=false
# YAJSW will by default assume a POSIX spawn for Linux and Mac OS X systems, unfortunately it has
# not yet been implemented for Mac OS X. The default process support within YAJSW for Mac OS X is
@@ -19,7 +19,6 @@ import static org.junit.Assert.*;
import java.io.File;
import java.io.InvalidClassException;
import java.rmi.RemoteException;
import java.rmi.UnmarshalException;
import java.security.Principal;
import java.util.HashSet;
@@ -117,11 +116,10 @@ public class GhidraServerSerialFilterFailureTest extends AbstractGhidraHeadlessI
serverHandle.getRepositoryServer(getBogusUserSubject(), new Callback[0]);
fail("serial filter rejection failed to perform");
}
catch (RemoteException e) {
catch (Exception e) {
Throwable cause = e.getCause();
assertTrue("expected remote unmarshall exception", cause instanceof UnmarshalException);
cause = cause.getCause();
assertTrue("expected remote invalid class exceptionn",
assertTrue("Expected remote unmarshall exception", e instanceof UnmarshalException);
assertTrue("Expected remote invalid class exceptionn",
cause instanceof InvalidClassException);
}
@@ -528,8 +528,6 @@ public class ServerTestUtil {
Thread.sleep(200);
if (isServerRegistered(portFactory.getRMIRegistryPort()) &&
canConnect(portFactory.getRMISSLPort())) {
Msg.info(ServerTestUtil.class,
"Successfully verified Ghidra Server registration and SSL port availability");
success = true;
return;
}
+27
View File
@@ -339,6 +339,33 @@ def getCurrentDateTimeLong() {
return formattedDate
}
/*********************************************************************************
* Returns true if 'project' has a direct or transitive API project dependency
* on the project with path 'targetPath'. The 'targetPath' should be specified
* in the form ":<project-name>"
*********************************************************************************/
boolean hasApiProjectDependency(Project project, String targetPath) {
def visited = [] as Set
def walk
walk = { Project p ->
if (!visited.add(p)) return false
def apiDeps = p.configurations.api
.allDependencies
.withType(org.gradle.api.artifacts.ProjectDependency)
if (apiDeps.any { it.dependencyProject.path == targetPath }) {
return true
}
return apiDeps.any { dep -> walk(dep.dependencyProject) }
}
walk(project)
}
/*********************************************************************************
* Returns a list of all the external library paths declared as dependencies for the
* given project
+20 -14
View File
@@ -148,8 +148,10 @@ def initTestJVM(Task task, String rootDirName) {
task.minHeapSize = xms
task.maxHeapSize = xmx
task.doFirst {
task.jvmArgs '-DupgradeProgramErrorMessage=' + upgradeProgramErrorMessage,
task.doFirst {
def args = [
'-DupgradeProgramErrorMessage=' + upgradeProgramErrorMessage,
'-DupgradeTimeErrorMessage=' + upgradeTimeErrorMessage,
'-Dlog4j.configurationFile=' + logPropertiesUrl,
'-Dghidra.test.property.batch.mode=true',
@@ -171,7 +173,10 @@ def initTestJVM(Task task, String rootDirName) {
'-Duser.language=en',
'-Djdk.attach.allowAttachSelf',
'-XX:TieredStopAtLevel=1',
'-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=' + debugPort,
// Note: this following arg is needed to enable remote debug.
//'-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:' + debugPort,
// Allow illegal reflective accesses
'--add-opens=java.base/java.util.concurrent=ALL-UNNAMED',
@@ -181,19 +186,9 @@ def initTestJVM(Task task, String rootDirName) {
'--add-opens=java.desktop/javax.swing=ALL-UNNAMED',
'--add-opens=java.desktop/javax.swing.text=ALL-UNNAMED'
// Note: this args are used to speed-up the tests, but are not safe for production code
// Note: these args are used to speed-up the tests, but are not safe for production code
// -noverify and -XX:TieredStopAtLevel=1
// Note: modern remote debug invocation;
// -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000
//
// We removed these lines, which should not be needed in modern JVMs
// -Xdebug
// -Xnoagent
// -Djava.compiler=NONE
// -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000
//
// TODO Future Updates:
// The test configuration should be updated to support all known modes of operation:
@@ -203,6 +198,17 @@ def initTestJVM(Task task, String rootDirName) {
// For better command-line usage we will need to update tests such that they can
// share a VM, enabling us to elimnate the use of 'forEver 1' in this file.
//
]
if (hasApiProjectDependency(project, ":FileSystem")) {
// If project has a FileSystem dependency we must assume there may be a
// Ghidra ApplicationConfiguration that will attempt to configure a
// serial filter factory which must be done in a lazy fashion when gradle
// test workers are used.
args << '-Djdk.serialFilterFactory=ghidra.framework.remote.GhidraSerialFilterFactory'
}
task.jvmArgs args
}
}
/*********************************************************************************
+20 -15
View File
@@ -310,8 +310,10 @@ def initTestJVM(Task task, String rootDirName) {
task.minHeapSize xms
task.maxHeapSize xmx
task.doFirst {
task.jvmArgs '-DupgradeProgramErrorMessage=' + upgradeProgramErrorMessage,
task.doFirst {
def args = [
'-DupgradeProgramErrorMessage=' + upgradeProgramErrorMessage,
'-DupgradeTimeErrorMessage=' + upgradeTimeErrorMessage,
'-Dlog4j.configurationFile=' + logPropertiesUrl,
'-Dghidra.test.property.batch.mode=true',
@@ -332,8 +334,10 @@ def initTestJVM(Task task, String rootDirName) {
'-Duser.language=en',
'-Djdk.attach.allowAttachSelf',
'-XX:TieredStopAtLevel=1',
'-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=' + debugPort,
// Note: this following arg is needed to enable remote debug.
//'-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:' + debugPort,
// Allow illegal reflective accesses
'--add-opens=java.base/java.util.concurrent=ALL-UNNAMED',
'--add-opens=java.desktop/sun.awt=ALL-UNNAMED',
@@ -342,18 +346,19 @@ def initTestJVM(Task task, String rootDirName) {
'--add-opens=java.desktop/javax.swing=ALL-UNNAMED',
'--add-opens=java.desktop/javax.swing.text=ALL-UNNAMED'
// Note: this args are used to speed-up the tests, but are not safe for production code
// -noverify and -XX:TieredStopAtLevel=1
// Note: these args are used to speed-up the tests, but are not safe for production code
// -noverify and -XX:TieredStopAtLevel=1
]
if (hasApiProjectDependency(project, ":FileSystem")) {
// If project has a FileSystem dependency we must assume there may be a
// Ghidra ApplicationConfiguration that will attempt to configure a
// serial filter factory which must be done in a lazy fashion when gradle
// test workers are used.
args << '-Djdk.serialFilterFactory=ghidra.framework.remote.GhidraSerialFilterFactory'
}
// Note: modern remote debug invocation;
// -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000
//
// We removed these lines, which should not be needed in modern JVMs
// -Xdebug
// -Xnoagent
// -Djava.compiler=NONE
// -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000
task.jvmArgs args
}
}