Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package libupnp for openSUSE:Factory checked 
in at 2026-08-15 22:40:11
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/libupnp (Old)
 and      /work/SRC/openSUSE:Factory/.libupnp.new.1258 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "libupnp"

Sat Aug 15 22:40:11 2026 rev:49 rq:1370965 version:22.0.6

Changes:
--------
--- /work/SRC/openSUSE:Factory/libupnp/libupnp.changes  2026-07-20 
09:57:05.695597178 +0200
+++ /work/SRC/openSUSE:Factory/.libupnp.new.1258/libupnp.changes        
2026-08-15 22:40:18.554917101 +0200
@@ -1,0 +2,6 @@
+Thu Aug 13 07:42:55 UTC 2026 - Jan Engelhardt <[email protected]>
+
+- Update to release 22.0.6
+  * Build fixes for OmniOS
+
+-------------------------------------------------------------------

Old:
----
  libupnp-22.0.4.tar.bz2

New:
----
  libupnp-22.0.6.tar.bz2

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ libupnp.spec ++++++
--- /var/tmp/diff_new_pack.YioNRS/_old  2026-08-15 22:40:19.501950437 +0200
+++ /var/tmp/diff_new_pack.YioNRS/_new  2026-08-15 22:40:19.503950507 +0200
@@ -19,7 +19,7 @@
 
 %define pnpver 22
 Name:           libupnp
-Version:        22.0.4
+Version:        22.0.6
 Release:        0
 Summary:        An implementation of Universal Plug and Play (UPnP)
 License:        BSD-3-Clause

++++++ _scmsync.obsinfo ++++++
--- /var/tmp/diff_new_pack.YioNRS/_old  2026-08-15 22:40:19.544951951 +0200
+++ /var/tmp/diff_new_pack.YioNRS/_new  2026-08-15 22:40:19.547952056 +0200
@@ -1,5 +1,5 @@
-mtime: 1784230268
-commit: 6c3a42da6f231a216489acbd991b7f515ddf845ea1b81dd1db1794d5bd5990b6
+mtime: 1786608334
+commit: 333233cbdac79e094a1767518106d4d755f3b5c2837b893456137867b1858393
 url: https://src.opensuse.org/jengelh/libupnp
 revision: master
 

++++++ build.specials.obscpio ++++++

++++++ build.specials.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/.gitignore new/.gitignore
--- old/.gitignore      1970-01-01 01:00:00.000000000 +0100
+++ new/.gitignore      2026-08-13 10:05:34.000000000 +0200
@@ -0,0 +1 @@
+.osc

++++++ libupnp-22.0.4.tar.bz2 -> libupnp-22.0.6.tar.bz2 ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnp-22.0.4/CMakeLists.txt 
new/libupnp-22.0.6/CMakeLists.txt
--- old/libupnp-22.0.4/CMakeLists.txt   2026-07-15 03:45:23.000000000 +0200
+++ new/libupnp-22.0.6/CMakeLists.txt   2026-08-12 21:42:59.000000000 +0200
@@ -7,7 +7,7 @@
 set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
 
 project(PUPNP
-       VERSION 22.0.4
+       VERSION 22.0.6
        LANGUAGES C)
 
 include(GNUInstallDirs)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnp-22.0.4/ChangeLog new/libupnp-22.0.6/ChangeLog
--- old/libupnp-22.0.4/ChangeLog        2026-07-15 03:45:23.000000000 +0200
+++ new/libupnp-22.0.6/ChangeLog        2026-08-12 21:42:59.000000000 +0200
@@ -1,4 +1,37 @@
 *******************************************************************************
