Skip to contentSkip to conversation

setrlimit and RLIM_INFINITY causing fsck failure, 2.4.18

DISCUSSION Peter Hartley 5 participants 10 messages Mar 19, 2002 – Mar 20, 2002

Subsystems (5)
lore
Share & tools
lore
#0 lore raw

When setrlimit/getrlimit were changed to take unsigned rather than signed limits, a new syscall was invented for getrlimit that returned the signed values that old programs expected. But no new syscall was invented for setrlimit; both old and new programs come to the same function (in kernel/sys.c). This was, according to the glibc source, at about 2.3.25 time.

This breaks any old program which tries to set a limit of RLIM_INFINITY. Such a program will end up passing a 0x7FFFFFFF to sys_setrlimit, which will be dutifully placed in the current->rlim array. Once there it will completely fail to compare equal to the kernel's RLIM_INFINITY value, 0xFFFFFFFF.

"Old" in this context means any program linked against a glibc that was built against 2.2 headers.

In particular, this means that an e2fsck 1.27 built against such a glibc will fail with SIGXFS every time on any block device bigger than 2Gbytes. This is because:

* e2fsck calls setrlimit(RLIMIT_FSIZE, RLIM_INFINITY) in an attempt to unset the limit. RLIM_INFINITY here is 0xFFFFFFFF. This is IMO the Right Thing.
* glibc knows nothing about the new unsigned limits, because it's compiled against 2.2 headers. So it clips the limit value to 0x7FFFFFFF to "correct" it before calling the setrlimit syscall. This is IMO still the Right Thing, because it's trying to call the old syscall as if to run a new program on a 2.2 kernel.
* The kernel writes the 0x7FFFFFFF value uninterpreted into the current->rlim array. This is IMO the bug.

Surely the only Right Things to do in the kernel are (a) invent a new setrlimit call that corrects the RLIM_INFINITY value, or (b) have the current setrlimit call correct the RLIM_INFINITY value unconditionally.

Answer (b) breaks programs that deliberately set a limit of 0x7FFFFFFF, but it's less intrusive than answer (a). The patch for (b) is fairly trivial and I'll rustle one up if people agree it's the Right Thing.

