Skip to contentSkip to conversation

RLIMIT_MEMLOCK accounting of shmctl() SHM_LOCK is broken

PATCH In review Michael Kerrisk 5 participants 13 messages Nov 23, 2004 – Nov 27, 2004

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

PATCH 1/1 RLIMIT_MEMLOCK accounting of shmctl() SHM_LOCK is broken

Linus, Andrew,

The accounting of shmctl() SHM_LOCK memory locks against the user structure is broken. The problem is that the check of the size of the to-be-locked region is based on the size of the segment as specified when it was created by shmget() (this size is *not* rounded up to a page boundary). This size is then rounded down (>> PAGE_SHIFT) to PAGE_SIZE during the check in mm/mlock.c::user_shm_lock().

The consequence is that one can lock an unlimited number of System V shared memory segments as long as they are each less than PAGE_SIZE bytes.

The program below can be used to demonstrate the problem. For example, running it with the following command line:

$ ./shm_lock_many 2000 1000

creates and locks 1000 segments of 2000 bytes each, even while RLIMIT_MEMLOCK is set to the default of 32kiB.

The simple patch at the end of this message is my attempt at a fix (it removes the problem when I test), but maybe there is something better.

Cheers,

Michael

=====================

/* shm_lock_many.c */

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define errExit(msg) { perror(msg); exit(EXIT_FAILURE); }

int
main(int argc, char *argv[])