+Version 22.0.6
+*******************************************************************************
+
+- Fix OmniOS build failure in test-upnp-ghsa-f8r2. The GHSA-279g-pwr2-f8r2
+  regression test calls socket()/connect()/send()/recv() directly, same as
+  test-upnp-gh-325, but was registered without the SOCKET_LIBRARY/
+  NSL_LIBRARY linkage those symbols need outside glibc (e.g. Solaris/
+  illumos). Also fixes a stray placement bug where its registration had
+  landed between gh-325's registration and gh-325's own linking block.
+
+*******************************************************************************
+Version 22.0.5
+*******************************************************************************
+
+- GHSA-279g-pwr2-f8r2: Fix path truncation via embedded NUL byte (%00) in
+  request paths. remove_escaped_chars() correctly decodes %00 to a literal
+  NUL byte, but process_request() then treated the decoded buffer as a
+  NUL-terminated C string in alias/virtual-directory routing checks, so a
+  request for /alias%00.png was silently served as /alias, bypassing
+  extension-based ACL/WAF filters and virtual-directory routing. Fixed by
+  rejecting the request when the decoded string length no longer matches
+  the tracked buffer length.
+- GHSA-hg5x-73vf-vfm2: Fix unbounded memory allocation and integer
+  truncation in chunked Transfer-Encoding parsing. match_int() parsed a hex
+  chunk size into a 64-bit long but only rejected negative values, so a
+  declared size above INT_MAX silently wrapped (e.g. 2^32 wrapped to 0 and
+  was misread as the chunked-entity terminator). Separately, a declared
+  chunk size was never checked against g_maxContentLength before its bytes
+  were buffered, allowing a single oversized chunk to exhaust memory.
+  Fixed by rejecting out-of-range chunk sizes and rejecting a chunk size
+  exceeding g_maxContentLength before any of its body is read.
+
+*******************************************************************************
 Version 22.0.4
 *******************************************************************************
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnp-22.0.4/README.md new/libupnp-22.0.6/README.md
--- old/libupnp-22.0.4/README.md        2026-07-15 03:45:23.000000000 +0200
+++ new/libupnp-22.0.6/README.md        2026-08-12 21:42:59.000000000 +0200
@@ -5,7 +5,8 @@
 
 | branch         | status                                                      
                                        |
 | -------------- | 
---------------------------------------------------------------------------------------------------
 |
