On 2020-02-11 at 16:57 +0000, Mickey Sola (micksola) via clamav-users
wrote:
> Wanted to add a bit of insight to this convo from the dev side of
> things:
> 
> VirusEvent currently works by forking the existing clamd process into
> a new, short-lived process that handles execution of the user's
> script.
> 
> This is a legacy design choice and is problematic for a number of
> reasons--most relevant here is that you will need at minimum 2x the
> amount of resources clamd is already using to execute the VirusEvent.
> It was this resource drain, combined with the threaded nature of the
> old on access code, which led to us disabling the feature (only for on
> access scanning, not clamd/clamdscan).
> 
> From what I can tell, your problem is that the fork system command is
> failing (code path for that error requires a negative return for
> fork())--very likely due to lack of resources on the server.
> 
> Ideally, we would fix this resource consumption issue on its own, or
> better, as part of a larger redesign of clamd, but for
> now--like Ged, I would also recommend increasing memory resources and
> seeing if that solves the issue.
> 
> -Mickey

This can be easily solved by changing the fork() to vfork()

--- ./clamd/others.c    2020-02-04 15:59:26.000000000 +0100
+++ ./clamd/others.c.new        2020-02-17 02:25:10.404123000 +0100
@@ -157,9 +157,9 @@
 
     pthread_mutex_lock(&virusaction_lock);
     /* We can only call async-signal-safe functions after fork(). */
-    pid = fork();
+    pid = vfork();
     if (pid == 0) { /* child */
-        exit(execle("/bin/sh", "sh", "-c", buffer_cmd, NULL, env));
+        _exit(execle("/bin/sh", "sh", "-c", buffer_cmd, NULL, env));
     } else if (pid > 0) { /* parent */
         pthread_mutex_unlock(&virusaction_lock);
         while (waitpid(pid, NULL, 0) == -1 && errno == EINTR) continue;




You will surely find text saying how nowadays there is no need for
vfork(), since fork() uses copy-on-write and it has practically no
penalty.
Well, I have found years ago that there *is* a difference (at least on
Linux, which is used by the OP). When the memory usage of the process
goes over a point¹ (in the default overcommit_memory=0) fork() will
start failing, while vfork() still works.

The context around that code means that it is safe to exchange the
fork() with vfork(), as it only modifies a pid_t variable "before
successfully calling _exit(2) or  one  of the exec(3) family of
functions".

Note that I am changing to _exit(2) the original exit(3) call used if
execle fails. Usage of exit(3) seems like a bug even when in the fork()
version, since atexit() handlers would be called at the children
process, which could have undesired effects.


It had been about 10 years since I last tested this, but I have made a
cheap program showing the issue. Hope it doesn't get stripped by the
mailing list.

Compiling with
 gcc -o vfork-test vfork-test.c -O3 
will -depending on the host memory- quickly stop after a few iterations
under the normal overcommit_memory=0, fail much earlier for
non-overcomitting (2), and "never" for always overcommit (1).

If instead of fork() you use vfork():
 gcc -o vfork-test -Dfork=vfork vfork-test.c  -O3

you get the "always forking" behavior also under overcommit_memory=0,
without changing the overcommit behavior systemwide (and a slight
increase under non-overcomitting mode).


Kind regards



Note: use -O0 if you want the program to force the memory to be
allocated.

¹ 
https://unix.stackexchange.com/questions/378172/what-does-heuristics-in-overcommit-memory-0-mean

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif

void allocate(size_t amount) {
	int i;
	char *ptr = malloc(amount);
	if (!ptr) {
		puts("malloc() failed\n");
		exit(1);
	}

	// Note this may be optimized out by the compiler, see -O0 vs -O3
	for (i=0; i < amount; i += PAGE_SIZE) {
		/* Touch the memory */
		ptr[i] = 'x';
	}
}

void tryfork() {
	pid_t p = fork();
	if (p == 0) {
		execl("/bin/echo", "echo", "Ok", NULL);
	} else if (p == -1) {
		printf("fork() failed: %s\n", strerror(errno));
		exit(1);
	} else {
		waitpid(p, NULL, 0);
	}
}

int main() {
	size_t memory = 0;
	size_t increment = 512 * 1024  * 1024;
	setvbuf(stdout, NULL, _IONBF, 0);
	
	while (1) {
		allocate(increment);
		memory += increment;
		
		printf("Using %zd bytes\t", memory);
		tryfork();
	}
}
_______________________________________________

clamav-users mailing list
clamav-users@lists.clamav.net
https://lists.clamav.net/mailman/listinfo/clamav-users


Help us build a comprehensive ClamAV guide:
https://github.com/vrtadmin/clamav-faq

http://www.clamav.net/contact.html#ml

Reply via email to