Complete test situation that reproduced the bug: 2.2.20 system (with glibc-2.2.5, and 2.2.20 headers in /usr/include) compiling a glibc-2.2.5 and e2fsprogs-1.27 which are then run on a 2.4.18 system. (As e2fsck is statically linked, it gets the compile host's glibc, of course.) Root fs is a 4.5Gbyte ext2 on a 5Gbyte Quantum IDE.

So for my purposes I can fix this by upgrading my compile host to a glibc that's built against 2.4.18 headers, but I still reckon there's a kernel bug here.

Peter

PS. Please CC me on replies, as I only read the list on an archive site. Ta.

#1 lore raw
Quoted 3 lines

Answer (b) breaks programs that deliberately set a limit of 0x7FFFFFFF, but it's less intrusive than answer (a). The patch for (b) is fairly trivial and I'll rustle one up if people agree it's the Right Thing.

(b) is a standards violation

Quoted 3 lines

So for my purposes I can fix this by upgrading my compile host to a glibc that's built against 2.4.18 headers, but I still reckon there's a kernel bug here.

Its a compatibility bug. The original submission when it was changed did try to address this. Originally its a kernel bug because the standards always said that it was unsigned

#2 lore raw
In reply to Peter Hartley
Quoted 7 lines · Peter Hartley

In particular, this means that an e2fsck 1.27 built against such a glibc will fail with SIGXFS every time on any block device bigger than 2Gbytes. This is because:

* e2fsck calls setrlimit(RLIMIT_FSIZE, RLIM_INFINITY) in an attempt to unset the limit. RLIM_INFINITY here is 0xFFFFFFFF. This is IMO the Right Thing.

It is only the right thing if the original limit was not 0xFFFFFFFF. Otherwise, it is just adding to the problem, because the problem only happens when you try to SET the limit.

Quoted 3 lines

Surely the only Right Things to do in the kernel are (a) invent a new setrlimit call that corrects the RLIM_INFINITY value, or (b) have the current setrlimit call correct the RLIM_INFINITY value unconditionally.

(c) rlimit should not apply to block devices.

There were patches for this floating around, and I thought they made it into 2.4.18, but they did not.

I do agree that we should also fix "setrlimit" to do the 0x7FFFFFFF to 0xFFFFFFFF correction.

Cheers, Andreas
--
Andreas Dilger \ "If a man ate a pound of pasta and a pound of antipasto,
\ would they cancel out, leaving him still hungry?"
http://www-mddsp.enel.ucalgary.ca/People/adilger/ -- Dogbert

#3 lore raw
Quoted 1 line

(c) rlimit should not apply to block devices.

Also correct.

Quoted 2 lines

There were patches for this floating around, and I thought they made it into 2.4.18, but they did not.

I saw some stuff. I may even have looked at merging it with -ac. Not sure if it got fixed finally there or not. A resend of those diffs would be good.

#4 lore raw
In reply to Peter Hartley
Quoted 3 lines · Andreas Dilger
Quoted 2 lines · Peter Hartley

In particular, this means that an e2fsck 1.27 built against such a glibc will fail with SIGXFS every time on any block device bigger than

2Gbytes.

Quoted 9 lines
Quoted 5 lines

This is because:

* e2fsck calls setrlimit(RLIMIT_FSIZE, RLIM_INFINITY) in an attempt to unset the limit. RLIM_INFINITY here is 0xFFFFFFFF. This is IMO the Right Thing.

It is only the right thing if the original limit was not 0xFFFFFFFF. Otherwise, it is just adding to the problem, because the problem only happens when you try to SET the limit.

True. (Old programs *will* perceive the value as 0xFFFFFFFF if it is RLIM_INFINITY; the kernel clips it to 0x7FFFFFFF in sys_old_getrlimit() but glibc expands it again in __new_getrlimit().)

Quoted 8 lines
Quoted 3 lines

Surely the only Right Things to do in the kernel are (a) invent a new setrlimit call that corrects the RLIM_INFINITY value, or (b) have the current setrlimit call correct the RLIM_INFINITY value unconditionally.

(c) rlimit should not apply to block devices.

There were patches for this floating around, and I thought they made it into 2.4.18, but they did not.

Looking a bit closer at the particular SIGXFS that kills fsck (in generic_file_write) there's some S_ISBLK stuff going on just below.

Is the fix just as simple as: (untested) (and with a mailer than mangles tabs)

filemap.c Tue Mar 19 18:20:40 2002 renamed from filemap.c~ Mon Feb 25 19:38:13 2002 +1 -1
@@ -2885,9 +2885,9 @@
   * Check whether we've reached the file size limit.
   */
  err = -EFBIG;
 
- if (limit != RLIM_INFINITY) {
+ if (limit != RLIM_INFINITY && !S_ISBLK(inode->i_mode)) {
   if (pos >= limit) {
    send_sig(SIGXFSZ, current, 0);
    goto out;
   }

All this rlimit stuff is a bit wonky in the presence of 64-bit file sizes anyway. Perhaps if we fix just the block-device case we can brush the rest under the carpet?

Peter

#5 lore raw
Quoted 2 lines

Is the fix just as simple as: (untested) (and with a mailer than mangles tabs)

Test it and see 8)

Quoted 3 lines

All this rlimit stuff is a bit wonky in the presence of 64-bit file sizes anyway. Perhaps if we fix just the block-device case we can brush the rest under the carpet?

For 64bit platforms life is ok, for the x86 rlimit64 might make sense. Im not sure if it ever appeared in any standards 8(

#6 lore raw
Quoted 1 line · AC

Test it and see 8)

OK, did do. Patch attached. The occurrence in filemap.c was the one killing fsck, but I went looking for other occurrences of SIGXFS and made sure they did the right thing too.

This doesn't address any other wonkiness of rlimit, it just stops it applying to block devices.

Peter

fs/buffer.c +1 -1
diff -u3 -r linux/fs/buffer.c linux.pdh/fs/buffer.c
@@ -1834,7 +1834,7 @@
 
 	err = -EFBIG;
         limit = current->rlim[RLIMIT_FSIZE].rlim_cur;
-	if (limit != RLIM_INFINITY && size > (loff_t)limit) {
+	if (limit != RLIM_INFINITY && size > (loff_t)limit && !S_ISBLK(inode->i_mode)) {
 		send_sig(SIGXFSZ, current, 0);
 		goto out;
 	}
mm/filemap.c +2 -2
diff -u3 -r linux/mm/filemap.c linux.pdh/mm/filemap.c
@@ -2885,8 +2885,8 @@
 	 * Check whether we've reached the file size limit.
 	 */
 	err = -EFBIG;
-	
-	if (limit != RLIM_INFINITY) {
+
+	if (limit != RLIM_INFINITY && !S_ISBLK(inode->i_mode)) {
 		if (pos >= limit) {
 			send_sig(SIGXFSZ, current, 0);
 			goto out;
mm/memory.c +1 -1
diff -u3 -r linux/mm/memory.c linux.pdh/mm/memory.c
@@ -1059,7 +1059,7 @@
 
 do_expand:
 	limit = current->rlim[RLIMIT_FSIZE].rlim_cur;
-	if (limit != RLIM_INFINITY && offset > limit)
+	if (limit != RLIM_INFINITY && offset > limit && !S_ISBLK(inode->i_mode))
 		goto out_sig;
 	if (offset > inode->i_sb->s_maxbytes)
 		goto out;
mm/shmem.c +1 -1
diff -u3 -r linux/mm/shmem.c linux.pdh/mm/shmem.c
@@ -780,7 +780,7 @@
 	 * Check whether we've reached the file size limit.
 	 */
 	err = -EFBIG;
-	if (limit != RLIM_INFINITY) {
+	if (limit != RLIM_INFINITY && !S_ISBLK(inode->i_mode)) {
 		if (pos >= limit) {
 			send_sig(SIGXFSZ, current, 0);
 			goto out;
#7 lore raw
In reply to Peter Hartley
Quoted 4 lines · Peter Hartley

But no new syscall was invented for setrlimit; both old and new programs come to the same function (in kernel/sys.c). This was, according to the glibc source, at about 2.3.25 time.

Yes. This resulted in a very nasty ABI change, since in effect RLIM_INFINITY changed on us. It's rather embarassing, actually, since this is the sort of thing that I generally complain about happening with various user-land libraries (such as glibc, ncurses, libgal, etc.) and I used to say that kernel programmers were generally a lot more careful about such things. Well, I can't say that any more....

Quoted 4 lines

* e2fsck calls setrlimit(RLIMIT_FSIZE, RLIM_INFINITY) in an attempt to unset the limit. RLIM_INFINITY here is 0xFFFFFFFF. This is IMO the Right Thing.

I did this because I was tired of bug reports from users who were losing due to other programs that were attempting to set the limit of RLIMIT_FSIZE, and screwing up because they were compiled with 2.2 headers. (Or rather, were compiled with a glibc which was compiled against 2.2 headers.)n

Quoted 6 lines

* glibc knows nothing about the new unsigned limits, because it's compiled against 2.2 headers. So it clips the limit value to 0x7FFFFFFF to "correct" it before calling the setrlimit syscall. This is IMO still the Right Thing, because it's trying to call the old syscall as if to run a new program on a 2.2 kernel.

Unfortunately, all of my testing was done under systems where the glibc was already compiled under 2.4 headers, so I didn't realize that glibc would try to be "helpful" and correct the limit used by rlimit. (In other words, the e2fsprogs workaround was only worked in the case where other programs were losing because they were using the 2.2 kernel ABI, but the libc was using the 2.4 kernel ABI. Sigh.)

So obviously, the way I need to fix e2fsprogs is to fork a child process, check to see whether or not I can safely call setrlimit, and if not, exit without trying to set it. :-( This is a really dirty hack, but I don't see any other way fixing user-land programs that are trying to work around this ABI mess. (And since distributions are already shipping 2.4 kernels, this is a mess that userspace programs are going to have to deal with for a non-trivial amount of time.)

Quoted 7 lines

Surely the only Right Things to do in the kernel are (a) invent a new setrlimit call that corrects the RLIM_INFINITY value, or (b) have the current setrlimit call correct the RLIM_INFINITY value unconditionally.

Answer (b) breaks programs that deliberately set a limit of 0x7FFFFFFF, but it's less intrusive than answer (a). The patch for (b) is fairly trivial and I'll rustle one up if people agree it's the Right Thing.

I think we should do (b) as soon as possible, but it'll still be a program since most distributions are either shipping CD-ROM's with 2.4 kernels on them already (sigh).

The other fix is that the filesize limit *really* shouldn't apply to block devices. We should probably do both fixes.

- Ted

#8 lore raw
In reply to Peter Hartley
Quoted 2 lines · Theodore Tso

The other fix is that the filesize limit *really* shouldn't apply to block devices. We should probably do both fixes.

The filesize limit did not apply to block devices until ~2.4.10, so this manifestation of the bug is a recent introduction.

The whole rlimit mess itself is a might bit awful since it and the nonstandard 2.2 file size extensions resulted in a horribly conflicting glob of problems. *sigh*

-ben

Signature · 3 lines
"A man with a bass just walked in,
 and he's putting it down
 on the floor."
#9 lore raw
In reply to Peter Hartley
Quoted 15 lines · Theodore Tso
Quoted 6 lines · Peter Hartley

* glibc knows nothing about the new unsigned limits, because it's compiled against 2.2 headers. So it clips the limit value to 0x7FFFFFFF to "correct" it before calling the setrlimit syscall. This is IMO still the Right Thing, because it's trying to call the old syscall as if to run a new program on a 2.2 kernel.

Unfortunately, all of my testing was done under systems where the glibc was already compiled under 2.4 headers, so I didn't realize that glibc would try to be "helpful" and correct the limit used by rlimit. (In other words, the e2fsprogs workaround was only worked in the case where other programs were losing because they were using the 2.2 kernel ABI, but the libc was using the 2.4 kernel ABI. Sigh.)

Quoted 5 lines

So obviously, the way I need to fix e2fsprogs is to fork a child process, check to see whether or not I can safely call setrlimit, and if not, exit without trying to set it. :-( This is a really dirty hack, but I don't see any other way fixing user-land programs that are trying to work around this ABI mess.

I don't think you can tell whether it's safe to call setrlimit (unless you mean having e2fsck call the *syscall* directly, which is icky). The getrlimit in a 2.2-headered glibc returns 0x7FFFFFFF whether the kernel's idea of the value is 0x7FFFFFFF or 0xFFFFFFFF. I still like Andreas Dilger's idea that you only set the limit if it's not already RLIM_INFINITY as returned by glibc.

If PAM, or something else, has already set the 0x7FFFFFFF value, there appears to be no way of setting the 0xFFFFFFFF value via a 2.2-headered glibc :-( and you'd need to go for the syscall :-((

Peter