-| main  (2.0.x)  | 
![main](https://github.com/pupnp/pupnp/actions/workflows/ccpp.yml/badge.svg?branch=main)
            |
+| main  (22.0.x) | 
![main](https://github.com/pupnp/pupnp/actions/workflows/ccpp.yml/badge.svg?branch=main)
            |
+| branch-2.0.x   | 
![2.0.x](https://github.com/pupnp/pupnp/actions/workflows/ccpp.yml/badge.svg?branch=branch-2.0.x)
   |
 | branch-1.18.x  | 
![1.18.x](https://github.com/pupnp/pupnp/actions/workflows/ccpp.yml/badge.svg?branch=branch-1.18.x)
 |
 | branch-1.14.x  | 
![1.14.x](https://github.com/pupnp/pupnp/actions/workflows/ccpp.yml/badge.svg?branch=branch-1.14.x)
 |
 | branch-1.12.x  | 
![1.12.x](https://github.com/pupnp/pupnp/actions/workflows/ccpp.yml/badge.svg?branch=branch-1.12.x)
 |
@@ -116,7 +117,12 @@
 
 | Release Number | Date       | History                                  |
 | -------------- | ---------- | ---------------------------------------- |
+| 22.0.5         | 2026-08-12 | [Portable UPnP SDK][Portable UPnP SDK]   |
+| 22.0.4         | 2026-07-15 | [Portable UPnP SDK][Portable UPnP SDK]   |
 | 22.0.3         | 2026-07-14 | [Portable UPnP SDK][Portable UPnP SDK]   |
+| 22.0.2         | NA         | Internal                                 |
+| 22.0.1         | NA         | Internal                                 |
+| 22.0.0         | NA         | Internal                                 |
 | 2.0.2          | 2026-06-17 | [Portable UPnP SDK][Portable UPnP SDK]   |
 | 2.0.1          | 2026-06-17 | [Portable UPnP SDK][Portable UPnP SDK]   |
 | 2.0.0          | 2026-06-16 | [Portable UPnP SDK][Portable UPnP SDK]   |
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnp-22.0.4/SECURITY.md 
new/libupnp-22.0.6/SECURITY.md
--- old/libupnp-22.0.4/SECURITY.md      2026-07-15 03:45:23.000000000 +0200
+++ new/libupnp-22.0.6/SECURITY.md      2026-08-12 21:42:59.000000000 +0200
@@ -6,9 +6,8 @@
 
 | Version  | Supported          |
 | -------- | ------------------ |
-| 1.18.x   | :white_check_mark: |
-| 1.14.x   | :x:                |
-| < 1.14.x | :x:                |
+| 22.0.x   | :white_check_mark: |
+| < 22.0.x | :x:                |
 
 ## Reporting a Vulnerability
 
@@ -30,3 +29,12 @@
 
 Nonetheless, we will try to provide a fix for the problem as soon as possible
 and request that you do not disclose the issue publicly until we have released 
a patch.
+
+If you would like to contribute the fix yourself, we do not accept public pull
+requests for an unpatched vulnerability, since that would expose the issue
+before a fix is available to users. Instead, we can set up a temporary private
+fork attached to the advisory so you can collaborate with us and submit your
+patch there directly.
+
+Once a fix is released, we publish the advisory and request a CVE ID through
+GitHub.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnp-22.0.4/docs/Doxyfile 
new/libupnp-22.0.6/docs/Doxyfile
--- old/libupnp-22.0.4/docs/Doxyfile    2026-07-15 03:45:23.000000000 +0200
+++ new/libupnp-22.0.6/docs/Doxyfile    2026-08-12 21:42:59.000000000 +0200
@@ -38,7 +38,7 @@
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = 22.0.4
+PROJECT_NUMBER         = 22.0.6
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnp-22.0.4/gtest/test_httpreadwrite.cpp 
new/libupnp-22.0.6/gtest/test_httpreadwrite.cpp
--- old/libupnp-22.0.4/gtest/test_httpreadwrite.cpp     2026-07-15 
03:45:23.000000000 +0200
+++ new/libupnp-22.0.6/gtest/test_httpreadwrite.cpp     2026-08-12 
21:42:59.000000000 +0200
@@ -297,6 +297,153 @@
        EXPECT_EQ(ret, UPNP_E_SUCCESS);
 }
 
+// regression: GHSA-hg5x-73vf-vfm2
+// Two related bugs in chunked Transfer-Encoding parsing:
+//
+// 1. A hex chunk-size token is parsed by match_int() into a 64-bit `long`
+//    (via strtol) but only checked for `num < 0`, then narrowed to `int`
+//    with `*value = (int)num;` — silently wrapping values above INT_MAX
+//    instead of rejecting them. Worse, the call site
+//    (parser_parse_chunky_entity, "%x%L%c") passes `&parser->chunk_size`,
+//    which is `size_t`, into a vararg slot read back as `int *`
+//    (httpparser.c's match(), case 'x'/'d') — a type-confused write that
+//    only ever touches the low 4 bytes of the 8-byte field. A declared
+//    chunk size of "100000000" (2^32) wraps to 0 and is misread as the
+//    chunked-entity terminator, letting an attacker-declared multi-
+//    gigabyte chunk be silently accepted as an empty, already-complete
+//    entity.
+//
+// 2. Even with a syntactically valid (in-range) chunk size, the declared
+//    size is never compared against g_maxContentLength before the parser
+//    buffers the chunk's bytes: parser->msg.entity.length is only
+//    incremented in parser_parse_chunky_body() once the *entire* declared
+//    chunk has already been received into parser->msg.msg (see
+//    httpparser.c:1693), so the existing g_maxContentLength check in
+//    http_RecvMessage() (which reads entity.length) never fires while a
+//    single oversized chunk is still streaming in.
+class GhsaHg5xTestSuite : public ::testing::Test
+{
+protected:
+       int sv[2]{-1, -1};
+       size_t saved_limit_{};
+
+       void SetUp() override
+       {
+               saved_limit_ = g_maxContentLength;
+               g_maxContentLength = 1024; /* 1 KB limit */
+               ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);
+       }
+
+       void TearDown() override
+       {
+               g_maxContentLength = saved_limit_;
+               if (sv[0] >= 0)
+                       close(sv[0]);
+               if (sv[1] >= 0)
+                       close(sv[1]);
+       }
+};
+
+// A chunk-size line declaring 2^32 ("100000000") must not be silently
+// truncated to 0 and treated as an already-complete, empty entity.
+TEST_F(GhsaHg5xTestSuite, OverflowingChunkSizeIsRejected)
+{
+       static const char hdr[] = "HTTP/1.1 200 OK\r\n"
+                                 "Transfer-Encoding: chunked\r\n"
+                                 "\r\n";
+
+       write(sv[1], hdr, sizeof(hdr) - 1);
+       /* 0x100000000 = 2^32; (int) truncation wraps this to 0, which is
+        * the chunked-entity terminator, followed by an empty trailer
+        * header block. */
+       write(sv[1], "100000000\r\n", 11);
+       write(sv[1], "\r\n", 2);
+       close(sv[1]);
+       sv[1] = -1;
+
+       SOCKINFO info{};
+       info.socket = sv[0];
+
+       http_parser_t parser{};
+       int timeout = 5;
+       int http_err = 0;
+       int ret = http_RecvMessage(
+               &info, &parser, HTTPMETHOD_GET, &timeout, &http_err);
+
+       httpmsg_destroy(&parser.msg);
+
+       EXPECT_NE(ret, UPNP_E_SUCCESS);
+}
+
+// A single declared chunk larger than g_maxContentLength must be rejected
+// as soon as its buffered bytes exceed the limit, without waiting for the
+// full declared chunk size to be received.
+TEST_F(GhsaHg5xTestSuite, OversizedSingleChunkIsRejectedBeforeBuffering)
+{
+       static const char hdr[] = "HTTP/1.1 200 OK\r\n"
+                                 "Transfer-Encoding: chunked\r\n"
+                                 "\r\n";
+
+       char chunk_data[2048];
+       memset(chunk_data, 'X', sizeof(chunk_data));
+
+       write(sv[1], hdr, sizeof(hdr) - 1);
+       /* Declare an 8 KB chunk (0x2000), but only ever send 2 KB of it —
+        * more than g_maxContentLength (1 KB), but less than the declared
+        * chunk size, so the chunk never completes and entity.length is
+        * never updated. */
+       write(sv[1], "2000\r\n", 6);
+       write(sv[1], chunk_data, sizeof(chunk_data));
+       close(sv[1]);
+       sv[1] = -1;
+
+       SOCKINFO info{};
+       info.socket = sv[0];
+
+       http_parser_t parser{};
+       int timeout = 5;
+       int http_err = 0;
+       int ret = http_RecvMessage(
+               &info, &parser, HTTPMETHOD_GET, &timeout, &http_err);
+
+       httpmsg_destroy(&parser.msg);
+
+       EXPECT_EQ(ret, UPNP_E_OUTOF_BOUNDS);
+       EXPECT_EQ(http_err, HTTP_REQ_ENTITY_TOO_LARGE);
+}
+
+// A chunk size within range and under the limit must still be accepted.
+TEST_F(GhsaHg5xTestSuite, ValidChunkSizeIsAccepted)
+{
+       static const char hdr[] = "HTTP/1.1 200 OK\r\n"
+                                 "Transfer-Encoding: chunked\r\n"
+                                 "\r\n";
+
+       char chunk_data[512];
+       memset(chunk_data, 'X', sizeof(chunk_data));
+
+       write(sv[1], hdr, sizeof(hdr) - 1);
+       write(sv[1], "200\r\n", 5); /* 0x200 = 512 bytes */
+       write(sv[1], chunk_data, sizeof(chunk_data));
+       write(sv[1], "\r\n", 2);
+       write(sv[1], "0\r\n\r\n", 5); /* terminator */
+       close(sv[1]);
+       sv[1] = -1;
+
+       SOCKINFO info{};
+       info.socket = sv[0];
+
+       http_parser_t parser{};
+       int timeout = 5;
+       int http_err = 0;
+       int ret = http_RecvMessage(
+               &info, &parser, HTTPMETHOD_GET, &timeout, &http_err);
+
+       httpmsg_destroy(&parser.msg);
+
+       EXPECT_EQ(ret, UPNP_E_SUCCESS);
+}
+
 int main(int argc, char **argv)
 {
        ::testing::InitGoogleTest(&argc, argv);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnp-22.0.4/libupnp.spec 
new/libupnp-22.0.6/libupnp.spec
--- old/libupnp-22.0.4/libupnp.spec     2026-07-15 03:45:23.000000000 +0200
+++ new/libupnp-22.0.6/libupnp.spec     2026-08-12 21:42:59.000000000 +0200
@@ -1,4 +1,4 @@
-Version: 22.0.4
+Version: 22.0.6
 Summary: Universal Plug and Play (UPnP) SDK
 Name: libupnp-1.18
 Release: 1%{?dist}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnp-22.0.4/upnp/src/genlib/net/http/httpparser.c 
new/libupnp-22.0.6/upnp/src/genlib/net/http/httpparser.c
--- old/libupnp-22.0.4/upnp/src/genlib/net/http/httpparser.c    2026-07-15 
03:45:23.000000000 +0200
+++ new/libupnp-22.0.6/upnp/src/genlib/net/http/httpparser.c    2026-08-12 
21:42:59.000000000 +0200
@@ -43,6 +43,7 @@
 #include "UpnpGlobal.h"
 #include "membuffer.h"
 #include "upnp.h"
+#include "upnpapi.h"
 #include "upnpdebug.h"
 #include "uri.h"
 #include <errno.h>
@@ -792,7 +793,8 @@
                        num = strtol(token.buf, &end_ptr, base);
                        /* all and only those chars in token should be used for
                         * num */
-                       if (num < 0 || end_ptr != token.buf + token.length ||
+                       if (num < 0 || num > INT_MAX ||
+                               end_ptr != token.buf + token.length ||
                                ((num == LONG_MIN || num == LONG_MAX) &&
                                        (errno == ERANGE))) {
                                status = PARSE_NO_MATCH;
@@ -1691,13 +1693,21 @@
                parser->scanner.cursor = save_pos;
                /*update temp  */
                parser->msg.entity.length += parser->chunk_size;
+
+               /* Check content length limit during chunked accumulation */
+               if (g_maxContentLength > 0 &&
+                       parser->msg.entity.length >
+                               (unsigned int)g_maxContentLength) {
+                       parser->http_error_code = HTTP_REQ_ENTITY_TOO_LARGE;
+                       return PARSE_FAILURE;
+               }
+
                parser->ent_position = ENTREAD_USING_CHUNKED;
                return PARSE_CONTINUE_1;
        } else
                /* need more data for chunk */
                return PARSE_INCOMPLETE;
 }
-
 /************************************************************************
  * Function: parser_parse_chunky_headers
  *
@@ -1760,13 +1770,14 @@
        parse_status_t status;
        size_t save_pos;
        memptr dummy;
+       int chunk_size;
 
        assert(parser->ent_position == ENTREAD_USING_CHUNKED);
 
        save_pos = scanner->cursor;
 
        /* get size of chunk, discard extension, discard CRLF */
-       status = match(scanner, "%x%L%c", &parser->chunk_size, &dummy);
+       status = match(scanner, "%x%L%c", &chunk_size, &dummy);
        if (status != (parse_status_t)PARSE_OK) {
                scanner->cursor = save_pos;
                UpnpPrintf(UPNP_INFO,
@@ -1776,6 +1787,9 @@
                        "CHUNK COULD NOT BE PARSED\n");
                return status;
        }
+
+       parser->chunk_size = (size_t)chunk_size;
+
        /* remove chunk info just matched; just retain data */
        membuffer_delete(
                &parser->msg.msg, save_pos, (scanner->cursor - save_pos));
@@ -1790,13 +1804,18 @@
                /* read entity headers */
                parser->ent_position = ENTREAD_CHUNKY_HEADERS;
        } else {
+               /* Check if this single chunk exceeds the max content length */
+               if (g_maxContentLength > 0 &&
+                       parser->chunk_size > (unsigned int)g_maxContentLength) {
+                       parser->http_error_code = HTTP_REQ_ENTITY_TOO_LARGE;
+                       return PARSE_FAILURE;
+               }
                /* read chunk body */
                parser->ent_position = ENTREAD_CHUNKY_BODY;
        }
 
        return PARSE_CONTINUE_1; /* continue to reading body */
 }
-
 /************************************************************************
  * Function: parser_parse_entity_until_close
  *
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/libupnp-22.0.4/upnp/src/genlib/net/http/httpreadwrite.c 
new/libupnp-22.0.6/upnp/src/genlib/net/http/httpreadwrite.c
--- old/libupnp-22.0.4/upnp/src/genlib/net/http/httpreadwrite.c 2026-07-15 
03:45:23.000000000 +0200
+++ new/libupnp-22.0.6/upnp/src/genlib/net/http/httpreadwrite.c 2026-08-12 
21:42:59.000000000 +0200
@@ -437,15 +437,44 @@
                        case PARSE_FAILURE:
                        case PARSE_NO_MATCH:
                                *http_error_code = parser->http_error_code;
+                               if (*http_error_code ==
+                                       HTTP_REQ_ENTITY_TOO_LARGE) {
+                                       ret = UPNP_E_OUTOF_BOUNDS;
+                               } else {
+                                       ret = UPNP_E_BAD_HTTPMSG;
+                               }
                                line = __LINE__;
-                               ret = UPNP_E_BAD_HTTPMSG;
                                goto ExitFunction;
                        case PARSE_INCOMPLETE_ENTITY:
                                /* read until close */
                                ok_on_close = 1;
+                               /* Check content length limit during chunked
+                                * entity accumulation */
+                               if (g_maxContentLength > 0 &&
+                                       parser->msg.entity.length >
+                                               (unsigned int)
+                                                       g_maxContentLength) {
+                                       *http_error_code =
+                                               HTTP_REQ_ENTITY_TOO_LARGE;
+                                       line = __LINE__;
+                                       ret = UPNP_E_OUTOF_BOUNDS;
+                                       goto ExitFunction;
+                               }
                                break;
                        case PARSE_CONTINUE_1:
                                /* Web post request. */
+                               /* Check content length limit for chunked/web
+                                * post */
+                               if (g_maxContentLength > 0 &&
+                                       parser->msg.entity.length >
+                                               (unsigned int)
+                                                       g_maxContentLength) {
+                                       *http_error_code =
+                                               HTTP_REQ_ENTITY_TOO_LARGE;
+                                       line = __LINE__;
+                                       ret = UPNP_E_OUTOF_BOUNDS;
+                                       goto ExitFunction;
+                               }
                                line = __LINE__;
                                ret = PARSE_SUCCESS;
                                goto ExitFunction;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnp-22.0.4/upnp/src/genlib/net/http/webserver.c 
new/libupnp-22.0.6/upnp/src/genlib/net/http/webserver.c
--- old/libupnp-22.0.4/upnp/src/genlib/net/http/webserver.c     2026-07-15 
03:45:23.000000000 +0200
+++ new/libupnp-22.0.6/upnp/src/genlib/net/http/webserver.c     2026-08-12 
21:42:59.000000000 +0200
@@ -1284,6 +1284,14 @@
        request_doc[url->pathquery.size] = '\0';
        dummy = url->pathquery.size;
        remove_escaped_chars(request_doc, &dummy);
+       /* Prevent path truncation (NUL byte injection):
+        * If remove_escaped_chars decoded a %00, strlen will stop early,
+        * making it strictly less than the actual decoded length (dummy). */
+       if (strlen(request_doc) != dummy) {
+               err_code = HTTP_BAD_REQUEST;
+               goto error_handler;
+       }
+
        code = remove_dots(request_doc, url->pathquery.size);
        if (code != 0) {
                err_code = HTTP_FORBIDDEN;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnp-22.0.4/upnp/test/CMakeLists.txt 
new/libupnp-22.0.6/upnp/test/CMakeLists.txt
--- old/libupnp-22.0.4/upnp/test/CMakeLists.txt 2026-07-15 03:45:23.000000000 
+0200
+++ new/libupnp-22.0.6/upnp/test/CMakeLists.txt 2026-08-12 21:42:59.000000000 
+0200
@@ -286,6 +286,25 @@
        )
 endif()
 
+# GHSA-279g-pwr2-f8r2: embedded NUL byte (%00) in a request path is decoded
+# to a literal NUL by remove_escaped_chars(), but downstream alias/routing
+# checks in webserver.c use NUL-terminated string functions, so bytes after
+# the NUL are silently dropped and the request wrongly matches a shorter
+# registered alias.  Uses the same web_server_ut_set_alias hook.
+UPNP_Add_Unit_Test(test-upnp-ghsa-f8r2 poc_ghsa_f8r2_nullbyte.c)
+if(UPNP_BUILD_SHARED)
+       target_link_libraries(test-upnp-ghsa-f8r2 PRIVATE
+               $<$<BOOL:${SOCKET_LIBRARY}>:${SOCKET_LIBRARY}>
+               $<$<BOOL:${NSL_LIBRARY}>:${NSL_LIBRARY}>
+       )
+endif()
+if(UPNP_BUILD_STATIC)
+       target_link_libraries(test-upnp-ghsa-f8r2-static PRIVATE
+               $<$<BOOL:${SOCKET_LIBRARY}>:${SOCKET_LIBRARY}>
+               $<$<BOOL:${NSL_LIBRARY}>:${NSL_LIBRARY}>
+       )
+endif()
+
 # Issue #347: HTTP response headers must use title-case names.
 # Starts the UPnP HTTP server via UpnpInit2, sends a GET request over a raw
 # TCP socket, and verifies title-case header names in the response.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libupnp-22.0.4/upnp/test/poc_ghsa_f8r2_nullbyte.c 
new/libupnp-22.0.6/upnp/test/poc_ghsa_f8r2_nullbyte.c
--- old/libupnp-22.0.4/upnp/test/poc_ghsa_f8r2_nullbyte.c       1970-01-01 
01:00:00.000000000 +0100
+++ new/libupnp-22.0.6/upnp/test/poc_ghsa_f8r2_nullbyte.c       2026-08-12 
21:42:59.000000000 +0200
@@ -0,0 +1,169 @@
+/* poc_ghsa_f8r2_nullbyte.c
+ *
+ * Regression test for GHSA-279g-pwr2-f8r2: embedded NUL byte (%00)
+ * injection in HTTP request paths bypasses path handling.
+ *
+ * process_request() in webserver.c percent-decodes the request path with
+ * remove_escaped_chars(), which correctly turns "%00" into a literal NUL
+ * byte and tracks the true (post-decode) length in a separate size_t.
+ * Everything downstream, however, treats the decoded buffer as a plain
+ * C string (strcmp/strncmp/strlen in remove_dots(), isFileInVirtualDir(),
+ * get_alias(), membuffer_assign_str()), so any bytes after the embedded
+ * NUL are silently invisible to those checks.
+ *
+ * This lets a request for "/desc.xml%00.png" match an alias registered
+ * as "/desc.xml", even though the two are different byte sequences on
+ * the wire -- exactly the kind of internal/external request-path mismatch
+ * a WAF or reverse-proxy extension filter would not expect.
+ *
+ * Pre-fix: server returns 200 (silently matches the truncated alias).
+ * Post-fix: server rejects the request instead of matching the alias.
+ *
+ * regression: GHSA-279g-pwr2-f8r2
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifndef _WIN32
+
+       #include "upnp.h"
+
+       #include <arpa/inet.h>
+       #include <netinet/in.h>
+       #include <sys/socket.h>
+       #include <unistd.h>
+
+/* regression: GHSA-279g-pwr2-f8r2 -- test hook exported from libupnp */
+extern int web_server_ut_set_alias(
+       const char *name, const char *content, size_t len);
+
+       #ifndef MSG_NOSIGNAL
+               #define MSG_NOSIGNAL 0
+       #endif
+
+/* The miniserver rejects requests whose Host header isn't a numeric
+ * ip:port (DNS-rebinding protection), so the Host header below must
+ * match the server's own address. */
+static int get_status_code(const char *ip, unsigned short port,
+       const char *path)
+{
+       int sock;
+       struct sockaddr_in addr;
+       char req[256];
+       char buf[512];
+       ssize_t n;
+       size_t total = 0;
+       int status = -1;
+
+       sock = socket(AF_INET, SOCK_STREAM, 0);
+       if (sock < 0)
+               return -1;
+
+       memset(&addr, 0, sizeof addr);
+       addr.sin_family = AF_INET;
+       addr.sin_port = htons(port);
+       inet_pton(AF_INET, ip, &addr.sin_addr);
+
+       if (connect(sock, (struct sockaddr *)&addr, sizeof addr) != 0) {
+               close(sock);
+               return -1;
+       }
+
+       snprintf(req,
+               sizeof req,
+               "GET %s HTTP/1.1\r\n"
+               "Host: %s:%u\r\n"
+               "Connection: close\r\n\r\n",
+               path,
+               ip,
+               port);
+       send(sock, req, strlen(req), MSG_NOSIGNAL);
+
+       while (total < sizeof buf - 1 &&
+               (n = recv(sock, buf + total, sizeof buf - 1 - total, 0)) >
+                       0) {
+               total += (size_t)n;
+       }
+       buf[total] = '\0';
+       close(sock);
+
+       if (total > 0) {
+               sscanf(buf, "HTTP/%*d.%*d %d", &status);
+       }
+
+       return status;
+}
+
+int main(void)
+{
+       int rc;
+       char *ip;
+       unsigned short port;
+       int status;
+
+       rc = UpnpInit2(NULL, 0);
+       if (rc != UPNP_E_SUCCESS) {
+               fprintf(stderr,
+                       "UpnpInit2 failed (%d); skipping (no network?)\n",
+                       rc);
+               return EXIT_SUCCESS;
+       }
+
+       rc = web_server_ut_set_alias("/desc.xml", "<root/>", 7);
+       if (rc != UPNP_E_SUCCESS) {
+               fprintf(stderr, "web_server_ut_set_alias failed (%d)\n", rc);
+               UpnpFinish();
+               return EXIT_FAILURE;
+       }
+
+       ip = UpnpGetServerIpAddress();
+       port = UpnpGetServerPort();
+       if (ip == NULL || port == 0) {
+               fprintf(stderr,
+                       "UpnpGetServerIpAddress/UpnpGetServerPort returned "
+                       "nothing; skipping\n");
+               UpnpFinish();
+               return EXIT_SUCCESS;
+       }
+
+       /* Sanity check: the plain alias must resolve, otherwise the '%00'
+        * check below would be meaningless. */
+       if (get_status_code(ip, port, "/desc.xml") != 200) {
+               fprintf(stderr,
+                       "Baseline request for '/desc.xml' did not return "
+                       "200; skipping (environment issue?)\n");
+               UpnpFinish();
+               return EXIT_SUCCESS;
+       }
+
+       /* "%00" decodes to a NUL byte; the registered alias is "/desc.xml",
+        * not "/desc.xml\0.png", so this must NOT be served as the alias. */
+       status = get_status_code(ip, port, "/desc.xml%00.png");
+
+       UpnpFinish();
+
+       if (status == 200) {
+               fprintf(stderr,
+                       "FAIL: server returned 200 for '/desc.xml%%00.png' "
+                       "-- embedded NUL byte truncated the path to match "
+                       "the '/desc.xml' alias (GHSA-279g-pwr2-f8r2)\n");
+               return EXIT_FAILURE;
+       }
+
+       printf("PASS: request with embedded NUL byte was not silently "
+              "truncated to match the alias (status=%d)\n",
+               status);
+       return EXIT_SUCCESS;
+}
+
+#else /* _WIN32 */
+
+int main(void)
+{
+       puts("SKIP: test uses POSIX sockets (not available on Windows).");
+       return EXIT_SUCCESS;
+}
+
+#endif /* _WIN32 */

Reply via email to