Skip to content

file: enforce auth realms on plugin-dispatched URL prefixes - #35

Open
JuliusBairaktaris wants to merge 1 commit into
openwrt:masterfrom
JuliusBairaktaris:auth-plugin-prefixes
Open

file: enforce auth realms on plugin-dispatched URL prefixes#35
JuliusBairaktaris wants to merge 1 commit into
openwrt:masterfrom
JuliusBairaktaris:auth-plugin-prefixes

Conversation

@JuliusBairaktaris

Copy link
Copy Markdown

uh_auth_check() is reached only from __handle_file_request(). uh_handle_request() consults dispatch_find(url, NULL) first and returns early when a handler matches, so requests routed to a .check_url handler never pass an auth check:

handler registration prefix
ubus ubus.c uh_ubus_check_url() conf.ubus_prefix
lua lua.c check_lua_url() conf.lua_prefix
ucode ucode.c check_ucode_url() conf.ucode_prefix

CGI registers .check_path instead and is matched from inside __handle_file_request(), after the auth check, which is why CGI-served paths are unaffected and the gap is easy to miss.

An operator configuring a config httpauth section — uhttpd.init create_httpauth() writes it to the realm file consumed by uh_auth_check() — for a path served by one of those handlers receives no authentication on that path, with no warning or error.

This patch evaluates the realm before dispatching, against the URL path with the query string removed. Realm matching is a prefix compare in URL space, and the plugin matchers use uh_path_match() on the same unmodified URL, so the two agree on what is covered.

Testing

Compiles clean under the package's own strict flags (-Os -Wall -Werror -Wmissing-declarations --std=gnu99, aarch64 cortex-a53 musl, gcc 15.3.0).

I want to be straight about the limits: this is compile-verified and derived from reading the dispatch path — I have not stood up a uhttpd with an httpauth realm on a ubus prefix and observed the 401 appear. If you'd like that before merging, say so and I'll produce it.

Note

__handle_file_request() has a second unauthenticated dispatch in the error-handler branch:

if (is_error_handler) {
    d = dispatch_find(url, NULL);
    if (d) {
        uh_invoke_handler(cl, d, url, NULL);   /* no auth check */

I left it alone — it is reachable only through conf.error_handler, which is administrator-configured rather than attacker-chosen, so it is a much weaker path. It is the same shape as the bug being fixed though, so if you'd prefer both handled here I'm happy to extend the patch.

Copilot AI review requested due to automatic review settings August 1, 2026 02:26

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Fixes a bypass where requests dispatched via .check_url handlers skip uh_auth_check() entirely, causing configured HTTP auth realms to not be enforced on ubus/lua/ucode prefixes.

Changes:

  • Added uh_dispatch_auth_check() to evaluate the auth realm for .check_url dispatches using the URL path without the query string.
  • Updated uh_handle_request() to run the auth check before invoking a matched plugin handler.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread file.c Outdated
Comment on lines +973 to +985
char path[PATH_MAX];
const char *auth, *q;
char *user, *pass;
size_t len;

q = strchr(url, '?');
len = q ? (size_t)(q - url) : strlen(url);

if (len >= sizeof(path))
len = sizeof(path) - 1;

memcpy(path, url, len);
path[len] = 0;
Comment thread file.c Outdated
memcpy(path, url, len);
path[len] = 0;

blobmsg_parse(hdr_policy, 1, tb, blob_data(cl->hdr.head), blob_len(cl->hdr.head));
uh_auth_check() is reached only from __handle_file_request(). uh_handle_request()
consults dispatch_find(url, NULL) first and returns early when a handler matches,
so requests routed to a .check_url handler never pass an auth check:

  ubus   ubus.c   uh_ubus_check_url()   conf.ubus_prefix
  lua    lua.c    check_lua_url()       conf.lua_prefix
  ucode  ucode.c  check_ucode_url()     conf.ucode_prefix

CGI registers .check_path instead and is matched from inside
__handle_file_request(), after the auth check, which is why CGI-served paths are
unaffected and the gap is easy to miss.

An operator configuring a `config httpauth` section (uhttpd.init writes it to the
realm file consumed by uh_auth_check) for a path served by one of those handlers
receives no authentication on that path, with no warning.

Evaluate the realm before dispatching, on the very URL dispatch_find() matched and
uh_invoke_handler() is about to receive, so the auth check and the plugin matcher
cannot disagree about what is covered.

The query string needs no removal: uh_auth_check() selects a realm by comparing
its leading strlen(realm->path) bytes, and a query can only follow the path, so
it can never turn a matching realm into a non-matching one.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants