Skip to contentSkip to conversation

fdset's leakage

PATCH In review Kirill Korotaev 6 participants 8 messages Jul 10, 2006 – Jul 12, 2006 VFS

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 fdset's leakage

Andrew,

Another patch from Alexey Kuznetsov fixing memory leak in alloc_fdtable().

[PATCH] fdset's leakage

When found, it is obvious. nfds calculated when allocating fdsets is rewritten by calculation of size of fdtable, and when we are unlucky, we try to free fdsets of wrong size.

Found due to OpenVZ resource management (User Beancounters).

fs/file.c +3 -1
diff -urp linux-2.6-orig/fs/file.c linux-2.6/fs/file.c
@@ -277,11 +277,13 @@ static struct fdtable *alloc_fdtable(int
 	} while (nfds <= nr);
 	new_fds = alloc_fd_array(nfds);
 	if (!new_fds)
-		goto out;
+		goto out2;
 	fdt->fd = new_fds;
 	fdt->max_fds = nfds;
 	fdt->free_files = NULL;
 	return fdt;
+out2:
+	nfds = fdt->max_fdset;
 out:
   	if (new_openset)
   		free_fdset(new_openset, nfds);
#1 lore raw
Quoted 24 lines · On Mon, 10 Jul 2006 17:40:51 +0400 Kirill Korotaev <[email protected]> wrote:

Andrew,

Another patch from Alexey Kuznetsov fixing memory leak in alloc_fdtable().

[PATCH] fdset's leakage

When found, it is obvious. nfds calculated when allocating fdsets is rewritten by calculation of size of fdtable, and when we are unlucky, we try to free fdsets of wrong size.

Found due to OpenVZ resource management (User Beancounters).

diff -urp linux-2.6-orig/fs/file.c linux-2.6/fs/file.c

fs/file.c +0 -1
@@ -277,11 +277,13 @@ static struct fdtable *alloc_fdtable(int
 	} while (nfds <= nr);
 	new_fds = alloc_fd_array(nfds);
 	if (!new_fds)
-		goto out;

fs/file.c static struct fdtable *alloc_fdtable(int · line 289

Quoted 10 lines
fs/file.c +3 -0
+		goto out2;
 	fdt->fd = new_fds;
 	fdt->max_fds = nfds;
 	fdt->free_files = NULL;
 	return fdt;
+out2:
+	nfds = fdt->max_fdset;
 out:
   	if (new_openset)
   		free_fdset(new_openset, nfds);

OK, that was a simple fix. And if we need this fix backported to 2.6.17.x then it'd be best to go with the simple fix.

And I think we do need to backport this to 2.6.17.x because NR_OPEN can be really big, and vmalloc() is not immortal.

But the code in there is really sick. In all cases we do:

	free_fdset(foo->open_fds, foo->max_fdset);
	free_fdset(foo->close_on_exec, foo->max_fdset);

How much neater and more reliable would it be to do:

	free_fdsets(foo);

?

Also,

	nfds = NR_OPEN_DEFAULT;
	/*
	 * Expand to the max in easy steps, and keep expanding it until
	 * we have enough for the requested fd array size.
	 */
	do {
#if NR_OPEN_DEFAULT < 256
		if (nfds < 256)
			nfds = 256;
		else
#endif
		if (nfds < (PAGE_SIZE / sizeof(struct file *)))
			nfds = PAGE_SIZE / sizeof(struct file *);
		else {
			nfds = nfds * 2;
			if (nfds > NR_OPEN)
				nfds = NR_OPEN;
  		}
	} while (nfds <= nr);

That's going to take a long time to compute if nr > NR_OPEN. I just fixed a similar infinite loop in this function. Methinks this

	nfds = max(NR_OPEN_DEFAULT, 256);
	nfds = max(nfds, PAGE_SIZE/sizeof(struct file *));
	nfds = max(nfds, round_up_pow_of_two(nr + 1));
	nfds = min(nfds, NR_OPEN);

is clearer and less buggy. I _think_ it's also equivalent (as long as NR_OPEN>256). But please check my logic.

#2 lore raw

[strange loop snipped]

Quoted 2 lines

That's going to take a long time to compute if nr > NR_OPEN. I just fixed a similar infinite loop in this function.

That other fix looks buggy btw. Here it is:

- nfds = 8 * L1_CACHE_BYTES;
- /* Expand to the max in easy steps */
- while (nfds <= nr) {
- nfds = nfds * 2;
- if (nfds > NR_OPEN)
- nfds = NR_OPEN;
- }
+ nfds = max_t(int, 8 * L1_CACHE_BYTES, roundup_pow_of_two(nfds));
+ if (nfds > NR_OPEN)
+ nfds = NR_OPEN;

Surely you meant to say "roundup_pow_of_two(nr + 1)"?

René

#3 lore raw
In reply to Andrew Morton

Andrew,

Quoted 42 lines
Quoted 29 lines

Another patch from Alexey Kuznetsov fixing memory leak in alloc_fdtable().

[PATCH] fdset's leakage

When found, it is obvious. nfds calculated when allocating fdsets is rewritten by calculation of size of fdtable, and when we are unlucky, we try to free fdsets of wrong size.

Found due to OpenVZ resource management (User Beancounters).

diff -urp linux-2.6-orig/fs/file.c linux-2.6/fs/file.c

fs/file.c +3 -1
@@ -277,11 +277,13 @@ static struct fdtable *alloc_fdtable(int
 	} while (nfds <= nr);
 	new_fds = alloc_fd_array(nfds);
 	if (!new_fds)
-		goto out;
+		goto out2;
 	fdt->fd = new_fds;
 	fdt->max_fds = nfds;
 	fdt->free_files = NULL;
 	return fdt;
+out2:
+	nfds = fdt->max_fdset;
Quoted 3 lines

out:
if (new_openset)
free_fdset(new_openset, nfds);

OK, that was a simple fix. And if we need this fix backported to 2.6.17.x then it'd be best to go with the simple fix.

And I think we do need to backport this to 2.6.17.x because NR_OPEN can be really big, and vmalloc() is not immortal.

But the code in there is really sick. In all cases we do:

Quoted 8 lines
	free_fdset(foo->open_fds, foo->max_fdset);
	free_fdset(foo->close_on_exec, foo->max_fdset);

How much neater and more reliable would it be to do:

	free_fdsets(foo);

?

agree. should I prepare a patch?

Quoted 26 lines

Also,

	nfds = NR_OPEN_DEFAULT;
	/*
	 * Expand to the max in easy steps, and keep expanding it until
	 * we have enough for the requested fd array size.
	 */
	do {
#if NR_OPEN_DEFAULT < 256
		if (nfds < 256)
			nfds = 256;
		else
#endif
		if (nfds < (PAGE_SIZE / sizeof(struct file *)))
			nfds = PAGE_SIZE / sizeof(struct file *);
		else {
			nfds = nfds * 2;
			if (nfds > NR_OPEN)
				nfds = NR_OPEN;
  		}
	} while (nfds <= nr);

That's going to take a long time to compute if nr > NR_OPEN. I just fixed a similar infinite loop in this function. Methinks this

Quoted 7 lines
	nfds = max(NR_OPEN_DEFAULT, 256);
	nfds = max(nfds, PAGE_SIZE/sizeof(struct file *));
	nfds = max(nfds, round_up_pow_of_two(nr + 1));
	nfds = min(nfds, NR_OPEN);

is clearer and less buggy. I _think_ it's also equivalent (as long as NR_OPEN>256). But please check my logic.

Yeah, I also noticed these nasty loops but was too lazy to bother :) Too much crap for my nerves :)

