[PATCH 6.18.y 4/5] cifs: SMB1 split: Separate out SMB1 decls into smb1proto.h
From: Sasha Levin
Date: Mon Sep 07 2026 - 07:55:30 EST
From: David Howells <dhowells@xxxxxxxxxx>
[ Upstream commit 645427b7a6c59e0074df29bf939aec2e9d6f2819 ]
Separate out SMB1 declarations scriptedly into smb1proto.h. Script below:
#!/usr/bin/perl -w
use strict;
unless (@ARGV) {
die "Usage: $0 <c_file1> [<c_file2> ...]\n";
}
# Data tracking
my %funcs = (); # Func name => { func prototype }
my %headers = (); # Header filename => { header content }
my %c_files = (); # C filename => { ordered func list, header pref }
my %cmarkers = (); # C filename marker => { header filename it's in }
# Parse state
my $pathname = "-";
my $lineno = 0;
sub error(@) {
print STDERR $pathname, ":", $lineno, ": ", @_, "\n";
exit(1);
}
sub pad($) {
# Reindent the function arguments to line the arguments up with the char
# after the opening bracket on the func argument list
my ($lines) = @_;
return $lines if ($#{$lines} <= 0);
my $has_empty = 0;
for (my $i = 0; $i <= $#{$lines}; $i++) {
$lines->[$i] =~ s/^[ \t]+//;
$has_empty = 1 if ($lines->[$i] eq "");
}
if ($has_empty) {
my @clean = grep /.+/, @{$lines};
$lines = \@clean;
}
my $indlen = index($lines->[0], "(");
return $lines if ($indlen < 0);
my $indent = "";
$indlen++;
$indent .= "\t" x ($indlen / 8);
$indent .= " " x ($indlen % 8);
my @padded = ();
my $acc = "";
my $len = -$indlen;
for (my $i = 0; $i <= $#{$lines}; $i++) {
my $argument = $lines->[$i];
my $arglen = length($argument);
my $last = ($i == $#{$lines} ? 1 : 0);
if ($i == 0 ||
$i == 1) {
$acc .= $argument;
$acc .= ";" if ($last);
$len += $arglen + $last;
next;
}
if (!$acc) {
$acc = $indent . $argument;
$acc .= ";" if ($last);
$len += $arglen + $last;
next;
}
if ($indlen + $len + 1 + $arglen + $last > 79) {
push @padded, $acc;
$acc = $indent . $argument;
$acc .= ";" if ($last);
$len = $arglen + $last;
next;
}
$acc .= " " . $argument;
$acc .= ";" if ($last);
$len += 1 + $arglen + $last;
}
push @padded, $acc if ($acc);
return \@padded;
}
sub earliest(@) {
my $ret = -1;
foreach (@_) {
$ret = $_ if ($ret < 0 || ($_ >= 0 && $_ < $ret));
}
return $ret;
}
foreach my $file (@ARGV) {
# Open the file for reading.
next if $file =~ /trace[.]h$/;
next if $file =~ /smbdirect[.][ch]$/;
open my $fh, "<$file"
or die "Could not open file '$file'";
$pathname = $file;
$lineno = 0;
my $filename;
my @file_content = ();
my @copy = ();
my $state = 0;
my $qual = "";
my $type = "";
my $funcname = "";
my @funcdef = ();
my $bracket = 0;
my $comment = 0;
my $smb1 = 0;
my $header = 0;
my $inline = 0;
my $file_marker = "";
my $config = "";
my $c_file = 0;
$filename = $pathname;
$filename =~ s!.*/!!;
if ($file =~ m!.h$!) {
my %new_h_file = (
path => $pathname,
fname => $filename,
content => [],
);
$header = \%new_h_file;
$headers{$filename} = \%new_h_file;
} elsif ($file =~ m!.c$!) {
my %new_c_file = (
path => $pathname,
fname => $filename,
funcs => [],
);
$c_file = \%new_c_file;
$c_files{$filename} = \%new_c_file;
} else {
warn("Ignoring unexpected file $file\n");
next;
}
$smb1 = 1 if ($file =~ m!/smb1ops.c|/cifssmb.c|/cifstransport.c!);
foreach my $line (<$fh>) {
$lineno++;
chomp($line);
push @copy, $line;
if (!$line) {
# Blank line
push @file_content, @copy;
@copy = ();
next;
}
# Handle continuation or end of block comment. Look for C file
# prototype insertion point markers.
if ($comment) {
if ($line =~ m![*]/!) {
if ($comment == 2 && $file_marker) {
$cmarkers{$file_marker} = $file_marker;
push @copy, "#C_MARKER " . $file_marker;
$file_marker = 0;
}
$comment = 0;
} else {
$comment++;
if ($comment == 2 && $line =~ m! [*] ([a-z][a-z_0-9]*[.][c])$!) {
$file_marker = $1;
print("Found file marker ", $file_marker, " in ", $filename, "\n");
}
}
push @file_content, @copy;
@copy = ();
next;
}
# Check cpp directives, particularly looking for SMB1 bits
if ($line =~ /^[#]/) {
if ($header) {
if ($line =~ /ifdef.*(CONFIG_[A-Z0-9_])/) {
error("multiconfig") if $config;
$config = $1;
$smb1++ if ($config eq "CONFIG_CIFS_ALLOW_INSECURE_LEGACY");
} elsif ($line =~ /endif/) {
$smb1-- if ($config eq "CONFIG_CIFS_ALLOW_INSECURE_LEGACY");
$config = "";
}
}
push @file_content, @copy;
@copy = ();
next;
}
# Exclude interference in finding func names and return types
if ($line =~ /^[{]/ ||
$line =~ /##/ ||
$line =~ /^[_a-z0-9A-Z]+:$/ || # goto label
$line =~ /^do [{]/ ||
$line =~ m!^//!) {
push @file_content, @copy;
@copy = ();
next;
}
# Start of a block comment
if ($line =~ m!^/[*]!) {
$comment = 1 unless ($line =~ m![*]/!);
push @file_content, @copy;
@copy = ();
next;
}
# End of a braced section, such as a function implementation
if ($line =~ /^[}]/) {
$type = "";
$qual = "";
$funcname = "";
@funcdef = ();
push @file_content, @copy;
@copy = ();
next;
}
if ($line =~ /^typedef/) {
$type = "";
$qual = "";
$funcname = "";
@funcdef = ();
push @file_content, @copy;
@copy = ();
next;
}
# Extract function qualifiers. There may be multiple of these in more
# or less any order. Some of them cause the func to be skipped (e.g. inline).
if ($line =~ /^(static|extern|inline|noinline|noinline_for_stack|__always_inline)\W/ ||
$line =~ /^(static|extern|inline|noinline|noinline_for_stack|__always_inline)$/) {
error("Unexpected qualifier '$1'") if ($state != 0);
while ($line =~ /^(static|extern|inline|noinline|noinline_for_stack|__always_inline)\W/ ||
$line =~ /^(static|extern|inline|noinline|noinline_for_stack|__always_inline)$/) {
$qual .= " " if ($qual);
$qual .= $1;
$inline = 1 if ($1 eq "inline");
$inline = 1 if ($1 eq "__always_inline");
$line = substr($line, length($1));
$line =~ s/^\s+//;
}
}
if ($state == 0) {
# Extract what we assume to be the return type
if ($line =~ /^\s/) {
push @file_content, @copy;
@copy = ();
next;
}
while ($line =~ /^(unsigned|signed|bool|char|short|int|long|void|const|volatile|(struct|union|enum)\s+[_a-zA-Z][_a-zA-Z0-9]*|[*]|__init|__exit|__le16|__le32|__le64|__be16|__be32|__be64)/) {
$type .= " " if $type;
$type .= $1;
$line = substr($line, length($1));
$line =~ s/^\s+//;
}
if ($line =~ /^struct [{]/) {
# Ignore structure definitions
$type = "";
$qual = "";
$funcname = "";
@funcdef = ();
push @file_content, @copy;
@copy = ();
next;
}
if (index($line, "=") >= 0) {
# Ignore assignments
$type = "";
$qual = "";
$funcname = "";
@funcdef = "";
push @file_content, @copy;
@copy = ();
next;
}
# Try and extract a function's type and name
while ($line =~ /(^[_a-zA-Z][_a-zA-Z0-9]*)/) {
my $name = $1;
$line = substr($line, length($name));
next if ($line =~ /^[{]/);
$line =~ s/^\s+//;
my $ch = substr($line, 0, 1);
last if ($ch eq "[" || $ch eq ";"); # Global variables
if ($ch eq "(") {
# Found the function name
$state = 1;
$line = substr($line, 1);
$funcname = $name;
my $tmp = $qual . $type . " " . $funcname . "(";
$tmp =~ s/[*] /*/;
push @funcdef, $tmp;
$bracket = 1;
last;
}
if ($type) {
last if (index($line, ";") >= 0 && index($line, "(") == -1);
error("Unexpected name '$name' after '$type'");
}
$type .= " " if $type;
$type .= $name;
if ($line =~ /^(\s*[*]+)/) {
my $ptr = $1;
$type .= $ptr;
$line = substr($line, length($ptr));
}
}
}
# Try and extract a function's argument list
my $from = 0;
if ($state == 1) {
while (1) {
my $o = index($line, "(", $from);
my $c = index($line, ")", $from);
my $m = index($line, ",", $from);
my $b = earliest($o, $c, $m);
if ($b < 0) {
push @funcdef, $line
unless ($line eq "");
last;
}
my $ch = substr($line, $b, 1);
# Push the arguments separately on to the list
if ($ch eq ",") {
push @funcdef, substr($line, 0, $b + 1);
$line = substr($line, $b + 1);
$from = 0;
} elsif ($ch eq "(") {
# Handle brackets in the argument list (e.g. function
# pointers)
$bracket++;
$from = $b + 1;
} elsif ($ch eq ")") {
$bracket--;
if ($bracket == 0) {
push @funcdef, substr($line, 0, $b + 1);
$line = substr($line, $b + 1);
$state = 2;
last;
}
$from = $b + 1;
}
}
}
if ($state == 2) {
$inline = 1 if ($qual =~ /inline/);
#print("QUAL $qual $type $funcname $inline ", $#funcdef, "\n");
if (!$header &&
$qual !~ /static/ &&
$funcname ne "__acquires" &&
$funcname ne "__releases" &&
$funcname ne "module_init" &&
$funcname ne "module_exit" &&
$funcname ne "module_param" &&
$funcname ne "module_param_call" &&
$funcname ne "PROC_FILE_DEFINE" &&
$funcname !~ /MODULE_/ &&
$funcname !~ /DEFINE_/) {
# Okay, we appear to have a function implementation
my $func;
my $dup = 0;
if (exists($funcs{$funcname})) {
$func = $funcs{$funcname};
if (exists $func->{body}) {
print("dup $funcname\n");
$dup = 1;
}
} else {
my %new_func = (
name => $funcname,
cond => "",
legacy => 0,
);
$func = \%new_func;
$funcs{$funcname} = $func;
$func->{body} = pad(\@funcdef);
}
$func->{body} = pad(\@funcdef);
$func->{legacy} = 1 if $smb1;
if ($funcname eq "cifs_inval_name_dfs_link_error") {
$func->{cond} = "#ifdef CONFIG_CIFS_DFS_UPCALL";
} elsif ($funcname eq "cifs_listxattr") {
$func->{cond} = "#ifdef CONFIG_CIFS_XATTR";
}
push @{$c_file->{funcs}}, $func
unless $dup;
} elsif (!$header || $inline) {
# Ignore inline function implementations and other weirdies
push @file_content, @copy;
} elsif ($header && !$inline) {
push @file_content, "#FUNCPROTO " . $funcname;
my $func;
if (exists($funcs{$funcname})) {
$func = $funcs{$funcname};
$func->{lineno} = $lineno;
$func->{pathname} = $pathname;
} else {
my %new_func = (
name => $funcname,
cond => "",
lineno => $lineno,
pathname => $pathname,
legacy => 0,
);
$func = \%new_func;
$funcs{$funcname} = $func;
}
$func->{legacy} = 1 if $smb1;
}
@funcdef = ();
$type = "";
$qual = "";
$funcname = "";
$inline = 0;
$state = 0;
@copy = ();
}
if ($line =~ /;/) {
$type = "";
$qual = "";
$funcname = "";
@funcdef = ();
$state = 0;
push @file_content, @copy;
@copy = ();
}
}
close($fh);
if ($header) {
$header->{content} = \@file_content;
}
}
sub write_header($)
{
my ($header) = @_;
my $path = $header->{path};
my $legacy = 0;
$legacy = 1 if ($path =~ m!smb1proto[.]h!);
my @output = ();
foreach my $line (@{$header->{content}}) {
if ($line =~ "^[#]C_MARKER (.*)") {
my $file_marker = $cmarkers{$1};
my $c_file = $c_files{$file_marker};
print("Found $line\n");
foreach my $func (@{$c_file->{funcs}}) {
print("func ", $func->{name}, "\n");
push @output, @{$func->{body}};
}
next;
} elsif ($line =~ "^[#]FUNCPROTO ([_a-zA-Z0-9]+)") {
my $funcname = $1;
my $func = $funcs{$funcname};
if (!$func->{body}) {
print($func->{pathname}, ":", $func->{lineno}, ": '", $funcname,
"' dead prototype\n");
next;
}
if ($func->{legacy} == $legacy) {
#push @output, $line;
push @output, @{$func->{body}};
}
} else {
push @output, $line;
}
}
open my $fh, ">$path"
or die "Could not open file '$path' for writing";
foreach my $f (@output) {
print($fh $f, "\n") or die $path;
}
close($fh) or die $path;
}
foreach my $h (keys(%headers)) {
write_header($headers{$h});
}
Signed-off-by: David Howells <dhowells@xxxxxxxxxx>
cc: Steve French <sfrench@xxxxxxxxx>
cc: Paulo Alcantara <pc@xxxxxxxxxxxxx>
cc: Enzo Matsumiya <ematsumiya@xxxxxxx>
cc: linux-cifs@xxxxxxxxxxxxxxx
cc: linux-fsdevel@xxxxxxxxxxxxxxx
cc: linux-kernel@xxxxxxxxxxxxxxx
Acked-by: Enzo Matsumiya <ematsumiya@xxxxxxx>
Signed-off-by: Steve French <stfrench@xxxxxxxxxxxxx>
[ sashal: Reduced backport -- upstream 645427b7a6c59 touches 3 file(s), this
backport carries 3. Not backported here:
fs/smb/client/cifsproto.h
fs/smb/client/fscache.h
This note is generated from the file lists only; see the resolution record
for the reasoning. ]
Stable-dep-of: 6343c1da5619 ("smb: client: fix OOB read/write from unvalidated DataOffset in coalesce_t2()")
Signed-off-by: Sasha Levin <sashal@xxxxxxxxxx>
---
fs/smb/client/smb1ops.c | 171 ----------------------------------
fs/smb/client/smb1proto.h | 2 +
fs/smb/client/smb1transport.c | 171 ++++++++++++++++++++++++++++++++++
3 files changed, 173 insertions(+), 171 deletions(-)
diff --git a/fs/smb/client/smb1ops.c b/fs/smb/client/smb1ops.c
index ca8f3dd7ff63b..5a500a5e138c1 100644
--- a/fs/smb/client/smb1ops.c
+++ b/fs/smb/client/smb1ops.c
@@ -238,146 +238,6 @@ cifs_get_next_mid(struct TCP_Server_Info *server)
return mid;
}
-/*
- return codes:
- 0 not a transact2, or all data present
- >0 transact2 with that much data missing
- -EINVAL invalid transact2
- */
-static int
-check2ndT2(char *buf)
-{
- struct smb_hdr *pSMB = (struct smb_hdr *)buf;
- struct smb_t2_rsp *pSMBt;
- int remaining;
- __u16 total_data_size, data_in_this_rsp;
-
- if (pSMB->Command != SMB_COM_TRANSACTION2)
- return 0;
-
- /* check for plausible wct, bcc and t2 data and parm sizes */
- /* check for parm and data offset going beyond end of smb */
- if (pSMB->WordCount != 10) { /* coalesce_t2 depends on this */
- cifs_dbg(FYI, "Invalid transact2 word count\n");
- return -EINVAL;
- }
-
- pSMBt = (struct smb_t2_rsp *)pSMB;
-
- total_data_size = get_unaligned_le16(&pSMBt->t2_rsp.TotalDataCount);
- data_in_this_rsp = get_unaligned_le16(&pSMBt->t2_rsp.DataCount);
-
- if (total_data_size == data_in_this_rsp)
- return 0;
- else if (total_data_size < data_in_this_rsp) {
- cifs_dbg(FYI, "total data %d smaller than data in frame %d\n",
- total_data_size, data_in_this_rsp);
- return -EINVAL;
- }
-
- remaining = total_data_size - data_in_this_rsp;
-
- cifs_dbg(FYI, "missing %d bytes from transact2, check next response\n",
- remaining);
- if (total_data_size > CIFSMaxBufSize) {
- cifs_dbg(VFS, "TotalDataSize %d is over maximum buffer %d\n",
- total_data_size, CIFSMaxBufSize);
- return -EINVAL;
- }
- return remaining;
-}
-
-static int
-coalesce_t2(char *second_buf, struct smb_hdr *target_hdr)
-{
- struct smb_t2_rsp *pSMBs = (struct smb_t2_rsp *)second_buf;
- struct smb_t2_rsp *pSMBt = (struct smb_t2_rsp *)target_hdr;
- char *data_area_of_tgt;
- char *data_area_of_src;
- int remaining;
- unsigned int byte_count, total_in_tgt;
- __u16 tgt_total_cnt, src_total_cnt, total_in_src;
-
- src_total_cnt = get_unaligned_le16(&pSMBs->t2_rsp.TotalDataCount);
- tgt_total_cnt = get_unaligned_le16(&pSMBt->t2_rsp.TotalDataCount);
-
- if (tgt_total_cnt != src_total_cnt)
- cifs_dbg(FYI, "total data count of primary and secondary t2 differ source=%hu target=%hu\n",
- src_total_cnt, tgt_total_cnt);
-
- total_in_tgt = get_unaligned_le16(&pSMBt->t2_rsp.DataCount);
-
- remaining = tgt_total_cnt - total_in_tgt;
-
- if (remaining < 0) {
- cifs_dbg(FYI, "Server sent too much data. tgt_total_cnt=%hu total_in_tgt=%u\n",
- tgt_total_cnt, total_in_tgt);
- return -EPROTO;
- }
-
- if (remaining == 0) {
- /* nothing to do, ignore */
- cifs_dbg(FYI, "no more data remains\n");
- return 0;
- }
-
- total_in_src = get_unaligned_le16(&pSMBs->t2_rsp.DataCount);
- if (remaining < total_in_src)
- cifs_dbg(FYI, "transact2 2nd response contains too much data\n");
-
- /* find end of first SMB data area */
- data_area_of_tgt = (char *)&pSMBt->hdr.Protocol +
- get_unaligned_le16(&pSMBt->t2_rsp.DataOffset);
-
- /* validate target area */
- data_area_of_src = (char *)&pSMBs->hdr.Protocol +
- get_unaligned_le16(&pSMBs->t2_rsp.DataOffset);
-
- data_area_of_tgt += total_in_tgt;
-
- total_in_tgt += total_in_src;
- /* is the result too big for the field? */
- if (total_in_tgt > USHRT_MAX) {
- cifs_dbg(FYI, "coalesced DataCount too large (%u)\n",
- total_in_tgt);
- return -EPROTO;
- }
- put_unaligned_le16(total_in_tgt, &pSMBt->t2_rsp.DataCount);
-
- /* fix up the BCC */
- byte_count = get_bcc(target_hdr);
- byte_count += total_in_src;
- /* is the result too big for the field? */
- if (byte_count > USHRT_MAX) {
- cifs_dbg(FYI, "coalesced BCC too large (%u)\n", byte_count);
- return -EPROTO;
- }
- put_bcc(byte_count, target_hdr);
-
- byte_count = be32_to_cpu(target_hdr->smb_buf_length);
- byte_count += total_in_src;
- /* don't allow buffer to overflow */
- if (byte_count > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE - 4) {
- cifs_dbg(FYI, "coalesced BCC exceeds buffer size (%u)\n",
- byte_count);
- return -ENOBUFS;
- }
- target_hdr->smb_buf_length = cpu_to_be32(byte_count);
-
- /* copy second buffer into end of first buffer */
- memcpy(data_area_of_tgt, data_area_of_src, total_in_src);
-
- if (remaining != total_in_src) {
- /* more responses to go */
- cifs_dbg(FYI, "waiting for more secondary responses\n");
- return 1;
- }
-
- /* we are done */
- cifs_dbg(FYI, "found the last secondary response\n");
- return 0;
-}
-
static void
cifs_downgrade_oplock(struct TCP_Server_Info *server,
struct cifsInodeInfo *cinode, __u32 oplock,
@@ -386,37 +246,6 @@ cifs_downgrade_oplock(struct TCP_Server_Info *server,
cifs_set_oplock_level(cinode, oplock);
}
-static bool
-cifs_check_trans2(struct mid_q_entry *mid, struct TCP_Server_Info *server,
- char *buf, int malformed)
-{
- if (malformed)
- return false;
- if (check2ndT2(buf) <= 0)
- return false;
- mid->multiRsp = true;
- if (mid->resp_buf) {
- /* merge response - fix up 1st*/
- malformed = coalesce_t2(buf, mid->resp_buf);
- if (malformed > 0)
- return true;
- /* All parts received or packet is malformed. */
- mid->multiEnd = true;
- dequeue_mid(mid, malformed);
- return true;
- }
- if (!server->large_buf) {
- /*FIXME: switch to already allocated largebuf?*/
- cifs_dbg(VFS, "1st trans2 resp needs bigbuf\n");
- } else {
- /* Have first buffer */
- mid->resp_buf = buf;
- mid->large_buf = true;
- server->bigbuf = NULL;
- }
- return true;
-}
-
static bool
cifs_need_neg(struct TCP_Server_Info *server)
{
diff --git a/fs/smb/client/smb1proto.h b/fs/smb/client/smb1proto.h
index a73213f6a2aa1..8dfc6976ceba1 100644
--- a/fs/smb/client/smb1proto.h
+++ b/fs/smb/client/smb1proto.h
@@ -33,6 +33,8 @@ extern struct smb_version_values smb1_values;
/*
* smb1transport.c
*/
+bool cifs_check_trans2(struct mid_q_entry *mid, struct TCP_Server_Info *server,
+ char *buf, int malformed);
#endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */
#endif /* _SMB1PROTO_H */
diff --git a/fs/smb/client/smb1transport.c b/fs/smb/client/smb1transport.c
index bafc5181e0292..f7b00bcb9df1a 100644
--- a/fs/smb/client/smb1transport.c
+++ b/fs/smb/client/smb1transport.c
@@ -563,3 +563,174 @@ SendReceiveBlockingLock(const unsigned int xid, struct cifs_tcon *tcon,
return -ERESTARTSYS;
return rc;
}
+
+/*
+ return codes:
+ 0 not a transact2, or all data present
+ >0 transact2 with that much data missing
+ -EINVAL invalid transact2
+ */
+static int
+check2ndT2(char *buf)
+{
+ struct smb_hdr *pSMB = (struct smb_hdr *)buf;
+ struct smb_t2_rsp *pSMBt;
+ int remaining;
+ __u16 total_data_size, data_in_this_rsp;
+
+ if (pSMB->Command != SMB_COM_TRANSACTION2)
+ return 0;
+
+ /* check for plausible wct, bcc and t2 data and parm sizes */
+ /* check for parm and data offset going beyond end of smb */
+ if (pSMB->WordCount != 10) { /* coalesce_t2 depends on this */
+ cifs_dbg(FYI, "Invalid transact2 word count\n");
+ return -EINVAL;
+ }
+
+ pSMBt = (struct smb_t2_rsp *)pSMB;
+
+ total_data_size = get_unaligned_le16(&pSMBt->t2_rsp.TotalDataCount);
+ data_in_this_rsp = get_unaligned_le16(&pSMBt->t2_rsp.DataCount);
+
+ if (total_data_size == data_in_this_rsp)
+ return 0;
+ else if (total_data_size < data_in_this_rsp) {
+ cifs_dbg(FYI, "total data %d smaller than data in frame %d\n",
+ total_data_size, data_in_this_rsp);
+ return -EINVAL;
+ }
+
+ remaining = total_data_size - data_in_this_rsp;
+
+ cifs_dbg(FYI, "missing %d bytes from transact2, check next response\n",
+ remaining);
+ if (total_data_size > CIFSMaxBufSize) {
+ cifs_dbg(VFS, "TotalDataSize %d is over maximum buffer %d\n",
+ total_data_size, CIFSMaxBufSize);
+ return -EINVAL;
+ }
+ return remaining;
+}
+
+static int
+coalesce_t2(char *second_buf, struct smb_hdr *target_hdr)
+{
+ struct smb_t2_rsp *pSMBs = (struct smb_t2_rsp *)second_buf;
+ struct smb_t2_rsp *pSMBt = (struct smb_t2_rsp *)target_hdr;
+ char *data_area_of_tgt;
+ char *data_area_of_src;
+ int remaining;
+ unsigned int byte_count, total_in_tgt;
+ __u16 tgt_total_cnt, src_total_cnt, total_in_src;
+
+ src_total_cnt = get_unaligned_le16(&pSMBs->t2_rsp.TotalDataCount);
+ tgt_total_cnt = get_unaligned_le16(&pSMBt->t2_rsp.TotalDataCount);
+
+ if (tgt_total_cnt != src_total_cnt)
+ cifs_dbg(FYI, "total data count of primary and secondary t2 differ source=%hu target=%hu\n",
+ src_total_cnt, tgt_total_cnt);
+
+ total_in_tgt = get_unaligned_le16(&pSMBt->t2_rsp.DataCount);
+
+ remaining = tgt_total_cnt - total_in_tgt;
+
+ if (remaining < 0) {
+ cifs_dbg(FYI, "Server sent too much data. tgt_total_cnt=%hu total_in_tgt=%u\n",
+ tgt_total_cnt, total_in_tgt);
+ return -EPROTO;
+ }
+
+ if (remaining == 0) {
+ /* nothing to do, ignore */
+ cifs_dbg(FYI, "no more data remains\n");
+ return 0;
+ }
+
+ total_in_src = get_unaligned_le16(&pSMBs->t2_rsp.DataCount);
+ if (remaining < total_in_src)
+ cifs_dbg(FYI, "transact2 2nd response contains too much data\n");
+
+ /* find end of first SMB data area */
+ data_area_of_tgt = (char *)&pSMBt->hdr.Protocol +
+ get_unaligned_le16(&pSMBt->t2_rsp.DataOffset);
+
+ /* validate target area */
+ data_area_of_src = (char *)&pSMBs->hdr.Protocol +
+ get_unaligned_le16(&pSMBs->t2_rsp.DataOffset);
+
+ data_area_of_tgt += total_in_tgt;
+
+ total_in_tgt += total_in_src;
+ /* is the result too big for the field? */
+ if (total_in_tgt > USHRT_MAX) {
+ cifs_dbg(FYI, "coalesced DataCount too large (%u)\n",
+ total_in_tgt);
+ return -EPROTO;
+ }
+ put_unaligned_le16(total_in_tgt, &pSMBt->t2_rsp.DataCount);
+
+ /* fix up the BCC */
+ byte_count = get_bcc(target_hdr);
+ byte_count += total_in_src;
+ /* is the result too big for the field? */
+ if (byte_count > USHRT_MAX) {
+ cifs_dbg(FYI, "coalesced BCC too large (%u)\n", byte_count);
+ return -EPROTO;
+ }
+ put_bcc(byte_count, target_hdr);
+
+ byte_count = be32_to_cpu(target_hdr->smb_buf_length);
+ byte_count += total_in_src;
+ /* don't allow buffer to overflow */
+ if (byte_count > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE - 4) {
+ cifs_dbg(FYI, "coalesced BCC exceeds buffer size (%u)\n",
+ byte_count);
+ return -ENOBUFS;
+ }
+ target_hdr->smb_buf_length = cpu_to_be32(byte_count);
+
+ /* copy second buffer into end of first buffer */
+ memcpy(data_area_of_tgt, data_area_of_src, total_in_src);
+
+ if (remaining != total_in_src) {
+ /* more responses to go */
+ cifs_dbg(FYI, "waiting for more secondary responses\n");
+ return 1;
+ }
+
+ /* we are done */
+ cifs_dbg(FYI, "found the last secondary response\n");
+ return 0;
+}
+
+bool
+cifs_check_trans2(struct mid_q_entry *mid, struct TCP_Server_Info *server,
+ char *buf, int malformed)
+{
+ if (malformed)
+ return false;
+ if (check2ndT2(buf) <= 0)
+ return false;
+ mid->multiRsp = true;
+ if (mid->resp_buf) {
+ /* merge response - fix up 1st*/
+ malformed = coalesce_t2(buf, mid->resp_buf);
+ if (malformed > 0)
+ return true;
+ /* All parts received or packet is malformed. */
+ mid->multiEnd = true;
+ dequeue_mid(mid, malformed);
+ return true;
+ }
+ if (!server->large_buf) {
+ /*FIXME: switch to already allocated largebuf?*/
+ cifs_dbg(VFS, "1st trans2 resp needs bigbuf\n");
+ } else {
+ /* Have first buffer */
+ mid->resp_buf = buf;
+ mid->large_buf = true;
+ server->bigbuf = NULL;
+ }
+ return true;
+}
--
2.53.0