{
    int shmid, segSize, numSegs, j;

    if (argc < 3 || strcmp(argv[1], "--help") == 0) {
        fprintf(stderr, "Usage: %s segment-size num-segments\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    segSize = atoi(argv[1]);
    numSegs = atoi(argv[2]);

    for (j = 0; j < numSegs; j++) {
        shmid = shmget(IPC_PRIVATE, segSize, S_IRUSR | S_IWUSR);
        if (shmid == -1) errExit("shmget");
        printf("%d: %d\n", j, shmid);
        if (shmctl(shmid, SHM_LOCK, NULL) == -1) errExit("shmctl");
    }

    exit(EXIT_SUCCESS);
} /* main */

=======================

mm/mlock.c +1 -1
@@ -211,7 +211,7 @@
 	int allowed = 0;
 
 	spin_lock(&shmlock_user_lock);
-	locked = size >> PAGE_SHIFT;
+	locked = (size + PAGE_SIZE - 1) >> PAGE_SHIFT;
 	lock_limit = current->signal->rlim[RLIMIT_MEMLOCK].rlim_cur;
 	lock_limit >>= PAGE_SHIFT;
 	if (locked + user->locked_shm > lock_limit && !capable(CAP_IPC_LOCK))
Signature · 2 lines
NEU +++ DSL Komplett von GMX +++ http://www.gmx.net/de/go/dsl
GMX DSL-Netzanschluss + Tarif zum supergünstigen Komplett-Preis!
#1 lore raw
Quoted 9 lines · Michael Kerrisk

The accounting of shmctl() SHM_LOCK memory locks against the
user structure is broken. The problem is that the check of the size of the to-be-locked region is based on the size of the segment as specified when it was created by shmget() (this size is *not* rounded up to a page boundary). This size is then rounded down (>> PAGE_SHIFT) to PAGE_SIZE during the check in mm/mlock.c::user_shm_lock().

True. We should make the same change to user_shm_unlock(), and we may as well tweak the excessive spinlock coverage in there too.

mm/mlock.c renamed from mm/mlock.c~rlimit_memlock-accounting-of-shmctl-shm_lock-is-broken +3 -3
@@ -211,10 +211,10 @@ int user_shm_lock(size_t size, struct us
 	unsigned long lock_limit, locked;
 	int allowed = 0;
 
-	spin_lock(&shmlock_user_lock);
-	locked = size >> PAGE_SHIFT;
+	locked = (size + PAGE_SIZE - 1) >> PAGE_SHIFT;
 	lock_limit = current->signal->rlim[RLIMIT_MEMLOCK].rlim_cur;
 	lock_limit >>= PAGE_SHIFT;
+	spin_lock(&shmlock_user_lock);
 	if (locked + user->locked_shm > lock_limit && !capable(CAP_IPC_LOCK))
 		goto out;
 	get_uid(user);
@@ -228,7 +228,7 @@ out:
 void user_shm_unlock(size_t size, struct user_struct *user)
 {
 	spin_lock(&shmlock_user_lock);
-	user->locked_shm -= (size >> PAGE_SHIFT);
+	user->locked_shm -= (size + PAGE_SIZE - 1) >> PAGE_SHIFT);
 	spin_unlock(&shmlock_user_lock);
 	free_uid(user);
 }

_

and then ask Hugh and Manfred to double-check.

Looking at the callers, we do:

	user_shm_lock(inode->i_size, ...);

then, later:

	user_shm_unlock(inode->i_size, ...);

which does make one wonder "what happens if the file got larger while it was locked"?

#2 lore raw
Quoted 11 lines
Quoted 9 lines · Michael Kerrisk

The accounting of shmctl() SHM_LOCK memory locks against the
user structure is broken. The problem is that the check of the size of the to-be-locked region is based on the size of the segment as specified when it was created by shmget() (this size is *not* rounded up to a page boundary). This size is then rounded down (>> PAGE_SHIFT) to PAGE_SIZE during the check in mm/mlock.c::user_shm_lock().

Quoted 2 lines

True. We should make the same change to user_shm_unlock(), and we may as well tweak the excessive spinlock coverage in there too.

Thanks for the confirmation Andrew.

[...]

Quoted 4 lines

and then ask Hugh and Manfred to double-check.

Looking at the callers, we do:

Quoted 8 lines
	user_shm_lock(inode->i_size, ...);

then, later:

	user_shm_unlock(inode->i_size, ...);

which does make one wonder "what happens if the file got larger while it was locked"?

Not sure if I'm missing your point, but, just considering it from the point of view of System V shared memory, the segment can't change in size after it has been created.

Cheers,

Michael

Signature · 2 lines
Geschenkt: 3 Monate GMX ProMail + 3 Top-Spielfilme auf DVD
++ Jetzt kostenlos testen http://www.gmx.net/de/go/mail ++
#3 lore raw
In reply to Andrew Morton
Quoted 5 lines · Andrew Morton

True. We should make the same change to user_shm_unlock(), and we may as well tweak the excessive spinlock coverage in there too.
...
and then ask Hugh and Manfred to double-check.

Looked good to me.

Examining that code for the first time, I did wonder about a couple of minor irrelevancies with regard to lock_limit - should it too be rounded up? Well, not necessarily, you can argue that the limit should be treated strictly. And it certainly shouldn't be rounded up (wrapping to 0) if it's RLIM_INFINITY. Which raises the question, should we avoid shifting it down if it's RLIM_INFINITY? And should there be a wrapping check on locked + user->locked_shm? Well, locking that much memory will meet its own problems, probably not worth worrying here.

Quoted 10 lines

Looking at the callers, we do:

	user_shm_lock(inode->i_size, ...);

then, later:

	user_shm_unlock(inode->i_size, ...);

which does make one wonder "what happens if the file got larger while it was locked"?

They're only used on objects created by shmem_file_setup, and for those (unlike tmpfs files) there's no interface by which they might change their size after creation; and this isn't the only place which assumes that characteristic. So, it's okay (but not at all obvious).

Hugh

#4 lore raw

Further shmctl() SHM_LOCK strangeness

In reply to Andrew Morton

Manfred, Hugh,

While studying the RLIMIT_MEMLOCK stuff further, I came up with another observation: a process can perform a shmctl(SHM_LOCK) on *any* System V shared memory segment, regardles of the segment's ownership or permissions, providing the size of the segment falls within the process's RLIMIT_MEMLOCK limit.

Is this intended behaviour? For most other System V IPC "ctl" operations the process must either:

1. be the owner of the object or have an appropriate capability, or

2. have suitable permissions on the object.

Which of these two conditions applies depends on the "ctl" operation.

Cheers,

Michael

Signature · 2 lines
NEU +++ DSL Komplett von GMX +++ http://www.gmx.net/de/go/dsl
GMX DSL-Netzanschluss + Tarif zum supergünstigen Komplett-Preis!
#5 lore raw

Re: Further shmctl() SHM_LOCK strangeness

Quoted 7 lines · Michael Kerrisk

While studying the RLIMIT_MEMLOCK stuff further, I came up with another observation: a process can perform a shmctl(SHM_LOCK) on *any* System V shared memory segment, regardles of the segment's ownership or permissions, providing the size of the segment falls within the process's RLIMIT_MEMLOCK limit.

That's a very good observation.

I think it's unintended, but I'm not sure. I've forgotten what can_do_mlock on shm was about.

Offhand I find it hard to grasp whether it's harmless or bad, but inclined to think bad - if there happen to be lots of small enough shared memory segments on the system, a series of processes run by one unprivileged user can lock down lots of memory?

Isn't it further the case that any process can now SHM_UNLOCK any segment? That would surely be wrong.

I've added Rik and Chris to the CC list, they seem to be the main can_do_mlock guys, hope they can answer.

Hugh

Quoted 10 lines

Is this intended behaviour? For most other System V IPC "ctl" operations the process must either:

1. be the owner of the object or have an appropriate capability, or

2. have suitable permissions on the object.

Which of these two conditions applies depends on the "ctl" operation.

#6 lore raw

Re: Further shmctl() SHM_LOCK strangeness

Quoted 8 lines · Hugh Dickins
Quoted 3 lines

regardles of the segment's ownership or permissions, providing the size of the segment falls within the process's RLIMIT_MEMLOCK limit.

Offhand I find it hard to grasp whether it's harmless or bad, but inclined to think bad - if there happen to be lots of small enough shared memory segments on the system, a series of processes run by one unprivileged user can lock down lots of memory?

Mlocking and munlocking of shm segments is accounted against the user_struct, not against the process.

This should stop any malicious exploits.

Signature · 3 lines
"Debugging is twice as hard as writing the code in the first place.
Therefore, if you write the code as cleverly as possible, you are,
by definition, not smart enough to debug it." - Brian W. Kernighan
#7 lore raw

Re: Further shmctl() SHM_LOCK strangeness

Rik,

Quoted 11 lines · Hugh Dickins
Quoted 9 lines
Quoted 4 lines

regardles of the segment's ownership or permissions, providing the size of the segment falls within the process's RLIMIT_MEMLOCK limit.

Offhand I find it hard to grasp whether it's harmless or bad, but inclined to think bad - if there happen to be lots of small enough shared memory segments on the system, a series of processes run by one unprivileged user can lock down lots of memory?

Quoted 4 lines

Mlocking and munlocking of shm segments is accounted against the user_struct, not against the process.

This should stop any malicious exploits.

As noted by Hugh, the problem also applies for
SHM_UNLOCK: anyone can unlock any System V shared memory segment. If our reason for locking memory was security (no swapping), then this does allow for exploits.

(Also, I just want to reemphasise that these semantics are inconsistent with the types of ownership and permission checking performed for just about every other kind of System V "ctl" operation.)

Cheers,

Michael

Signature · 2 lines
Geschenkt: 3 Monate GMX ProMail + 3 Top-Spielfilme auf DVD
++ Jetzt kostenlos testen http://www.gmx.net/de/go/mail ++
#8 lore raw

Re: Further shmctl() SHM_LOCK strangeness

Quoted 5 lines · Michael Kerrisk

As noted by Hugh, the problem also applies for
SHM_UNLOCK: anyone can unlock any System V shared memory segment. If our reason for locking memory was security (no swapping), then this does allow for exploits.

Good point. I guess that for SHM_UNLOCK operations we need to check for either:

1) current->user is the same user who SHM_LOCKed the segment in question

or

2) the process has the correct capabilities set

Signature · 3 lines
"Debugging is twice as hard as writing the code in the first place.
Therefore, if you write the code as cleverly as possible, you are,
by definition, not smart enough to debug it." - Brian W. Kernighan
#9 lore raw

Re: Further shmctl() SHM_LOCK strangeness

Rik,

Quoted 8 lines · Michael Kerrisk
Quoted 6 lines

As noted by Hugh, the problem also applies for
SHM_UNLOCK: anyone can unlock any System V shared memory segment. If our reason for locking memory was security (no swapping), then this does allow for exploits.

Quoted 5 lines

Good point. I guess that for SHM_UNLOCK operations we need to check for either:

1) current->user is the same user who SHM_LOCKed the segment in question

I don't think this is sufficient -- there must
be protection against arbitrary SHM_LOCKs.

Quoted 3 lines

or

2) the process has the correct capabilities set

How about the following:

For *both* SHM_LOCK and SHM_UNLOCK, the process should either be the owner or the creator of the object or have the CAP_IPC_LOCK capability.

Note the following:

1. SHM_LOCK should be covered so that a process with a high RLIMIT_MEMLOCK is allowed to lock arbitrary segments that it doesn't own.

2. A framework like the above is consistent with the semantics of the existing shmctl() IPC_SET and IPC_RMID operations (see the shmctl(2) man page).

Cheers,

Michael

Signature · 2 lines
Geschenkt: 3 Monate GMX ProMail + 3 Top-Spielfilme auf DVD
++ Jetzt kostenlos testen http://www.gmx.net/de/go/mail ++
#10 lore raw