Your logic looks fine for me. Do we have already round_up_pow_of_two() function or should we create it as something like:
unsinged long round_up_pow_of_two(unsigned long x)
{
unsigned long res = 1 << BITS_PER_LONG;
while (res > x)
res >>= 1;
}
return res << 1;
}

or maybe using:
n = find_first_bit(x);
return res = 1 << n;
(though it depends on endianness IMHO)
?

Thanks,
Kirill

#4 lore raw
Quoted 7 lines · On Tue, 11 Jul 2006 13:05:03 +0400 Kirill Korotaev <[email protected]> wrote:

Andrew,

Quoted 5 lines

But the code in there is really sick. In all cases we do:

	free_fdset(foo->open_fds, foo->max_fdset);
	free_fdset(foo->close_on_exec, foo->max_fdset);
Quoted 6 lines
Quoted 5 lines

How much neater and more reliable would it be to do:

	free_fdsets(foo);

?

agree. should I prepare a patch?

Is OK, I'll take care of it later. We want to let your current patch bake as-is in mainline for a while so that we can backport it into 2.6.17.x with more confidence. That's a bit excessive in this case, but the principle is good.

Quoted 31 lines
Quoted 31 lines

Also,

	nfds = NR_OPEN_DEFAULT;
	/*
	 * Expand to the max in easy steps, and keep expanding it until
	 * we have enough for the requested fd array size.
	 */
	do {
#if NR_OPEN_DEFAULT < 256
		if (nfds < 256)
			nfds = 256;
		else
#endif
		if (nfds < (PAGE_SIZE / sizeof(struct file *)))
			nfds = PAGE_SIZE / sizeof(struct file *);
		else {
			nfds = nfds * 2;
			if (nfds > NR_OPEN)
				nfds = NR_OPEN;
  		}
	} while (nfds <= nr);

That's going to take a long time to compute if nr > NR_OPEN. I just fixed a similar infinite loop in this function. Methinks this

	nfds = max(NR_OPEN_DEFAULT, 256);
	nfds = max(nfds, PAGE_SIZE/sizeof(struct file *));
	nfds = max(nfds, round_up_pow_of_two(nr + 1));
	nfds = min(nfds, NR_OPEN);
