Describe the bug
Java_org_cef_browser_CefMessageRouter_1N_N_1RemoveHandler decrements the reference counter for MessageRouterHandler object before returning. As a result, the object's refcount can drop to 0, causing it to be freed; if a handler uses CefMessageRouter::RemoveHandler to remove itself from a message router, the router will keep a (now dangling) reference to the handler in the browser query info map (https://bitbucket.org/chromiumembedded/cef/src/cfff1e30212d0c208ac0d0eb3d033c6af7133449/libcef_dll/wrapper/cef_message_router.cc#lines-320 and https://bitbucket.org/chromiumembedded/cef/src/cfff1e30212d0c208ac0d0eb3d033c6af7133449/libcef_dll/wrapper/cef_message_router.cc#lines-341:344). If the handler also triggers OnBeforeBrowse, pending query cleanup (https://bitbucket.org/chromiumembedded/cef/src/cfff1e30212d0c208ac0d0eb3d033c6af7133449/libcef_dll/wrapper/cef_message_router.cc#lines-283:288) could happen before the callback resolves and removes the dangling reference from query info map, causing a method lookup to be performed on a vtable of a stale object (https://bitbucket.org/chromiumembedded/cef/src/cfff1e30212d0c208ac0d0eb3d033c6af7133449/libcef_dll/wrapper/cef_message_router.cc#lines-514) - on Linux with glibc, this can cause a segfault.
Additionally, even though it seems virtually impossible due to the ridiculous setup required, this bug could be used to replace the vtable of the dangling handler, leading to arbitrary code execution.
To Reproduce
Steps to reproduce the behavior:
- Compile and run the code attached below.
- Press "edit" href a bunch of times.
import org.cef.CefApp;
import org.cef.CefClient;
import org.cef.CefSettings;
import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.browser.CefMessageRouter;
import org.cef.callback.CefQueryCallback;
import org.cef.handler.CefMessageRouterHandler;
import org.cef.handler.CefMessageRouterHandlerAdapter;
import org.cef.handler.CefRequestHandlerAdapter;
import org.cef.network.CefRequest;
import javax.swing.*;
import java.awt.*;
class UafComponent {
int i = 0;
CefBrowser browser;
CefMessageRouter router;
CefMessageRouterHandler handler;
public UafComponent(CefBrowser browser, CefMessageRouter router, CefMessageRouterHandler handler) {
this.browser = browser;
this.router = router;
this.handler = handler;
router.addHandler(handler, true);
browser.getClient().addMessageRouter(router);
}
public void updateHTML() {
router.removeHandler(handler);
System.out.println("Handler removed!");
handler = new CefMessageRouterHandlerAdapter() {
@Override
public boolean onQuery(CefBrowser browser, CefFrame frame, long queryId, String request, boolean persistent, CefQueryCallback callback) {
System.out.println("onQuery" + i);
updateHTML();
try {
Thread.sleep(1000);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
callback.success("onQuery" + i);
return true;
}
};
i++;
router.addHandler(handler, true);
browser.loadURL("data:text/html;charset=utf-8,<a href=\"javascript:cefQuery({request:"test", onSuccess: function(response) {}, onFailure: function(errorCode, errorMessage) {console.error("Error thrown:", errorCode, errorMessage)}});\">edit</a>");
}
}
public class Main {
public static void main(String[] args) {
System.loadLibrary("cef");
System.loadLibrary("jcef");
SwingUtilities.invokeLater(() -> {
// Cef settings
CefSettings settings = new CefSettings();
settings.windowless_rendering_enabled = false;
// Initialize the CefApp with settings
CefApp cefApp = CefApp.getInstance(settings);
CefClient client = cefApp.createClient();
client.addRequestHandler(new CefRequestHandlerAdapter() {
@Override
public boolean onBeforeBrowse(CefBrowser browser, CefFrame frame, CefRequest request, boolean userGesture, boolean isRedirect) {
System.out.println("onBeforeBrowse");
return false;
}
});
// Create the browser
CefBrowser browser = client.createBrowser("data:text/html;charset=utf-8,<a href=\"javascript:cefQuery({request:"test", onSuccess: function(response) {}, onFailure: function(errorCode, errorMessage) {console.error("Error thrown:", errorCode, errorMessage)}});\">edit</a>", false, false);
UafComponent component = new UafComponent(browser, CefMessageRouter.create(), new CefMessageRouterHandlerAdapter() {});
component.updateHTML();
// Embed in Swing UI
Component browserUI = browser.getUIComponent();
browserUI.setFocusable(true);
browserUI.requestFocusInWindow();
JFrame frame = new JFrame("Interactive JCEF Browser");
frame.getContentPane().add(browserUI, BorderLayout.CENTER);
frame.setSize(800, 600);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setVisible(true);
// Ensure it gets focus
browserUI.requestFocusInWindow();
});
}
}
Expected behavior
Expected behaviour: the app never crashes; actual behaviour: the app sometimes crashes.
Screenshots
If applicable, add screenshots to help explain your problem.
Versions (please complete the following information):
- OS: Debian 13
- Java Version: OpenJDK 21.0.11
- JCEF Version: Current master branch (commit 6d3e8ca)
- CEF Version: Current master branch (commit cfff1e30212d0c208ac0d0eb3d033c6af7133449)
Additional context
Describe the bug
Java_org_cef_browser_CefMessageRouter_1N_N_1RemoveHandlerdecrements the reference counter forMessageRouterHandlerobject before returning. As a result, the object's refcount can drop to 0, causing it to be freed; if a handler usesCefMessageRouter::RemoveHandlerto remove itself from a message router, the router will keep a (now dangling) reference to the handler in the browser query info map (https://bitbucket.org/chromiumembedded/cef/src/cfff1e30212d0c208ac0d0eb3d033c6af7133449/libcef_dll/wrapper/cef_message_router.cc#lines-320 and https://bitbucket.org/chromiumembedded/cef/src/cfff1e30212d0c208ac0d0eb3d033c6af7133449/libcef_dll/wrapper/cef_message_router.cc#lines-341:344). If the handler also triggersOnBeforeBrowse, pending query cleanup (https://bitbucket.org/chromiumembedded/cef/src/cfff1e30212d0c208ac0d0eb3d033c6af7133449/libcef_dll/wrapper/cef_message_router.cc#lines-283:288) could happen before the callback resolves and removes the dangling reference from query info map, causing a method lookup to be performed on a vtable of a stale object (https://bitbucket.org/chromiumembedded/cef/src/cfff1e30212d0c208ac0d0eb3d033c6af7133449/libcef_dll/wrapper/cef_message_router.cc#lines-514) - on Linux with glibc, this can cause a segfault.Additionally, even though it seems virtually impossible due to the ridiculous setup required, this bug could be used to replace the vtable of the dangling handler, leading to arbitrary code execution.
To Reproduce
Steps to reproduce the behavior:
Expected behavior
Expected behaviour: the app never crashes; actual behaviour: the app sometimes crashes.
Screenshots
If applicable, add screenshots to help explain your problem.
Versions (please complete the following information):
Additional context