From 7565f9cae3c0f9bd895ada417c3a05027d694a2e Mon Sep 17 00:00:00 2001 From: ghidra1 Date: Wed, 16 Sep 2026 13:48:00 -0400 Subject: [PATCH 1/3] GP-7281 Improved Ghidra URL connection error handling and other related fixes --- .../app/plugin/core/osgi/BundleHost.java | 25 +++++-- .../field/URLAnnotatedStringHandler.java | 68 +++++++++++-------- .../ghidra/GhidraOSGIStreamHandler.java | 38 +++++++++++ .../app/plugin/core/go/GhidraGoPlugin.java | 25 ++++--- .../ghidra/framework/client/ClientUtil.java | 2 +- .../framework/client/UrlAllowListManager.java | 6 ++ .../remote/GhidraObjectInputFilter.java | 4 +- .../remote/GhidraSerialFilterFactory.java | 6 +- .../java/ghidra/framework/model/Project.java | 2 +- .../framework/project/DefaultProject.java | 5 +- .../DefaultGhidraProtocolConnector.java | 1 + .../framework/protocol/ghidra/GhidraURL.java | 3 +- .../protocol/ghidra/GhidraURLConnection.java | 4 ++ .../protocol/ghidra/GhidraURLQuery.java | 7 +- .../framework/protocol/ghidra/Handler.java | 6 +- 15 files changed, 149 insertions(+), 53 deletions(-) create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/framework/protocol/ghidra/GhidraOSGIStreamHandler.java diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleHost.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleHost.java index 3af390e98a..30c213ee24 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleHost.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleHost.java @@ -31,12 +31,14 @@ import org.jgrapht.traverse.TopologicalOrderIterator; import org.osgi.framework.*; import org.osgi.framework.launch.Framework; import org.osgi.framework.wiring.*; +import org.osgi.service.url.URLStreamHandlerService; import generic.io.NullPrintWriter; import generic.jar.ResourceFile; import ghidra.framework.Application; import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.PluginTool; +import ghidra.framework.protocol.ghidra.*; import ghidra.util.Msg; import ghidra.util.task.TaskLauncher; import ghidra.util.task.TaskMonitor; @@ -417,10 +419,6 @@ public class BundleHost { // setup the cache path config.setProperty(Constants.FRAMEWORK_STORAGE, makeCacheDir()); - // prevent the use of Felix URL handlers which can interfere with URL.openConnection - // exception handling - config.put(FelixConstants.SERVICE_URLHANDLERS_PROP, "false"); - config.put(FelixConstants.LOG_LEVEL_PROP, "1"); if (STDERR_DEBUGGING) { config.put(FelixConstants.LOG_LEVEL_PROP, "999"); @@ -481,6 +479,8 @@ public class BundleHost { throw new OSGiException("Felix OSGi framework has no bundle context"); } + registerGhidraProtocolHandler(frameworkBundleContext); + addDebuggingListeners(); Bundle bundle = frameworkBundleContext.getBundle(); @@ -521,6 +521,23 @@ public class BundleHost { } } + /** + * Install Ghidra URL stream handler service to force standard use of {@code ghidra} protocol + * {@link Handler}. This bypasses the improper IOException propagation caused by + * {@code URLHandlersStreamHandlerProxy.openConnection(URL)} which forces itself to + * act as a proxy for all normal protocol handlers. + * + * @param context OSGI Bundle context + */ + private void registerGhidraProtocolHandler(BundleContext context) { + Hashtable properties = new Hashtable<>(); + properties.put("url.handler.protocol", new String[] { GhidraURL.PROTOCOL }); + context.registerService( + URLStreamHandlerService.class.getName(), + new GhidraOSGIStreamHandler(), + properties); + } + /** * Gets the host framework. * @return the OSGi framework diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/URLAnnotatedStringHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/URLAnnotatedStringHandler.java index 552b493801..ce4b4c7081 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/URLAnnotatedStringHandler.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/URLAnnotatedStringHandler.java @@ -111,41 +111,55 @@ public class URLAnnotatedStringHandler implements AnnotatedStringHandler { } } + private boolean isUnsupportedGhidraURL(URL url) { + try { + return GhidraURL.isGhidraURL(url) && GhidraURL.getProjectPathname(url) == null; + } + catch (Exception e) { + return true; + } + } + @Override public boolean handleMouseClick(String[] annotationParts, Navigatable navigatable, ServiceProvider serviceProvider) { + String urlString = annotationParts[1]; URL url = getURLForString(urlString); - if (url != null) { - - String protocol = url.getProtocol(); - if (!allowedProtocols.contains(protocol)) { - Msg.showError(this, null, "URL Access Not Allowed", - "Unsupported URL annotation protocol - " + allowedProtocolsStr + - " required:\n\n" + - urlString); - return false; - } - - if (!ClientUtil.getAllowListProvider().isAllowed(url)) { - Msg.showError(this, null, "URL Access Not Allowed", - "Access denied by Server Allow List"); - return false; - } - - if (GhidraURL.PROTOCOL.equals(url.getProtocol())) { - ProgramManager programManager = serviceProvider.getService(ProgramManager.class); - return programManager.openProgram(url, ProgramManager.OPEN_CURRENT) != null; - } - - BrowserLoader.display(url, null, serviceProvider); - return true; + if (url == null) { + Msg.showError(this, null, "Invalid URL", + "Invalid URL annotation: " + urlString); + return false; } - Msg.showError(this, null, "Invalid URL", - "Invalid URL annotation - not a valid URL: " + urlString); + String protocol = url.getProtocol(); + if (!allowedProtocols.contains(protocol)) { + Msg.showError(this, null, "URL Access Not Allowed", + "Unsupported URL annotation protocol - " + allowedProtocolsStr + + " required:\n" + urlString); + return false; + } - return false; + if (isUnsupportedGhidraURL(url)) { + Msg.showError(this, null, "Invalid Ghidra URL", + "Unsupported Ghidra URL annotation:\n" + urlString); + return false; + } + + if (!GhidraURL.isLocalURL(url) && !ClientUtil.getAllowListProvider().isAllowed(url)) { + Msg.showError(this, null, "URL Access Not Allowed", + "Access denied by Server Allow List"); + return false; + } + + if (GhidraURL.isGhidraURL(url)) { + + ProgramManager programManager = serviceProvider.getService(ProgramManager.class); + return programManager.openProgram(url, ProgramManager.OPEN_CURRENT) != null; + } + + BrowserLoader.display(url, null, serviceProvider); + return true; } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/framework/protocol/ghidra/GhidraOSGIStreamHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/framework/protocol/ghidra/GhidraOSGIStreamHandler.java new file mode 100644 index 0000000000..d2e25b8a75 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/framework/protocol/ghidra/GhidraOSGIStreamHandler.java @@ -0,0 +1,38 @@ +/* ### + * 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.protocol.ghidra; + +import java.io.IOException; +import java.net.URL; +import java.net.URLConnection; + +import org.osgi.service.url.AbstractURLStreamHandlerService; + +/** + * {@link GhidraOSGIStreamHandler} provides a Ghidra URL stream handler service for + * Felix OSGI. This allows direct use of the standard {@code ghidra} protocol + * {@link Handler} and bypasses the improper IOException propagation caused by + * {@code URLHandlersStreamHandlerProxy.openConnection(URL)}. + */ +public class GhidraOSGIStreamHandler extends AbstractURLStreamHandlerService { + + private static final Handler ghidraProtocolHandler = new Handler(); + + @Override + public URLConnection openConnection(URL url) throws IOException { + return ghidraProtocolHandler.openConnection(url); + } +} diff --git a/Ghidra/Features/GhidraGo/src/main/java/ghidra/app/plugin/core/go/GhidraGoPlugin.java b/Ghidra/Features/GhidraGo/src/main/java/ghidra/app/plugin/core/go/GhidraGoPlugin.java index 15bbcb74a2..4fc1c1aeec 100644 --- a/Ghidra/Features/GhidraGo/src/main/java/ghidra/app/plugin/core/go/GhidraGoPlugin.java +++ b/Ghidra/Features/GhidraGo/src/main/java/ghidra/app/plugin/core/go/GhidraGoPlugin.java @@ -59,18 +59,27 @@ public class GhidraGoPlugin extends Plugin implements ApplicationLevelOnlyPlugin private void processUrl(URL url) { - URL projectUrl = GhidraURL.getProjectURL(url); - Msg.info(this, "GhidraGo accepting the resource at " + projectUrl); FrontEndTool frontEndTool = AppInfo.getFrontEndTool(); - // Check for case where server access has already been blocked to - // launching tool and then failing to access program. - if (!ClientUtil.getAllowListProvider().isAllowed(url)) { - Msg.showError(this, frontEndTool.getActiveWindow(), "URL Access Not Allowed", - "Access denied by Server Allow List:\n" + projectUrl); + try { + URL projectUrl = GhidraURL.getProjectURL(url); + + // Check for case where remote server access has already been blocked to + // launching tool and then failing to access program. + if (!GhidraURL.isLocalURL(url) && !ClientUtil.getAllowListProvider().isAllowed(url)) { + Msg.showError(this, frontEndTool.getActiveWindow(), "URL Access Not Allowed", + "Access denied by Server Allow List:\n" + projectUrl); + return; + } + + Msg.info(this, "GhidraGo accepting the resource at " + projectUrl); + } + catch (Exception e) { + Msg.showError(this, frontEndTool.getActiveWindow(), "GhidraGo Failed", + "GhidraGo rejected invalid URL: " + url); return; } - + Swing.runLater(() -> { frontEndTool.toFront(); frontEndTool.accept(url); diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/ClientUtil.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/ClientUtil.java index 30b33370cc..8a6e9e439a 100644 --- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/ClientUtil.java +++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/ClientUtil.java @@ -402,7 +402,7 @@ public class ClientUtil { if (hdl == null) { Exception e = connectTask.getException(); if (e == null) { - return null; // cancelled by user + throw new CancelledException(); } if (e instanceof IOException) { throw (IOException) e; diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/UrlAllowListManager.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/UrlAllowListManager.java index 7213e89e4a..e43259523b 100644 --- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/UrlAllowListManager.java +++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/UrlAllowListManager.java @@ -23,6 +23,8 @@ import java.util.*; import javax.swing.event.ChangeListener; +import org.apache.commons.lang3.StringUtils; + import com.google.gson.*; import ghidra.framework.Application; @@ -113,6 +115,10 @@ public class UrlAllowListManager { */ public static synchronized Boolean getAccess(String protocol, String host, int port) { + if (StringUtils.isBlank(protocol) || StringUtils.isBlank(host)) { + return false; + } + // Always allow access to localhost if alwaysAllowLocalAccess is true if (alwaysAllowLocalAccess && ("localhost".equals(host) || "127.0.0.1".equals(host))) { return true; diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/remote/GhidraObjectInputFilter.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/remote/GhidraObjectInputFilter.java index 9f708d1864..b626849c35 100644 --- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/remote/GhidraObjectInputFilter.java +++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/remote/GhidraObjectInputFilter.java @@ -513,8 +513,8 @@ public class GhidraObjectInputFilter implements ObjectInputFilter { * @param filterFile serial filter file * @param sourceNameSupplier source name supplied for use during logging, or null. It * is assumed that a the current thread may be used to differentiate a client connection - * over which the serialization is occuring. - * @throws IllegalStateException if error occured building or installing serial input filter + * over which the serialization is occurring. + * @throws IllegalStateException if error occurred building or installing serial input filter * and related filter factory. */ public static void configureServerSerialFilter(ResourceFile filterFile, diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/remote/GhidraSerialFilterFactory.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/remote/GhidraSerialFilterFactory.java index e4b550da41..312dd286e3 100644 --- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/remote/GhidraSerialFilterFactory.java +++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/remote/GhidraSerialFilterFactory.java @@ -23,7 +23,7 @@ import java.util.function.BinaryOperator; * {@link GhidraSerialFilterFactory} provides the serial filter factory which imposes * {@link GhidraObjectInputFilter} as a global serial input filter. *

- * NOTE: With the use of Gradle test JVM instances it may be neccessary for those instances + * NOTE: With the use of Gradle test JVM instances it may be necessary for those instances * to specify this class as the serial filter factory and rely on lazy initialization of the * {@link GhidraObjectInputFilter global serial filter}. *

@@ -38,7 +38,7 @@ public class GhidraSerialFilterFactory implements BinaryOperator
 	 * See {@link java.io.ObjectInputFilter.Config#setSerialFilterFactory(java.util.function.BinaryOperator)}.
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/Project.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/Project.java
index 8dc04de48f..4172f8bd3f 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/Project.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/model/Project.java
@@ -87,7 +87,7 @@ public interface Project extends AutoCloseable, Iterable {
 	 * @param visible true if project may be made visible or false if hidden.  Hidden viewed
 	 * projects are used when only life-cycle management is required (e.g., close view project 
 	 * when this project is closed).
-	 * @return project data for this view
+	 * @return project data for this view or null if user cancels operation (e.g., cancels password prompt)
 	 * @throws IOException if this project is closed, an invalid URL is specified, or failed to 
 	 * open/connect to project/repository.
 	 */
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/DefaultProject.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/DefaultProject.java
index 5d13d2aefc..f441ec50af 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/DefaultProject.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/DefaultProject.java
@@ -259,7 +259,10 @@ public class DefaultProject implements Project {
 		StatusCode responseCode = c.getStatusCode();
 		switch (responseCode) {
 			case OK:
-				break;
+				break; // project data is established below 
+
+			case CANCELLED:
+				return null; // e.g., user cancelled password request
 
 			case UNAUTHORIZED:
 				throw new IOException("Authorization failure");
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/DefaultGhidraProtocolConnector.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/DefaultGhidraProtocolConnector.java
index fbf8d289c0..d16cab350d 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/DefaultGhidraProtocolConnector.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/DefaultGhidraProtocolConnector.java
@@ -72,6 +72,7 @@ public class DefaultGhidraProtocolConnector extends GhidraProtocolConnector {
 			ClientUtil.getRepositoryServer(url.getHost(), url.getPort(), true);
 		if (!repositoryServerAdapter.isConnected()) {
 			if (repositoryServerAdapter.isCancelled()) {
+				statusCode = StatusCode.CANCELLED;
 				return statusCode;
 			}
 			Throwable t = repositoryServerAdapter.getLastConnectError();
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURL.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURL.java
index 6ae1e7d591..79297c4cd8 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURL.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURL.java
@@ -608,8 +608,9 @@ public class GhidraURL {
 	 * 
 	 * @param ghidraUrl Ghidra local or remote file/folder URL (server-only URL not permitted)
 	 * @return pathname of file or folder
+	 * @throws IllegalArgumentException if an invalid URL is specified
 	 */
-	public static String getProjectPathname(URL ghidraUrl) {
+	public static String getProjectPathname(URL ghidraUrl) throws IllegalArgumentException {
 
 		try {
 			URI uri = ghidraUrl.toURI();
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURLConnection.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURLConnection.java
index efa100a385..429001733f 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURLConnection.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURLConnection.java
@@ -50,6 +50,10 @@ public class GhidraURLConnection extends URLConnection {
 		 * This status code occurs when project is locked (i.e., in use).
 		 */
 		LOCKED(423, "Locked Project"),
+		/**
+		 * Ghidra Status-Code 499: Connect Request Cancelled (likely during password prompt)
+		 */
+		CANCELLED(499, "Client Cancelled Request"),
 		/**
 		 * Ghidra Status-Code 503: Unavailable.
 		 * This status code includes a variety of connection errors
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURLQuery.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURLQuery.java
index 4495c9005a..bb7684072b 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURLQuery.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURLQuery.java
@@ -165,11 +165,12 @@ public class GhidraURLQuery {
 			c.setReadOnly(readOnly); // writable repository connection
 			obj = c.getContent(); // read-only access
 			status = c.getStatusCode();
+			if (status == StatusCode.CANCELLED) {
+				throw new CancelledException();
+			}
 		}
 		catch (IOException e) {
-			if (status == null) {
-				status = StatusCode.UNAVAILABLE;
-			}
+			status = StatusCode.UNAVAILABLE;
 			resultHandler.handleError("URL Connection Error", e.getMessage(), ghidraUrl, e);
 		}
 
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/Handler.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/Handler.java
index d303c7f69f..bc67d95154 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/Handler.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/Handler.java
@@ -19,6 +19,8 @@ import java.io.IOException;
 import java.net.*;
 import java.util.*;
 
+import org.apache.commons.lang3.StringUtils;
+
 import ghidra.framework.client.ClientUtil;
 import ghidra.framework.remote.GhidraServerHandle;
 import ghidra.util.Msg;
@@ -71,8 +73,8 @@ public class Handler extends URLStreamHandler {
 		}
 
 		if (url.getAuthority() != null) {
-			// assume standard ghidra URL (ghidra://...) - query not allowed
-			return url.getQuery() == null;
+			// assume standard ghidra URL (ghidra://host...) - query not allowed
+			return !StringUtils.isBlank(url.getHost()) && url.getQuery() == null;
 		}
 		try {
 			return getProtocolExtensionHandler(url) != null;

From d6b03b9bca26f0b0ea48342f8c0304520a1d1b93 Mon Sep 17 00:00:00 2001
From: ghidra1 
Date: Wed, 16 Sep 2026 18:04:14 -0400
Subject: [PATCH 2/3] GP-7283 Corrected ELF section permissions determination

---
 .../app/util/opinion/ElfProgramBuilder.java   | 29 ++++++++++++-------
 1 file changed, 18 insertions(+), 11 deletions(-)

diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfProgramBuilder.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfProgramBuilder.java
index dfa006cade..4c39fe862e 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfProgramBuilder.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/ElfProgramBuilder.java
@@ -3214,8 +3214,6 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper {
 		long loadSizeBytes = elfProgramHeader.getAdjustedLoadSize();
 		long fullSizeBytes = elfProgramHeader.getAdjustedMemorySize();
 
-		boolean maintainExecuteBit = elf.getSectionHeaderCount() == 0;
-
 		if (fullSizeBytes <= 0) {
 			if (!space.isLoadedMemorySpace() && loadSizeBytes > 0) {
 				fullSizeBytes = loadSizeBytes;
@@ -3239,16 +3237,12 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper {
 
 			String comment = getSectionComment(addr, fullSizeBytes, space.getAddressableUnitSize(),
 				elfProgramHeader.getDescription(), address.isLoadedMemoryAddress());
-			if (!maintainExecuteBit && elfProgramHeader.isExecute()) {
-				comment += " (disabled execute bit)";
-			}
 
 			String blockName = getSegmentName(elfProgramHeader, segmentNumber);
 			if (loadSizeBytes != 0) {
 				addInitializedMemorySection(elfProgramHeader, elfProgramHeader.getOffset(),
 					loadSizeBytes, address, blockName, elfProgramHeader.isRead(),
-					elfProgramHeader.isWrite(),
-					maintainExecuteBit ? elfProgramHeader.isExecute() : false, comment,
+					elfProgramHeader.isWrite(), elfProgramHeader.isExecute(), comment,
 					isFragmentationOK,
 					elfProgramHeader.getType() == ElfProgramHeaderConstants.PT_LOAD);
 			}
@@ -3435,11 +3429,11 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper {
 		}
 
 		Address address = null;
+		ElfProgramHeader loadHeader = elf.getProgramLoadHeaderContaining(addr);
 
 		if (sectionByteLength == 0 &&
 			elfSectionToLoad.getType() == ElfSectionHeaderConstants.SHT_PROGBITS) {
 			// Check for and consume uninitialized portion of PT_LOAD segment if possible
-			ElfProgramHeader loadHeader = elf.getProgramLoadHeaderContaining(addr);
 			if (loadHeader != null) {
 				// NOTE: should never apply to relocatable ELF
 				Address segmentStart = getSegmentLoadAddress(loadHeader);
@@ -3478,6 +3472,19 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper {
 
 		final String blockName = elfSectionToLoad.getNameAsString();
 
+		boolean isExecute = elfSectionToLoad.isExecutable();
+		boolean isWrite = elfSectionToLoad.isWritable();
+		boolean isRead = true;
+
+		if (loadHeader != null) {
+			// If PT_LOAD exists, defer to it for permissions
+			// NOTE: This does not handle a section not fully contained within a program 
+			// header loaded region
+			isExecute = loadHeader.isExecute();
+			isWrite = loadHeader.isWrite();
+			isRead = loadHeader.isRead();
+		}
+
 		try {
 			if (loadOffset == -1 ||
 				elfSectionToLoad.getType() == ElfSectionHeaderConstants.SHT_NOBITS) {
@@ -3489,7 +3496,7 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper {
 					getSectionComment(addr, sectionByteLength, space.getAddressableUnitSize(),
 						elfSectionToLoad.getTypeAsString(), address.isLoadedMemoryAddress());
 				addUninitializedMemorySection(elfSectionToLoad, sectionByteLength, address,
-					blockName, true, elfSectionToLoad.isWritable(), elfSectionToLoad.isExecutable(),
+					blockName, isRead, isWrite, isExecute,
 					comment, false);
 			}
 			else {
@@ -3497,8 +3504,8 @@ class ElfProgramBuilder extends MemorySectionResolver implements ElfLoadHelper {
 					getSectionComment(addr, sectionByteLength, space.getAddressableUnitSize(),
 						elfSectionToLoad.getTypeAsString(), address.isLoadedMemoryAddress());
 				addInitializedMemorySection(elfSectionToLoad, loadOffset, sectionByteLength,
-					address, blockName, elfSectionToLoad.isAlloc(), elfSectionToLoad.isWritable(),
-					elfSectionToLoad.isExecutable(), comment, false, elfSectionToLoad.isAlloc());
+					address, blockName, isRead, isWrite,
+					isExecute, comment, false, elfSectionToLoad.isAlloc());
 			}
 		}
 		catch (AddressOverflowException e) {

From fbe86aacaa5351b3f0f88efef24fed403afcfafa Mon Sep 17 00:00:00 2001
From: ghidra1 
Date: Thu, 17 Sep 2026 13:24:53 -0400
Subject: [PATCH 3/3] GP-1 Correct GP-6643 regression with resolving ELF
 external symbols to libraries

---
 .../program/util/ExternalSymbolResolver.java  | 22 ++++++++++++++-----
 1 file changed, 16 insertions(+), 6 deletions(-)

diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/util/ExternalSymbolResolver.java b/Ghidra/Features/Base/src/main/java/ghidra/program/util/ExternalSymbolResolver.java
index c511deda76..58503ed9b7 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/program/util/ExternalSymbolResolver.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/program/util/ExternalSymbolResolver.java
@@ -212,6 +212,9 @@ public class ExternalSymbolResolver implements Closeable {
 			logger.accept("\t%d external symbols resolved, %d remain unresolved"
 					.formatted(getResolvedSymbolCount(), unresolvedExternalFunctionIds.size()));
 			for (ExtLibInfo extLib : extLibs) {
+				if (Library.UNKNOWN.equals(extLib.getName())) {
+					continue;
+				}
 				String libPath = extLib.getAssociatedProgramPath();
 				String loggedLibPath = libPath != null ? libPath : "missing";
 				if (extLib.problem != null) {
@@ -269,7 +272,9 @@ public class ExternalSymbolResolver implements Closeable {
 				try (Transaction tx = program.openTransaction("Resolve External Symbols")) {
 					for (ExtLibInfo extLib : extLibs) {
 						monitor.checkCancelled();
-						resolveSymbolsToLibrary(extLib);
+						if (extLib.libProgram != null) {
+							resolveSymbolsToLibrary(extLib);
+						}
 					}
 				}
 			}
@@ -292,7 +297,7 @@ public class ExternalSymbolResolver implements Closeable {
 				Program libProg = libPath != null ? getLibraryProgram(libPath) : null;
 				Throwable problem =
 					libProg == null && libPath != null ? problemLibraries.get(libPath) : null;
-				result.add(new ExtLibInfo(lib, problem));
+				result.add(new ExtLibInfo(lib, libProg, problem));
 			}
 			return result;
 		}
@@ -322,7 +327,7 @@ public class ExternalSymbolResolver implements Closeable {
 				ExternalLocation extLoc = externalManager.getExternalLocation(s);
 				String extLocName =
 					Objects.requireNonNullElse(extLoc.getOriginalImportedName(), extLoc.getLabel());
-				if (isExportedSymbol(program, extLocName)) {
+				if (isExportedSymbol(extLib.libProgram, extLocName)) {
 					try {
 						s.setNamespace(extLib.lib);
 						idIterator.remove();
@@ -360,6 +365,7 @@ public class ExternalSymbolResolver implements Closeable {
 		private class ExtLibInfo {
 
 			final Library lib;
+			final Program libProgram; // may be null
 			final List resolvedSymbols = new ArrayList<>();
 			final Throwable problem;
 
@@ -367,13 +373,15 @@ public class ExternalSymbolResolver implements Closeable {
 			 * Define external Library dependency associated with {@link ProgramSymbolResolver}
 			 * instance.
 			 * @param lib external library dependency
-			 * @param problem exception which occured while accessing Library
+			 * @param libProgram imported or discovered program which corresponds to lib, or null if not found
+			 * @param problem exception which occurred while accessing Library
 			 */
-			ExtLibInfo(Library lib, Throwable problem) {
+			ExtLibInfo(Library lib, Program libProgram, Throwable problem) {
 				if (program != lib.getSymbol().getProgram()) {
 					throw new AssertionError("Program mismatch");
 				}
 				this.lib = lib;
+				this.libProgram = libProgram;
 				this.problem = problem;
 			}
 
@@ -416,7 +424,9 @@ public class ExternalSymbolResolver implements Closeable {
 	 * @return true if program publishes a symbol the specified name
 	 */
 	private static boolean isExportedSymbol(Program program, String name) {
-
+		if (program == null) {
+			return false;
+		}
 		for (Symbol s : program.getSymbolTable().getLabelOrFunctionSymbols(name, null)) {
 			if (s.isExternalEntryPoint()) {
 				return true;