Re: Further shmctl() SHM_LOCK strangeness

Quoted 2 lines · Michael Kerrisk

I don't think this is sufficient -- there must
be protection against arbitrary SHM_LOCKs.

Why? We already have ulimits do that...

Quoted 5 lines

How about the following:

For *both* SHM_LOCK and SHM_UNLOCK, the process should either be the owner or the creator of the object or have the CAP_IPC_LOCK capability.

It makes a lot of sense, but I don't know whether or not it'd break any applications...

Signature · 3 lines
"Debugging is twice as hard as writing the code in the first place.
Therefore, if you write the code as cleverly as possible, you are,
by definition, not smart enough to debug it." - Brian W. Kernighan
#11 lore raw

Re: Further shmctl() SHM_LOCK strangeness

Rik,

Quoted 6 lines · Michael Kerrisk
Quoted 3 lines

I don't think this is sufficient -- there must
be protection against arbitrary SHM_LOCKs.

Why? We already have ulimits do that...

My gut feeling is that processes should not be able to arbitrarily lock shared memory segments created by other users in memory. I mean: why should I be able to someone else's segment into shared memory when I can't even access the contents of that shared memory. (Such semantics are simply inconsistent with the System V IPC model.)

Also (more below), I don't see any other sensible semantics for these operations, other than the ones I've proposed.

Quoted 8 lines
Quoted 5 lines

How about the following:

For *both* SHM_LOCK and SHM_UNLOCK, the process should either be the owner or the creator of the object or have the CAP_IPC_LOCK capability.

It makes a lot of sense, but I don't know whether or not it'd break any applications...

There is no reason why it should. In 2.6.8, the only processes that could lock shared memory segments were those with CAP_IPC_LOCK. Unprivileged processes did not get a look in.

2.6.9 changed things, but it is very unlikely that any applications depend on this (yet). Most userland developers are probably not even aware of the changed semantics in 2.6.9. The time to repair these semantics is *now*, before someone does depend on them. (In any case changes are required, since at a minimum, SHM_UNLOCK must be repaired.)

You earlier suggested the idea that SHM_UNLOCK might check to see if the process's user ID matched that of the process that did the SHM_LOCK. This doesn't work. Suppose someone else locks *my* segment (even though they don't have permission to access its contents or perform other "ctl" operations on it like IPC_RMID). Under your idea, I would not be able to do a SHM_UNLOCK to remove that lock.

Cheers,

Michael

Signature · 2 lines
Geschenkt: 3 Monate GMX ProMail + 3 Top-Spielfilme auf DVD
++ Jetzt kostenlos testen http://www.gmx.net/de/go/mail ++
#12 lore raw

Re: Further shmctl() SHM_LOCK strangeness

In reply to Hugh Dickins

Hugh,

Quoted 10 lines · Michael Kerrisk
Quoted 7 lines

While studying the RLIMIT_MEMLOCK stuff further, I came up with another observation: a process can perform a shmctl(SHM_LOCK) on *any* System V shared memory segment, regardles of the segment's ownership or permissions, providing the size of the segment falls within the process's RLIMIT_MEMLOCK limit.

That's a very good observation.

Thanks.

Quoted 2 lines

I think it's unintended, but I'm not sure. I've forgotten what can_do_mlock on shm was about.

can_do_mlock() returns true if we have CAP_IPC_LOCK or RLIMIT_MEMLOCK != 0.

Quoted 7 lines

Offhand I find it hard to grasp whether it's harmless or bad, but inclined to think bad - if there happen to be lots of small enough shared memory segments on the system, a series of processes run by one unprivileged user can lock down lots of memory?

Isn't it further the case that any process can now SHM_UNLOCK any segment?

I hadn't got as far as thinking about that, but you are correct. Anyone can SHM_UNLOCK a System V shared memory segment on 2.6.9, regardless of ownership and permissions.

Quoted 1 line

That would surely be wrong.

Agreed.

Cheers,

Michael

Quoted 16 lines

I've added Rik and Chris to the CC list, they seem to be the main can_do_mlock guys, hope they can answer.

Hugh

Quoted 10 lines

Is this intended behaviour? For most other System V IPC "ctl" operations the process must either:

1. be the owner of the object or have an appropriate capability, or

2. have suitable permissions on the object.

Which of these two conditions applies depends on the "ctl" operation.

Signature · 2 lines
Geschenkt: 3 Monate GMX ProMail + 3 Top-Spielfilme auf DVD
++ Jetzt kostenlos testen http://www.gmx.net/de/go/mail ++