a8b7c1dd |
/* |
2023340a |
* Copyright (C) 2007-2008 Sourcefire, Inc.
*
* Authors: Nigel Horne |
a8b7c1dd |
*
* This program is free software; you can redistribute it and/or modify |
2023340a |
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation. |
a8b7c1dd |
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software |
48b7b4a7 |
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA. |
a8b7c1dd |
*/ |
2023340a |
|
dea34e7d |
#if HAVE_CONFIG_H
#include "clamav-config.h"
#endif |
95fb46e5 |
#include <stdio.h>
#include <errno.h>
#include <string.h> |
45b28aba |
#ifdef HAVE_UNISTD_H |
95fb46e5 |
#include <unistd.h> |
45b28aba |
#endif |
95fb46e5 |
#include <sys/stat.h> |
bb3fdd1b |
#include <fcntl.h> |
45b28aba |
#ifdef HAVE_SYS_PARAM_H |
7c5a7a47 |
#include <sys/param.h> /* for NAME_MAX */ |
45b28aba |
#endif |
95fb46e5 |
#include "clamav.h"
#include "others.h" |
bb3fdd1b |
#include "untar.h" |
11b50569 |
#include "mbox.h"
#include "blob.h" |
96522097 |
#include "scanners.h" |
570b1d00 |
#include "matcher.h" |
95fb46e5 |
#define BLOCKSIZE 512 |
eeeca3bd |
#define TARSIZEOFFSET 124
#define TARSIZELEN 12
#define TARCHECKSUMOFFSET 148
#define TARCHECKSUMLEN 8
#define TARFILETYPEOFFSET 156 |
95fb46e5 |
|
bb3fdd1b |
static int
octal(const char *str) |
95fb46e5 |
{ |
ed734e16 |
int ret; |
95fb46e5 |
|
ed734e16 |
if(sscanf(str, "%o", (unsigned int *)&ret) != 1)
return -1; |
95fb46e5 |
return ret;
}
|
eeeca3bd |
/**
* Retrieve checksum values from a tar header block.
* @param header Header data block, padded with zeroes to reach BLOCKSIZE
* @return int value of checksum, -1 (from octal()) if bad value
*/
static int
getchecksum(const char *header)
{
char ochecksum[TARCHECKSUMLEN + 1];
int checksum = -1;
strncpy(ochecksum, header+TARCHECKSUMOFFSET, TARCHECKSUMLEN);
ochecksum[TARCHECKSUMLEN] = '\0';
checksum = octal(ochecksum);
return checksum;
}
/**
* Calculate checksum values for tar header blocks.
* @param header Header data block, padded with zeroes to reach BLOCKSIZE
* @param targetsum Check value to match (as int not octal!)
* @return 0 if checksum matches target, -1 if not
*/
static int
testchecksum(const char *header, int targetsum)
{
const unsigned char *posix;
const signed char *legacy;
int posix_sum = 0, legacy_sum = 0;
int i;
// targetsum -1 represents an error from octal()
if (targetsum == -1) {
return -1;
}
/* Build checksums. POSIX is unsigned; some legacy tars use signed. */
posix = (unsigned char *)header;
legacy = (signed char *)header;
for (i = 0; i < BLOCKSIZE; i++ ) {
if ((i >= TARCHECKSUMOFFSET) && (i < TARCHECKSUMOFFSET + TARCHECKSUMLEN)) {
/* Use ascii value of space in place of checksum value */
posix_sum += 32;
legacy_sum += 32;
}
else {
posix_sum += posix[i];
legacy_sum += legacy[i];
}
}
if ((targetsum == posix_sum) || (targetsum == legacy_sum)) {
return 0;
}
return -1;
}
|
95fb46e5 |
int |
a91f6d95 |
cli_untar(const char *dir, unsigned int posix, cli_ctx *ctx) |
95fb46e5 |
{ |
d0d1afd7 |
int size = 0, ret, fout=-1; |
95fb46e5 |
int in_block = 0; |
eeeca3bd |
int last_header_bad = 0; |
b1c49f39 |
int limitnear = 0; |
9140eb10 |
unsigned int files = 0; |
bb3fdd1b |
char fullname[NAME_MAX + 1]; |
a91f6d95 |
size_t pos = 0; |
b1c49f39 |
size_t currsize = 0; |
c904e1c9 |
char zero[BLOCKSIZE]; |
6ad45a29 |
unsigned int num_viruses = 0; |
95fb46e5 |
|
a91f6d95 |
cli_dbgmsg("In untar(%s)\n", dir); |
c904e1c9 |
memset(zero, 0, sizeof(zero)); |
95fb46e5 |
for(;;) { |
a91f6d95 |
const char *block; |
ec591c0a |
size_t nread; |
95fb46e5 |
|
a91f6d95 |
block = fmap_need_off_once_len(*ctx->fmap, pos, BLOCKSIZE, &nread); |
b1c49f39 |
cli_dbgmsg("cli_untar: pos = %lu\n", (unsigned long)pos); |
a91f6d95 |
if(!in_block && !nread) |
95fb46e5 |
break;
|
c904e1c9 |
if (!nread)
block = zero;
|
a91f6d95 |
if(!block) { |
d0d1afd7 |
if(fout>=0)
close(fout); |
f0931086 |
cli_errmsg("cli_untar: block read error\n"); |
871177cd |
return CL_EREAD; |
95fb46e5 |
} |
a91f6d95 |
pos += nread; |
95fb46e5 |
if(!in_block) {
char type; |
d0d1afd7 |
int directory, skipEntry = 0; |
eeeca3bd |
int checksum = -1;
char magic[7], name[101], osize[TARSIZELEN + 1]; |
b1c49f39 |
currsize = 0; |
bb3fdd1b |
|
d0d1afd7 |
if(fout>=0) {
lseek(fout, 0, SEEK_SET);
ret = cli_magic_scandesc(fout, ctx);
close(fout); |
33068e09 |
if (!ctx->engine->keeptmp) |
871177cd |
if (cli_unlink(fullname)) return CL_EUNLINK; |
6ad45a29 |
if (ret==CL_VIRUS) {
if (!SCAN_ALL) |
d0d1afd7 |
return CL_VIRUS; |
6ad45a29 |
else
num_viruses++;
} |
d0d1afd7 |
fout = -1; |
bb3fdd1b |
} |
95fb46e5 |
|
7c5a7a47 |
if(block[0] == '\0') /* We're done */ |
95fb46e5 |
break; |
d91ab809 |
if((ret=cli_checklimits("cli_untar", ctx, 0, 0, 0))!=CL_CLEAN)
return ret; |
9140eb10 |
|
eeeca3bd |
checksum = getchecksum(block);
cli_dbgmsg("cli_untar: Candidate checksum = %d, [%o in octal]\n", checksum, checksum);
if(testchecksum(block, checksum) != 0) {
// If checksum is bad, dump and look for next header block
cli_dbgmsg("cli_untar: Invalid checksum in tar header. Skip to next...\n");
if (last_header_bad == 0) {
last_header_bad++; |
55875b9e |
cli_dbgmsg("cli_untar: Invalid checksum found inside archive!\n"); |
eeeca3bd |
}
continue;
} else {
last_header_bad = 0;
cli_dbgmsg("cli_untar: Checksum %d is valid.\n", checksum);
}
|
90e80a54 |
/* Notice assumption that BLOCKSIZE > 262 */ |
a7f5fd00 |
if(posix) {
strncpy(magic, block+257, 5);
magic[5] = '\0';
if(strcmp(magic, "ustar") != 0) { |
9fc9db81 |
cli_dbgmsg("cli_untar: Incorrect magic string '%s' in tar header\n", magic); |
a7f5fd00 |
return CL_EFORMAT;
} |
95fb46e5 |
}
|
eeeca3bd |
type = block[TARFILETYPEOFFSET]; |
95fb46e5 |
switch(type) { |
45f8ad14 |
default: |
9fc9db81 |
cli_dbgmsg("cli_untar: unknown type flag %c\n", type); |
63943caf |
case '0': /* plain file */
case '\0': /* plain file */
case '7': /* contiguous file */ |
45f8ad14 |
case 'M': /* continuation of a file from another volume; might as well scan it. */ |
9140eb10 |
files++; |
95fb46e5 |
directory = 0;
break; |
ab592ce9 |
case '1': /* Link to already archived file */ |
63943caf |
case '5': /* directory */
case '2': /* sym link */
case '3': /* char device */
case '4': /* block device */
case '6': /* fifo special */ |
d2888b89 |
case 'V': /* Volume header */ |
95fb46e5 |
directory = 1;
break; |
d2888b89 |
case 'K':
case 'L':
/* GNU extension - ././@LongLink
* Discard the blocks with the extended filename,
* the last header will contain parts of it anyway
*/ |
45f8ad14 |
case 'N': /* Old GNU format way of storing long filenames. */
case 'A': /* Solaris ACL */
case 'E': /* Solaris Extended attribute s*/
case 'I': /* Inode only */
case 'g': /* Global extended header */
case 'x': /* Extended attributes */
case 'X': /* Extended attributes (POSIX) */ |
d2888b89 |
directory = 0;
skipEntry = 1;
break; |
95fb46e5 |
}
|
63943caf |
if(directory) {
in_block = 0; |
95fb46e5 |
continue; |
63943caf |
} |
95fb46e5 |
|
eeeca3bd |
strncpy(osize, block+TARSIZEOFFSET, TARSIZELEN);
osize[TARSIZELEN] = '\0'; |
d2888b89 |
size = octal(osize);
if(size < 0) { |
9fc9db81 |
cli_dbgmsg("cli_untar: Invalid size in tar header\n"); |
9140eb10 |
skipEntry++; |
9fc9db81 |
} else {
cli_dbgmsg("cli_untar: size = %d\n", size); |
b1c49f39 |
ret = cli_checklimits("cli_untar", ctx, size, 0, 0);
switch(ret) {
case CL_EMAXFILES: // Scan no more files
skipEntry++;
limitnear = 0;
break;
case CL_EMAXSIZE: // Either single file limit or total byte limit would be exceeded
cli_dbgmsg("cli_untar: would exceed limit, will try up to max");
limitnear = 1;
break;
default: // Ok based on reported content size
limitnear = 0;
break;
} |
9140eb10 |
} |
d2888b89 |
if(skipEntry) {
const int nskip = (size % BLOCKSIZE || !size) ? size + BLOCKSIZE - (size % BLOCKSIZE) : size; |
a91f6d95 |
|
2995f1c7 |
if(nskip < 0) { |
eeeca3bd |
cli_dbgmsg("cli_untar: got negative skip size, giving up\n"); |
2995f1c7 |
return CL_CLEAN;
} |
9140eb10 |
cli_dbgmsg("cli_untar: skipping entry\n"); |
a91f6d95 |
pos += nskip; |
d2888b89 |
continue;
}
|
bb3fdd1b |
strncpy(name, block, 100);
name[100] = '\0'; |
6ad45a29 |
if(cli_matchmeta(ctx, name, size, size, 0, files, 0, NULL) == CL_VIRUS) {
if (!SCAN_ALL)
return CL_VIRUS;
else
num_viruses++;
} |
570b1d00 |
|
58481352 |
snprintf(fullname, sizeof(fullname)-1, "%s"PATHSEP"tar%02u", dir, files); |
d0d1afd7 |
fullname[sizeof(fullname)-1] = '\0';
fout = open(fullname, O_RDWR|O_CREAT|O_EXCL|O_TRUNC|O_BINARY, 0600); |
95fb46e5 |
|
d0d1afd7 |
if(fout < 0) { |
e68d70e7 |
char err[128];
cli_errmsg("cli_untar: Can't create temporary file %s: %s\n", fullname, cli_strerror(errno, err, sizeof(err))); |
bb3fdd1b |
return CL_ETMPFILE;
} |
e68d70e7 |
|
d0d1afd7 |
cli_dbgmsg("cli_untar: extracting to %s\n", fullname); |
bb3fdd1b |
in_block = 1; |
95fb46e5 |
} else { /* write or continue writing file contents */ |
c904e1c9 |
int nbytes, nwritten; |
b1c49f39 |
int skipwrite = 0; |
c904e1c9 |
char err[128];
nbytes = size>512? 512:size; |
cd94be7a |
if (nread && nread < (size_t)nbytes) |
c904e1c9 |
nbytes = nread; |
95fb46e5 |
|
b1c49f39 |
if (limitnear > 0) {
currsize += nbytes;
cli_dbgmsg("cli_untar: Approaching limit...\n");
if (cli_checklimits("cli_untar", ctx, (unsigned long)currsize, 0, 0) != CL_SUCCESS) {
// Limit would be exceeded by this file, suppress writing beyond limit
// Need to keep reading to get to end of file chunk
skipwrite++;
}
}
if (skipwrite == 0) {
nwritten = (int)cli_writen(fout, block, (size_t)nbytes);
if(nwritten != nbytes) {
cli_errmsg("cli_untar: only wrote %d bytes to file %s (out of disc space?): %s\n",
nwritten, fullname, cli_strerror(errno, err, sizeof(err)));
close(fout);
return CL_EWRITE;
} |
95fb46e5 |
}
size -= nbytes; |
b1c49f39 |
if ((size != 0) && (nread == 0)) {
// Truncated tar file, so end file content like tar behavior
cli_dbgmsg("cli_untar: No bytes read! Forcing end of file content.\n");
size = 0;
} |
95fb46e5 |
} |
d4112005 |
if (size == 0)
in_block = 0; |
a91f6d95 |
} |
d0d1afd7 |
if(fout>=0) {
lseek(fout, 0, SEEK_SET);
ret = cli_magic_scandesc(fout, ctx);
close(fout); |
33068e09 |
if (!ctx->engine->keeptmp) |
871177cd |
if (cli_unlink(fullname)) return CL_EUNLINK; |
d0d1afd7 |
if (ret==CL_VIRUS)
return CL_VIRUS;
} |
6ad45a29 |
if (num_viruses)
return CL_VIRUS; |
d0d1afd7 |
return CL_CLEAN; |
95fb46e5 |
} |