Quoted 6 lines
Quoted 2 lines

is clearer and less buggy. I _think_ it's also equivalent (as long as NR_OPEN>256). But please check my logic.

Yeah, I also noticed these nasty loops but was too lazy to bother :) Too much crap for my nerves :)

Your logic looks fine for me.

I usually get that stuff wrong.

Quoted 1 line

Do we have already round_up_pow_of_two() function

yep, in kernel.h.

#5 lore raw
In reply to Kirill Korotaev
Quoted 87 lines · Kirill Korotaev

Andrew,

Quoted 42 lines
Quoted 29 lines

Another patch from Alexey Kuznetsov fixing memory leak in alloc_fdtable().

[PATCH] fdset's leakage

When found, it is obvious. nfds calculated when allocating fdsets is rewritten by calculation of size of fdtable, and when we are unlucky, we try to free fdsets of wrong size.

Found due to OpenVZ resource management (User Beancounters).

diff -urp linux-2.6-orig/fs/file.c linux-2.6/fs/file.c

fs/file.c +3 -1
@@ -277,11 +277,13 @@ static struct fdtable *alloc_fdtable(int
 	} while (nfds <= nr);
 	new_fds = alloc_fd_array(nfds);
 	if (!new_fds)
-		goto out;
+		goto out2;
 	fdt->fd = new_fds;
 	fdt->max_fds = nfds;
 	fdt->free_files = NULL;
 	return fdt;
+out2:
+	nfds = fdt->max_fdset;
Quoted 3 lines

out:
if (new_openset)
free_fdset(new_openset, nfds);

OK, that was a simple fix. And if we need this fix backported to 2.6.17.x then it'd be best to go with the simple fix.

And I think we do need to backport this to 2.6.17.x because NR_OPEN can be really big, and vmalloc() is not immortal.

But the code in there is really sick. In all cases we do:

Quoted 8 lines
	free_fdset(foo->open_fds, foo->max_fdset);
	free_fdset(foo->close_on_exec, foo->max_fdset);

How much neater and more reliable would it be to do:

	free_fdsets(foo);

?

agree. should I prepare a patch?

Quoted 26 lines

Also,

	nfds = NR_OPEN_DEFAULT;
	/*
	 * Expand to the max in easy steps, and keep expanding it until
	 * we have enough for the requested fd array size.
	 */
	do {
#if NR_OPEN_DEFAULT < 256
		if (nfds < 256)
			nfds = 256;
		else
#endif
		if (nfds < (PAGE_SIZE / sizeof(struct file *)))
			nfds = PAGE_SIZE / sizeof(struct file *);
		else {
			nfds = nfds * 2;
			if (nfds > NR_OPEN)
				nfds = NR_OPEN;
  		}
	} while (nfds <= nr);

That's going to take a long time to compute if nr > NR_OPEN. I just fixed a similar infinite loop in this function. Methinks this

Quoted 7 lines
	nfds = max(NR_OPEN_DEFAULT, 256);
	nfds = max(nfds, PAGE_SIZE/sizeof(struct file *));
	nfds = max(nfds, round_up_pow_of_two(nr + 1));
	nfds = min(nfds, NR_OPEN);

is clearer and less buggy. I _think_ it's also equivalent (as long as NR_OPEN>256). But please check my logic.

Quoted 8 lines

Yeah, I also noticed these nasty loops but was too lazy to bother :) Too much crap for my nerves :)

Your logic looks fine for me. Do we have already round_up_pow_of_two() function or should we create it as something like:
unsinged long round_up_pow_of_two(unsigned long x)
{
unsigned long res = 1 << BITS_PER_LONG;

You'll get a zero here. Should be 1 << (BITS_PER_LONG - 1).

Quoted 6 lines

while (res > x)

    res >>= 1;

}
return res << 1;
}

Quoted 8 lines

or maybe using:
n = find_first_bit(x);
return res = 1 << n;
(though it depends on endianness IMHO)
?

Thanks,
Kirill

-- Vadim Lobanov

#6 lore raw
Quoted 6 lines · Vadim Lobanov
Quoted 3 lines

unsinged long round_up_pow_of_two(unsigned long x) {
unsigned long res = 1 << BITS_PER_LONG;

You'll get a zero here. Should be 1 << (BITS_PER_LONG - 1).

Nope. It wont work on 64 bits platform :)

You want 1UL << (BITS_PER_LONG - 1).

But the roundup_pow_of_two() function is already defined in include/linux/kernel.h and uses fls_long()

Eric

#7 lore raw
In reply to Vadim Lobanov
Quoted 8 lines
Quoted 5 lines

Your logic looks fine for me. Do we have already round_up_pow_of_two() function or should we create it as something like:
unsinged long round_up_pow_of_two(unsigned long x)
{
unsigned long res = 1 << BITS_PER_LONG;

You'll get a zero here. Should be 1 << (BITS_PER_LONG - 1).

Good that so many people are watching when you even didn't write it yet :))) Thanks!

Kirill