--- Begin Message ---
Package: release.debian.org
Severity: normal
Tags: bulleye
User: release.debian....@packages.debian.org
Usertags: pu
The attached debdiff for libhttp-daemon-perl fixes CVE-2022-31081 in
Bullseye. This CVE has been marked as no-dsa by the security team.
The patch is accompanied by a new test and should not create any issue.
It had been used to fix unstable and will be used for Buster, <Stretch and
Jessie as well.
Thorsten
diff -Nru libhttp-daemon-perl-6.12/debian/changelog
libhttp-daemon-perl-6.12/debian/changelog
--- libhttp-daemon-perl-6.12/debian/changelog 2020-06-06 03:12:55.000000000
+0200
+++ libhttp-daemon-perl-6.12/debian/changelog 2022-07-26 20:08:59.000000000
+0200
@@ -1,3 +1,11 @@
+libhttp-daemon-perl (6.12-1+deb11u1) bullseye; urgency=high
+
+ * Non-maintainer upload by the ELTS Team.
+ * CVE-2022-31081 (Closes: #1014808)
+ improved Content-Length: handling in HTTP-header
+
+ -- Thorsten Alteholz <deb...@alteholz.de> Tue, 26 Jul 2022 20:08:59 +0200
+
libhttp-daemon-perl (6.12-1) unstable; urgency=medium
* Import upstream version 6.12.
diff -Nru libhttp-daemon-perl-6.12/debian/patches/CVE-2022-31081-1.patch
libhttp-daemon-perl-6.12/debian/patches/CVE-2022-31081-1.patch
--- libhttp-daemon-perl-6.12/debian/patches/CVE-2022-31081-1.patch
1970-01-01 01:00:00.000000000 +0100
+++ libhttp-daemon-perl-6.12/debian/patches/CVE-2022-31081-1.patch
2022-07-26 20:08:59.000000000 +0200
@@ -0,0 +1,48 @@
+commit e84475de51d6fd7b29354a997413472a99db70b2
+Author: Theo van Hoesel <tvanhoe...@perceptyx.com>
+Date: Thu Jun 16 08:28:30 2022 +0000
+
+ Fix Content-Length ', '-separated string issues
+
+ After a security issue, we ensure we comply to
+ RFC-7230 -- HTTP/1.1 Message Syntax and Routing
+ - section 3.3.2 -- Content-Length
+ - section 3.3.3 -- Message Body Length
+
+diff --git a/lib/HTTP/Daemon.pm b/lib/HTTP/Daemon.pm
+index c0cdf76..a5112b3 100644
+--- a/lib/HTTP/Daemon.pm
++++ b/lib/HTTP/Daemon.pm
+@@ -288,6 +288,32 @@ READ_HEADER:
+ }
+ elsif ($ct_len) {
+
++ # After a security issue, we ensure we comply to
++ # RFC-7230 -- HTTP/1.1 Message Syntax and Routing
++ # section 3.3.2 -- Content-Length
++ # section 3.3.3 -- Message Body Length
++
++ # split and clean up Content-Length ', ' separated string
++ my @vals = map {my $str = $_; $str =~ s/^\s+//; $str =~ s/\s+$//;
$str }
++ split ',', $ct_len;
++ # check that they are all numbers (RFC: Content-Length = 1*DIGIT)
++ my @nums = grep { /^[0-9]+$/} @vals;
++ unless (@vals == @nums) {
++ $self->send_error(400);
++ $self->reason("Content-Length value must be a unsigned integer");
++ return;
++ }
++ # check they are all the same
++ my $ct_len = shift @nums;
++ foreach (@nums) {
++ next if $_ == $ct_len;
++ $self->send_error(400);
++ $self->reason("Content-Length values are not the same");
++ return;
++ }
++ # ensure we have now a fixed header, with only 1 value
++ $r->header('Content-Length' => $ct_len);
++
+ # Plain body specified by "Content-Length"
+ my $missing = $ct_len - length($buf);
+ while ($missing > 0) {
diff -Nru libhttp-daemon-perl-6.12/debian/patches/CVE-2022-31081-2.patch
libhttp-daemon-perl-6.12/debian/patches/CVE-2022-31081-2.patch
--- libhttp-daemon-perl-6.12/debian/patches/CVE-2022-31081-2.patch
1970-01-01 01:00:00.000000000 +0100
+++ libhttp-daemon-perl-6.12/debian/patches/CVE-2022-31081-2.patch
2022-07-26 20:08:59.000000000 +0200
@@ -0,0 +1,33 @@
+commit 8dc5269d59e2d5d9eb1647d82c449ccd880f7fd0
+Author: Theo van Hoesel <tvanhoe...@perceptyx.com>
+Date: Tue Jun 21 20:00:47 2022 +0000
+
+ Include reason in response body content
+
+diff --git a/lib/HTTP/Daemon.pm b/lib/HTTP/Daemon.pm
+index a5112b3..2d022ae 100644
+--- a/lib/HTTP/Daemon.pm
++++ b/lib/HTTP/Daemon.pm
+@@ -299,16 +299,18 @@ READ_HEADER:
+ # check that they are all numbers (RFC: Content-Length = 1*DIGIT)
+ my @nums = grep { /^[0-9]+$/} @vals;
+ unless (@vals == @nums) {
+- $self->send_error(400);
+- $self->reason("Content-Length value must be a unsigned integer");
++ my $reason = "Content-Length value must be an unsigned integer";
++ $self->send_error(400, $reason);
++ $self->reason($reason);
+ return;
+ }
+ # check they are all the same
+ my $ct_len = shift @nums;
+ foreach (@nums) {
+ next if $_ == $ct_len;
+- $self->send_error(400);
+- $self->reason("Content-Length values are not the same");
++ my $reason = "Content-Length values are not the same";
++ $self->send_error(400, $reason);
++ $self->reason($reason);
+ return;
+ }
+ # ensure we have now a fixed header, with only 1 value
diff -Nru libhttp-daemon-perl-6.12/debian/patches/CVE-2022-31081-rename.patch
libhttp-daemon-perl-6.12/debian/patches/CVE-2022-31081-rename.patch
--- libhttp-daemon-perl-6.12/debian/patches/CVE-2022-31081-rename.patch
1970-01-01 01:00:00.000000000 +0100
+++ libhttp-daemon-perl-6.12/debian/patches/CVE-2022-31081-rename.patch
2022-07-26 20:08:59.000000000 +0200
@@ -0,0 +1,102 @@
+commit 331d5c1d1f0e48e6b57ef738c2a8509b1eb53376
+Author: Theo van Hoesel <tvanhoe...@perceptyx.com>
+Date: Thu Jun 16 08:17:39 2022 +0000
+
+ Rename variables
+
+ can not remember 2-letter abreviation more than 100 lines below
+
+diff --git a/lib/HTTP/Daemon.pm b/lib/HTTP/Daemon.pm
+index a02486c..c0cdf76 100644
+--- a/lib/HTTP/Daemon.pm
++++ b/lib/HTTP/Daemon.pm
+@@ -192,9 +192,9 @@ READ_HEADER:
+ }
+
+ # Find out how much content to read
+- my $te = $r->header('Transfer-Encoding');
+- my $ct = $r->header('Content-Type');
+- my $len = $r->header('Content-Length');
++ my $tr_enc = $r->header('Transfer-Encoding');
++ my $ct_type = $r->header('Content-Type');
++ my $ct_len = $r->header('Content-Length');
+
+ # Act on the Expect header, if it's there
+ for my $e ($r->header('Expect')) {
+@@ -209,7 +209,7 @@ READ_HEADER:
+ }
+ }
+
+- if ($te && lc($te) eq 'chunked') {
++ if ($tr_enc && lc($tr_enc) eq 'chunked') {
+
+ # Handle chunked transfer encoding
+ my $body = "";
+@@ -280,32 +280,32 @@ READ_HEADER:
+ $r->push_header($key, $val) if $key;
+
+ }
+- elsif ($te) {
++ elsif ($tr_enc) {
+ $self->send_error(501); # Unknown transfer encoding
+- $self->reason("Unknown transfer encoding '$te'");
++ $self->reason("Unknown transfer encoding '$tr_enc'");
+ return;
+
+ }
+- elsif ($len) {
++ elsif ($ct_len) {
+
+ # Plain body specified by "Content-Length"
+- my $missing = $len - length($buf);
++ my $missing = $ct_len - length($buf);
+ while ($missing > 0) {
+ print "Need $missing more bytes of content\n" if $DEBUG;
+ my $n = $self->_need_more($buf, $timeout, $fdset);
+ return unless $n;
+ $missing -= $n;
+ }
+- if (length($buf) > $len) {
+- $r->content(substr($buf, 0, $len));
+- substr($buf, 0, $len) = '';
++ if (length($buf) > $ct_len) {
++ $r->content(substr($buf, 0, $ct_len));
++ substr($buf, 0, $ct_len) = '';
+ }
+ else {
+ $r->content($buf);
+ $buf = '';
+ }
+ }
+- elsif ($ct && $ct =~ m/^multipart\/\w+\s*;.*boundary\s*=\s*("?)(\w+)\1/i)
{
++ elsif ($ct_type && $ct_type =~
m/^multipart\/\w+\s*;.*boundary\s*=\s*("?)(\w+)\1/i) {
+
+ # Handle multipart content type
+ my $boundary = "$CRLF--$2--";
+@@ -497,8 +497,8 @@ sub send_redirect {
+ print $self "Location: $loc$CRLF";
+
+ if ($content) {
+- my $ct = $content =~ /^\s*</ ? "text/html" : "text/plain";
+- print $self "Content-Type: $ct$CRLF";
++ my $ct_type = $content =~ /^\s*</ ? "text/html" : "text/plain";
++ print $self "Content-Type: $ct_type$CRLF";
+ }
+ print $self $CRLF;
+ print $self $content if $content && !$self->head_request;
+@@ -537,12 +537,12 @@ sub send_file_response {
+ local (*F);
+ sysopen(F, $file, 0) or return $self->send_error(RC_FORBIDDEN);
+ binmode(F);
+- my ($ct, $ce) = guess_media_type($file);
++ my ($mime_type, $file_enc) = guess_media_type($file);
+ my ($size, $mtime) = (stat _)[7, 9];
+ unless ($self->antique_client) {
+ $self->send_basic_header;
+- print $self "Content-Type: $ct$CRLF";
+- print $self "Content-Encoding: $ce$CRLF" if $ce;
++ print $self "Content-Type: $mime_type$CRLF";
++ print $self "Content-Encoding: $file_enc$CRLF" if $file_enc;
+ print $self "Content-Length: $size$CRLF" if $size;
+ print $self "Last-Modified: ", time2str($mtime), "$CRLF" if
$mtime;
+ print $self $CRLF;
diff -Nru libhttp-daemon-perl-6.12/debian/patches/CVE-2022-31081-testcase.patch
libhttp-daemon-perl-6.12/debian/patches/CVE-2022-31081-testcase.patch
--- libhttp-daemon-perl-6.12/debian/patches/CVE-2022-31081-testcase.patch
1970-01-01 01:00:00.000000000 +0100
+++ libhttp-daemon-perl-6.12/debian/patches/CVE-2022-31081-testcase.patch
2022-07-26 20:08:59.000000000 +0200
@@ -0,0 +1,292 @@
+commit faebad54455c2c2919e234202362570925fb99d1
+Author: Theo van Hoesel <tvanhoe...@perceptyx.com>
+Date: Tue Jun 21 20:30:36 2022 +0000
+
+ Add new test for Content-Length issues
+
+ prove we fixed CVE-2022-31081
+
+diff --git a/t/content_length.t b/t/content_length.t
+new file mode 100644
+index 0000000..1751845
+--- /dev/null
++++ b/t/content_length.t
+@@ -0,0 +1,278 @@
++use strict;
++use warnings;
++
++use Test::More 0.98;
++
++use Config;
++
++use HTTP::Daemon;
++use HTTP::Response;
++use HTTP::Status;
++use HTTP::Tiny 0.042;
++
++patch_http_tiny(); # do not fix Content-Length, we want to forge something bad
++
++plan skip_all => "This system cannot fork" unless can_fork();
++
++my $BASE_URL;
++my @TESTS = get_tests();
++
++for my $test (@TESTS) {
++
++ my $http_daemon = HTTP::Daemon->new() or die "HTTP::Daemon->new: $!";
++ $BASE_URL = $http_daemon->url;
++
++ my $pid = fork;
++ die "fork: $!" if !defined $pid;
++ if ($pid == 0) {
++ accept_requests($http_daemon);
++ }
++
++ my $resp = http_test_request($test);
++
++ ok $resp, $test->{title};
++
++ is $resp->{status}, $test->{status},
++ "... and has expected status";
++
++ like $resp->{content}, $test->{like},
++ "... and body does match"
++ if $test->{like};
++
++}
++
++done_testing;
++
++
++
++sub get_tests{
++ {
++ title => "Hello World Request ... it works as expected",
++ path => "hello-world",
++ status => 200,
++ like => qr/^Hello World$/,
++ },
++ {
++ title => "Positive Content Length",
++ method => "POST",
++ headers => {
++ 'Content-Length' => '+1', # quotes are needed to retain plus-sign
++ },
++ status => 400,
++ like => qr/value must be an unsigned integer/,
++ },
++ {
++ title => "Negative Content Length",
++ method => "POST",
++ headers => {
++ 'Content-Length' => '-1',
++ },
++ status => 400,
++ like => qr/value must be an unsigned integer/,
++ },
++ {
++ title => "Non Integer Content Length",
++ method => "POST",
++ headers => {
++ 'Content-Length' => '3.14',
++ },
++ status => 400,
++ like => qr/value must be an unsigned integer/,
++ },
++ {
++ title => "Explicit Content Length ... with exact length",
++ method => "POST",
++ headers => {
++ 'Content-Length' => '8',
++ },
++ body => "ABCDEFGH",
++ status => 200,
++ like => qr/^ABCDEFGH$/,
++ },
++ {
++ title => "Implicit Content Length ... will always pass",
++ method => "POST",
++ body => "ABCDEFGH",
++ status => 200,
++ like => qr/^ABCDEFGH$/,
++ },
++ {
++ title => "Shorter Content Length ... gets truncated",
++ method => "POST",
++ headers => {
++ 'Content-Length' => '4',
++ },
++ body => "ABCDEFGH",
++ status => 200,
++ like => qr/^ABCD$/,
++ },
++ {
++ title => "Different Content Length ... must fail",
++ method => "POST",
++ headers => {
++ 'Content-Length' => ['8', '4'],
++ },
++ body => "ABCDEFGH",
++ status => 400,
++ like => qr/values are not the same/,
++ },
++ {
++ title => "Underscore Content Length ... must match",
++ method => "POST",
++ headers => {
++ 'Content_Length' => '4',
++ },
++ body => "ABCDEFGH",
++ status => 400,
++ like => qr/values are not the same/,
++ },
++ {
++ title => "Longer Content Length ... gets timeout",
++ method => "POST",
++ headers => {
++ 'Content-Length' => '9',
++ },
++ body => "ABCDEFGH",
++ status => 599, # silly code !!!
++ like => qr/^Timeout/,
++ },
++
++}
++
++
++
++sub router_table {
++ {
++ '/hello-world' => {
++ 'GET' => sub {
++ my $resp = HTTP::Response->new(200);
++ $resp->content('Hello World');
++ return $resp;
++ },
++ },
++
++ '/' => {
++ 'POST' => sub {
++ my $rqst = shift;
++
++ my $body = $rqst->content();
++
++ my $resp = HTTP::Response->new(200);
++ $resp->content($body);
++
++ return $resp
++ },
++ },
++ }
++}
++
++
++
++sub can_fork {
++ $Config{d_fork} || (($^O eq 'MSWin32' || $^O eq 'NetWare')
++ and $Config{useithreads}
++ and $Config{ccflags} =~ /-DPERL_IMPLICIT_SYS/);
++}
++
++
++
++# run the mini HTTP dispatcher that can handle various routes / methods
++sub accept_requests{
++ my $http_daemon = shift;
++ while (my $conn = $http_daemon->accept) {
++ while (my $rqst = $conn->get_request) {
++ if (my $resp = dispatch_request($rqst)) {
++ $conn->send_response($resp);
++ }
++ }
++ $conn->close;
++ undef($conn);
++ $http_daemon->close;
++ exit 1;
++ }
++}
++
++
++
++sub dispatch_request{
++ my $rqst = shift
++ or return;
++ my $path = $rqst->uri->path
++ or return;
++ my $meth = $rqst->method
++ or return;
++ my $code = router_table()->{$path}{$meth}
++ or return HTTP::Response->new(RC_NOT_FOUND);
++ my $resp = $code->($rqst);
++ return $resp;
++}
++
++
++
++sub http_test_request {
++ my $test = shift;
++ my $http_client = HTTP::Tiny->new(
++ timeout => 5,
++ proxy => undef,
++ http_proxy => undef,
++ https_proxy => undef,
++ );
++ my $resp;
++ eval {
++ local $SIG{ALRM} = sub { die "Timeout\n" };
++ alarm 2;
++ $resp = $http_client->request(
++ $test->{method} || "GET",
++ $BASE_URL . ($test->{path} || ""),
++ {
++ headers => $test->{headers},
++ content => $test->{body}
++ },
++ );
++ };
++ my $err = $@;
++ alarm 0;
++ diag $err if $err;
++
++ return $resp
++}
++
++
++
++sub patch_http_tiny {
++
++ # we need to patch write_content_body
++ # this is part of HTTP::Tiny internal module HTTP::Tiny::Handle
++ #
++ # the below code is from the original HTTP::Tiny module, where just two
lines
++ # have been commented out
++
++ no strict 'refs';
++
++ *HTTP::Tiny::Handle::write_content_body = sub {
++ @_ == 2 || die(q/Usage: $handle->write_content_body(request)/ . "\n");
++ my ($self, $request) = @_;
++
++ my ($len, $content_length) = (0,
$request->{headers}{'content-length'});
++ while () {
++ my $data = $request->{cb}->();
++
++ defined $data && length $data
++ or last;
++
++ if ( $] ge '5.008' ) {
++ utf8::downgrade($data, 1)
++ or die(qq/Wide character in write_content()\n/);
++ }
++
++ $len += $self->write($data);
++ }
++
++# this should not be checked during our tests, we want to forge bad
requests
++#
++# $len == $content_length
++# or die(qq/Content-Length mismatch (got: $len expected:
$content_length)\n/);
++
++ return $len;
++ };
++}
diff -Nru libhttp-daemon-perl-6.12/debian/patches/series
libhttp-daemon-perl-6.12/debian/patches/series
--- libhttp-daemon-perl-6.12/debian/patches/series 1970-01-01
01:00:00.000000000 +0100
+++ libhttp-daemon-perl-6.12/debian/patches/series 2022-07-26
20:08:59.000000000 +0200
@@ -0,0 +1,4 @@
+CVE-2022-31081-testcase.patch
+CVE-2022-31081-rename.patch
+CVE-2022-31081-1.patch
+CVE-2022-31081-2.patch
--- End Message ---