'php file upload scanning using clamav, permissions on /tmp/

I'm trying to scan files (generally 100MB+ zips) using clamav on apache 2.4, php 5.6, using a socket to the clamav-daemon. I'm not using PHP-FPM. (p.s. the socket works, I can send a PING and get a PONG).

<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);

if (isset($_FILES['file']) && is_uploaded_file($_FILES['file']['tmp_name'])) {

    $socket = socket_create(AF_UNIX, SOCK_STREAM, 0);
    if(socket_connect($socket, '/var/run/clamav/clamd.ctl')) {
        $result = "";
        $file = $_FILES['file']['tmp_name'];
        socket_send($socket, "SCAN $file", strlen($file) + 5, 0);
        socket_recv($socket, $result, 20000, 0);
        var_dump($result);
    }
    socket_close($socket);
}
?>

<form method="post" enctype="multipart/form-data"><input type="file" name="file"><input type="submit"></form>

Uploading a file yields:

string(65) "/tmp/phpxYBjyS: lstat() failed: No such file or directory. ERROR "

This seems to me like a permission error (even though /tmp is drwxrwxrwt 9 root root 4096 Dec 4 13:10 tmp). But I could be wrong. I can't easily look at the file since after the php process finishes the file is removed.

clamdscan, at the command line, works of course (e.g. /tmp/virus.txt: Eicar-Test-Signature FOUND).

My /etc/clamav/clamd.conf is still default. It looks like this:

LocalSocket /var/run/clamav/clamd.ctl
FixStaleSocket true
LocalSocketGroup clamav
LocalSocketMode 666
# TemporaryDirectory is not set to its default /tmp here to make overriding
# the default with environment variables TMPDIR/TMP/TEMP possible
User clamav
AllowSupplementaryGroups true
ScanMail true
ScanArchive true
ArchiveBlockEncrypted false
MaxDirectoryRecursion 15
FollowDirectorySymlinks false
FollowFileSymlinks false
ReadTimeout 180
MaxThreads 12
MaxConnectionQueueLength 15
LogSyslog false
LogRotate true
LogFacility LOG_LOCAL6
LogClean false
LogVerbose false
DatabaseDirectory /var/lib/clamav
OfficialDatabaseOnly false
SelfCheck 3600
Foreground false
Debug false
ScanPE true
MaxEmbeddedPE 10M
ScanOLE2 true
ScanPDF true
ScanHTML true
MaxHTMLNormalize 10M
MaxHTMLNoTags 2M
MaxScriptNormalize 5M
MaxZipTypeRcg 1M
ScanSWF true
DetectBrokenExecutables false
ExitOnOOM false
LeaveTemporaryFiles false
AlgorithmicDetection true
ScanELF true
IdleTimeout 30
CrossFilesystems true
PhishingSignatures true
PhishingScanURLs true
PhishingAlwaysBlockSSLMismatch false
PhishingAlwaysBlockCloak false
PartitionIntersection false
DetectPUA false
ScanPartialMessages false
HeuristicScanPrecedence false
StructuredDataDetection false
CommandReadTimeout 5
SendBufTimeout 200
MaxQueue 100
ExtendedDetectionInfo true
OLE2BlockMacros false
ScanOnAccess false
AllowAllMatchScan true
ForceToDisk false
DisableCertCheck false
DisableCache false
MaxScanSize 100M
MaxFileSize 25M
MaxRecursion 16
MaxFiles 10000
MaxPartitions 50
MaxIconsPE 100
PCREMatchLimit 10000
PCRERecMatchLimit 5000
PCREMaxFileSize 25M
ScanXMLDOCS true
ScanHWP3 true
MaxRecHWP3 16
StatsEnabled false
StatsPEDisabled true
StatsHostID auto
StatsTimeout 10
StreamMaxLength 25M
LogFile /var/log/clamav/clamav.log
LogTime true
LogFileUnlock false
LogFileMaxSize 0
Bytecode true
BytecodeSecurity TrustSigned
BytecodeTimeout 60000

/Edit Tried using exec rather than a socket.

if (isset($_FILES['file']) && is_uploaded_file($_FILES['file']['tmp_name'])) {
  $path = escapeshellarg($_FILES['file']['tmp_name']);
  $code = -1;
  $result = '';
  exec('clamdscan ' . $path, $result, $code);
  if ($code !== 0) {
    var_dump($result);
  }
}

this also yields a similar error

array(6) {
[0]=>
string(64) "/tmp/php2hQTE8: lstat() failed: No such file or directory. ERROR"
[1]=>
string(0) ""
[2]=>
string(36) "----------- SCAN SUMMARY -----------"
[3]=>
string(17) "Infected files: 0"
[4]=>
string(15) "Total errors: 1"
[5]=>
string(25) "Time: 0.000 sec (0 m 0 s)"
}


Solution 1:[1]

This looks like Moodle is running under a service that is using systemd PrivateTmp.

Many distributions now configure systemd units for services like apache & php-fpm to give the service its own, private, tmp directory (at least partly for security reasons).

If you look in the systemd unit files for apache or php-fpm (look under /lib/systemd/system for a file called something like apache2.service, httpd.service or php-fpm.service), you'll probably see the line PrivateTmp=true in the [Service] section.

The best way to override this, if that is what you decide you need to do, is to create an override file in the relevant /etc/systemd/systemd/ subdirectory for the service. You can do this using systemctl edit for the service in question, e.g. systemctl edit php-fpm.service if running under php-fpm on RHEL. The service name will be different for apache2 depending on what distribution you're on.

The override file should contain just:

# A comment to explain why you're doing this
[Service]
PrivateTmp=false

You'll likely also need to add the user clamd is running as to the webserver group, to give it permission to access the temp file once it can actually see it, e.g. usermod -a -G apache clamscan on RHEL.

Solution 2:[2]

Try passing --fdpass as an option to clamdscan.

I had a similar issue where files can be found with clamscan but not clamdscan and it worked for me.

--fdpass Pass the file descriptor permissions to clamd. This is useful if clamd is running as a different user as it is faster than streaming the file to clamd. Only available if connected to clamd via local(unix) socket.

Source: https://linux.die.net/man/1/clamdscan

Solution 3:[3]

Running clamdscan $the_file won't necessarily work, because of Apache's systemd-private-tmp. And you get the log message "File path check failure: No such file or directory."

But the easy way is: cat $the_file | clamdscan - Or, use the --stream option, (though I found the --fdpass option doesn't help).

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Nick Phillips
Solution 2 Rejinderi
Solution 3