From nobody Sat Oct 4 00:22:22 2025 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E15DC309DDB for ; Fri, 22 Aug 2025 14:27:54 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755872876; cv=none; b=RhKoSZ09DBx+km/3zIhYGQQHn321PqrbRAwhGJ/Ml9KANm48U6rZctghKpIRlrzGW1/leAhn8sbpu50/LImY8bwwbUeMtuvBA1ZW85yk3TC4U9fCpYBhm7TTDB9zbwPv0Y4sNrNHLF5quCDcca/BR2/UMkWkx2YcRpIK8kU72tg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755872876; c=relaxed/simple; bh=rCTWizcS8tppI1Pb0q/Qf3pwJuoMcIAjRl0Y1SBqJ7k=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=sMsnegrcV/YEzAtiacbu1LEaa5hL9DKKh0B5FW4mIXWbHwpU6EecsrvcLGwoARpihrgtP7cLc37WWmuBeL+DW4nqH8QMQr5ulpd6xXG8Ygcz/TQieLb6370uXwmqrhjXgBVNmDHb/n/ix1VUwROGRapcnsDBD9SYFp4QRuDV3VY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=LS/FdKZx; arc=none smtp.client-ip=170.10.129.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="LS/FdKZx" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1755872873; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=MyiW9WG9d02tUGD/bkZ0ruTVUbMBtZ9QTexYOI6sGjk=; b=LS/FdKZxEN6o9hW2CaQp8Bucu91jvLAgaZrNTthKAB85jQb0jf+L1Jd88iH9MSQf/7hYBq vPkKOFv+Ks4RTKERGR8aDz3+XJXScDETif5IIf/bmsrMUb1vSL95mQhmvlBXlcZK+AGr0G q7AnLkljjmsiDXgTY2NhDcfECJrwSU8= Received: from mx-prod-mc-02.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-413-xBDlz-VGM3KQFE3OqAiivQ-1; Fri, 22 Aug 2025 10:22:39 -0400 X-MC-Unique: xBDlz-VGM3KQFE3OqAiivQ-1 X-Mimecast-MFC-AGG-ID: xBDlz-VGM3KQFE3OqAiivQ_1755872558 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-02.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 762F31956087; Fri, 22 Aug 2025 14:22:38 +0000 (UTC) Received: from warthog.procyon.org.com (unknown [10.42.28.132]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 3B68C1955F24; Fri, 22 Aug 2025 14:22:37 +0000 (UTC) From: David Howells To: Jarkko Sakkinen Cc: David Howells , keyrings@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 7/7] request-key: Support the promised multiwildcard matching Date: Fri, 22 Aug 2025 15:22:14 +0100 Message-ID: <20250822142215.2475014-8-dhowells@redhat.com> In-Reply-To: <20250822142215.2475014-1-dhowells@redhat.com> References: <20250822142215.2475014-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 Content-Type: text/plain; charset="utf-8" The manual page for request-key.conf says that multiple wildcards can be used in match elements in the config files, so implement this feature. Also add a flag, "--test-match", to allow that match function to be tested directly, e.g.: request-key --test-match "**a*a***i**a**a**" antidisestablishmentarianism Signed-off-by: David Howells Reviewed-by: Jarkko Sakkinen --- man/request-key.8 | 19 +++- request-key.c | 252 ++++++++++++++++++++++++++++++++++++---------- 2 files changed, 217 insertions(+), 54 deletions(-) diff --git a/man/request-key.8 b/man/request-key.8 index 90121d9..7cec6e8 100644 --- a/man/request-key.8 +++ b/man/request-key.8 @@ -16,6 +16,7 @@ request\-key \- handle key instantiation callback request= s from the kernel \fB/sbin/request\-key\fP \fIop key uid gid t-ring p-ring s-ring\fP [\fIinf= o\fP] \fB/sbin/request\-key -d\fP [\fB-lnv\fP] [\fB-D\fP \fIdesc\fP] \fIop key\f= P... \fB/sbin/request\-key --check\fP [\fB-lnv\fP] \fItype desc\fP [\fIinfo\fP]= [\fIop\fP] +\fB/sbin/request\-key --match\fP [\fB-v\fP] \fIpattern datum\fP [\fIdatum.= ..\fP] .fi .SH DESCRIPTION This program is invoked by the kernel when the kernel is asked for a key t= hat @@ -65,6 +66,10 @@ If this is provide, then a simpler debugging mode is eng= aged that defaults most of the arguments, but otherwise operates much the same as '-d'. .IP \fB--help\fP Print help text and exit. +.IP \fB--match\fP +Test the pattern matcher. The first parameter is the pattern and all +subsequent parameters are strings to try matching against that. The progr= am +exits 1 if any match failure occurs, 0 if all succeed. .IP \fB--version\fP Print the program version and exit. .SH EXAMPLES @@ -84,9 +89,19 @@ request-key --check user debug:bar request-key --check user debug:bar foo request-key --check user debug:bar foo create .fi +.PP +Pattern match testing can be done directly without invoking any of the loo= kup +code and without reference to the configuration, e.g.: +.PP +.nf +request-key --match "debug:*" debug:a user:b user:debug:c +request-key --match -v "*a*a***a*a**a**" antidisestablishmentarianism +.fi +.PP .SH ERRORS -All errors will be logged to the syslog unless \fB-n\fP or \fB--check\fP a= re -given. Errors will also be logged to stderr if \fB-v\fP is given. +All errors will be logged to the syslog unless one of \fB-n\fP, \fB--check= \fP +or \fB--match\fP are given. Errors will also be logged to stderr if \fB-v= \fP +is given. .SH FILES .ul /etc/request\-key.d/*.conf diff --git a/request-key.c b/request-key.c index 302d083..97afeee 100644 --- a/request-key.c +++ b/request-key.c @@ -16,6 +16,7 @@ * data: command name, e.g.: "/home/dhowells/request-key-create.sh" */ =20 +#define _GNU_SOURCE #include #include #include @@ -56,6 +57,7 @@ struct parameters { }; =20 static int verbosity; +static int match_debug; static int xlocaldirs; static int xnolog; static int debug_mode; @@ -81,8 +83,8 @@ static void pipe_to_program(struct parameters *params, char **argv) __attribute__((noreturn)); =20 -static int match(const char *pattern, int plen, const char *datum, int dle= n, - unsigned int *wildness); +static int match(const char *pattern, unsigned int plen, + const char *datum, unsigned int dlen); =20 static void debug(const char *fmt, ...) __attribute__((format(printf, 1, 2= ))); static void debug(const char *fmt, ...) @@ -153,6 +155,7 @@ static const char help_text[] =3D "Usage: request-key [OPTIONS] [PARAMS]\n" " request-key [OPTIONS] -d -D [PARAMS]\n" " request-key [OPTIONS] --check [] []\n" + " request-key [OPTIONS] --match [datum ...]\n" "\n" "Where the required parameters, [PARAMS], are, in order:\n" " : The operation type (e.g. 'create')\n" @@ -168,6 +171,7 @@ static const char help_text[] =3D " -d : Debug mode for direct cmdline testing\n" " -D : Description for debug mode\n" " -l : Use config from local dir, not /etc\n" + " -M : Enable string-matching algorithm debugging\n" " -n : Don't log to syslog\n" " -v : Turn up verbosity (can use multiple times)\n" " --help : Print this text and exit\n" @@ -191,12 +195,20 @@ static const char help_text[] =3D " ./request-key --check user debug:bar\n" " ./request-key --check user debug:bar foo\n" " ./request-key --check user debug:bar foo create\n" + "\n" + "Another test mode is available ('--match') that allows the\n" + "pattern match evaluator to be tested against a bunch of sample\n" + "strings. For each string, either 'No' or 'Yes(n)' is printed,\n" + "where 'n' is the number of wild-matched characters. For example:\n" + "\n" + " ./request-key --match \"**foo**bar*\" \"foobar\"\n" ; =20 static struct option long_options[] =3D { { .name =3D "help", .val =3D 1 }, { .name =3D "version", .val =3D 2 }, { .name =3D "check", .val =3D 3 }, + { .name =3D "match", .val =3D 4 }, {} }; =20 @@ -236,6 +248,12 @@ int main(int argc, char *argv[]) debug_mode =3D 2; xnolog =3D 1; break; + case 4: + if (debug_mode) + cmderror("Multiple debug modes specified\n"); + debug_mode =3D 3; + xnolog =3D 1; + break; case 'D': test_desc =3D optarg; break; @@ -351,6 +369,30 @@ int main(int argc, char *argv[]) params.process_keyring =3D "undef-pring"; params.session_keyring =3D "undef-sring"; break; + + case 3: + const char *pattern; + int e =3D 0; + + if (verbosity) + match_debug =3D 1; + if (argc < 2) + cmderror("Insufficient arguments\n"); + pattern =3D argv[0]; + argv++; + argc--; + for (; argc; argc--) { + int m =3D match(pattern, strlen(pattern), + argv[0], strlen(argv[0])); + if (m >=3D 0) { + printf("Yes(%d)\n", m); + } else { + printf("No\n"); + e =3D 3; + } + argv++; + } + exit(e); } =20 debug("Key type: %s\n", params.key_type); @@ -450,6 +492,7 @@ static void scan_conf_file(struct parameters *params, i= nt dirfd, const char *con for (confline =3D 1;; confline++) { unsigned int wildness[4] =3D {}; unsigned int len; + int m; =20 /* read the file line-by-line */ if (!fgets(buf, sizeof(buf), conf)) { @@ -478,9 +521,10 @@ static void scan_conf_file(struct parameters *params, = int dirfd, const char *con goto syntax_error; *p =3D 0; =20 - if (!match(q, p - q, params->op, params->oplen, &wildness[0])) + m =3D match(q, p - q, params->op, params->oplen); + if (m < 0) continue; - + wildness[0] +=3D m; p++; =20 /* attempt to match the type */ @@ -494,8 +538,10 @@ static void scan_conf_file(struct parameters *params, = int dirfd, const char *con goto syntax_error; *p =3D 0; =20 - if (!match(q, p - q, params->key_type, params->ktlen, &wildness[1])) + m =3D match(q, p - q, params->key_type, params->ktlen); + if (m < 0) continue; + wildness[1] +=3D m; =20 p++; =20 @@ -510,9 +556,10 @@ static void scan_conf_file(struct parameters *params, = int dirfd, const char *con goto syntax_error; *p =3D 0; =20 - if (!match(q, p - q, params->key_desc, params->kdlen, &wildness[2])) + m =3D match(q, p - q, params->key_desc, params->kdlen); + if (m < 0) continue; - + wildness[2] +=3D m; p++; =20 /* attempt to match the callout info */ @@ -526,8 +573,10 @@ static void scan_conf_file(struct parameters *params, = int dirfd, const char *con goto syntax_error; *p =3D 0; =20 - if (!match(q, p - q, params->callout_info, params->cilen, &wildness[3])) + m =3D match(q, p - q, params->callout_info, params->cilen); + if (m < 0) continue; + wildness[3] +=3D m; =20 p++; =20 @@ -569,75 +618,174 @@ syntax_error: line_error("Syntax error\n"); } =20 +static void mdebug(const char *msg, + const char *pattern, unsigned int plen, + const char *datum, unsigned int dlen) +{ + if (match_debug) + printf("- %s(%*.*s,%*.*s)\n", + msg, plen, plen, pattern, dlen, dlen, datum); +} + +static void mdebug2(const char *msg) +{ + if (match_debug) + printf("- %s\n", msg); +} + /*************************************************************************= ****/ /* * attempt to match a datum to a pattern * - one asterisk is allowed anywhere in the pattern to indicate a wildcard - * - returns true if matched, false if not - * - adds the total number of chars skipped by wildcard to *_wildness + * - returns -1 if not matched or the number of chars skipped by wildcard */ -static int match(const char *pattern, int plen, const char *datum, int dle= n, - unsigned int *_wildness) +static int match(const char *pattern, unsigned int plen, + const char *datum, unsigned int dlen) { - const char *asterisk; - int n; + const char *left, *right; + unsigned int wild =3D 0, n; =20 if (verbosity >=3D 2) debug("match(%*.*s,%*.*s)", plen, plen, pattern, dlen, dlen, datum); + mdebug("match", pattern, plen, datum, dlen); =20 - asterisk =3D memchr(pattern, '*', plen); - if (!asterisk) { - /* exact match only if no wildcard */ - if (plen =3D=3D dlen && memcmp(pattern, datum, dlen) =3D=3D 0) - goto yes; + if (plen <=3D 0) goto no; + + /* Special case "*" matches everything */ + if (plen =3D=3D 1 && pattern[0] =3D=3D '*') { + wild =3D dlen; + goto yes; } =20 - /* the datum mustn't be shorter than the pattern without the asterisk */ - if (dlen < plen - 1) + left =3D memchr(pattern, '*', plen); + if (!left) { + /* No wildcard: exact match only. */ + mdebug2("exact-only"); + if (plen =3D=3D dlen && memcmp(pattern, datum, dlen) =3D=3D 0) { + wild =3D 0; + goto yes; + } goto no; + } =20 - n =3D asterisk - pattern; - if (n =3D=3D 0) { - /* wildcard at beginning of pattern */ - pattern++; - if (!*pattern) - goto yes_wildcard; /* "*" matches everything */ + if (left > pattern) { + /* Check prefix matches. */ + mdebug("prefix", pattern, left - pattern, datum, dlen); + if (dlen < left - pattern || + memcmp(pattern, datum, left - pattern) !=3D 0) { + mdebug2("prefix-x"); + goto no; + } + n =3D left - pattern; + datum +=3D n; + dlen -=3D n; + pattern +=3D n + 1; + plen -=3D n + 1; + while (plen > 0 && *pattern =3D=3D '*') { + pattern++; + plen--; + } + if (!plen) { + /* Simple prefix match. */ + mdebug2("simple-prefix"); + wild =3D dlen; + goto yes; + } + } else { + while (plen > 0 && *pattern =3D=3D '*') { + pattern++; + plen--; + } + } =20 - /* match the end of the datum */ - if (memcmp(pattern, datum + (dlen - (plen - 1)), plen - 1) =3D=3D 0) - goto yes_wildcard; - goto no; + right =3D memrchr(pattern, '*', plen); + if (right !=3D pattern + plen - 1) { + /* Check suffix matches */ + const char *suffix =3D right ? right + 1 : pattern; + const char *p_end =3D pattern + plen; + const char *d_end =3D datum + dlen; + int slen =3D p_end - suffix; + + mdebug("suffix", suffix, slen, datum, dlen); + if (slen > dlen || + memcmp(suffix, d_end - slen, slen) !=3D 0) { + mdebug2("suffix-x"); + goto no; + } + dlen -=3D slen; + plen -=3D slen; + while (plen > 0 && pattern[plen - 1] =3D=3D '*') + plen--; + if (!plen) { + /* Simple prefix+suffix match. */ + wild =3D dlen; + goto yes; + } + } else { + while (plen > 0 && pattern[plen - 1] =3D=3D '*') + plen--; } =20 - /* need to match beginning of datum for "abc*" and "abc*def" */ - if (memcmp(pattern, datum, n) !=3D 0) - goto no; + /* We now have a remnant part of the pattern bounded by a pair of + * wildcards (e.g.: [aa**]bb*cc*dd[**ee]) and we need to find the + * middle substrings in order in the remnant part of the datum. The + * remnant pattern fragment may also contain further wildcards. + */ + for (;;) { + mdebug("middle", pattern, plen, datum, dlen); + const char *sub =3D pattern, *p; + int slen, skip; + + left =3D memchr(pattern, '*', plen); + if (!left) { + /* No further wildcard */ + mdebug2("middle-1"); + if (plen > dlen) + goto no; + p =3D memmem(datum, dlen, pattern, plen); + if (!p) { + mdebug2("middle-1-x"); + goto no; + } + wild +=3D dlen - plen; + goto yes; + } =20 - if (!asterisk[1]) - goto yes_wildcard; /* "abc*" matches */ + slen =3D left - pattern; + pattern =3D left + 1; + plen -=3D slen + 1; =20 - /* match the end of the datum */ - asterisk++; - n =3D plen - n - 1; - if (memcmp(pattern, datum + (dlen - n), n) =3D=3D 0) - goto yes_wildcard; + p =3D memmem(datum, dlen, sub, slen); + if (!p) { + mdebug2("middle-x"); + goto no; + } + skip =3D p - datum; + wild +=3D skip; + datum +=3D skip + slen; + dlen -=3D skip + slen; + + while (plen && *pattern =3D=3D '*') { + pattern++; + plen--; + } =20 -no: - if (verbosity >=3D 2) - debug(" =3D no\n"); - return 0; + /* plen really shouldn't be 0 here. */ + if (plen <=3D 0) { + error("plen unexpectedly 0\n"); + goto no; + } + } =20 yes: if (verbosity >=3D 2) - debug(" =3D yes (w=3D0)\n"); - return 1; - -yes_wildcard: - *_wildness +=3D dlen - (plen - 1); + debug(" =3D yes (w=3D%u)\n", wild); + return wild; +no: if (verbosity >=3D 2) - debug(" =3D yes (w=3D%u)\n", dlen - (plen - 1)); - return 1; + debug(" =3D no\n"); + return -1; =20 } /* end match() */