destroy unused shmem segments
Solar,
I'm looking into the -ow patch and I see this code:
#ifdef CONFIG_HARDEN_SHM
void shm_exit (void)
{
int i;
struct shmid_kernel *shp;
for (i = 0; i <= shm_ids.max_id; i++) {
shp = shm_get(i);
if (!shp) continue;
if (shp->shm_cprid != current->pid) continue;
if (shp->shm_nattch <= 0) {
shp->shm_flags |= SHM_DEST;
shm_destroy (shp);
}
}
}
#endif
NORET_TYPE void do_exit(long code)
{
...
#ifdef CONFIG_HARDEN_SHM
shm_exit();
#endif
...
}However, the shm segment should be already freed by exit_mm() => vma->close():
static struct vm_operations_struct shm_vm_ops = {
open: shm_open, /* callback for a new vm-area open */
close: shm_close, /* callback for when the vm-area is released */
nopage: shmem_nopage,
};
static void shm_close (struct vm_area_struct *shmd)
{
...
shp->shm_nattch--;
#ifdef CONFIG_HARDEN_SHM
if(shp->shm_nattch == 0) {
shp->shm_flags |= SHM_DEST;
shm_destroy (shp);
}
#else
if(shp->shm_nattch == 0 &&
shp->shm_flags & SHM_DEST)
shm_destroy (shp);
#endif
...
}Is it some additional "safety" check or a workaround for some dubious race? I see no explicit need of such freeing cycle in do_exit().
Thanks,
Signature · 1 line
Vasiliy
Vasiliy,
Quoted 2 lines · Vasiliy Kulikov
Is it some additional "safety" check or a workaround for some dubious race?
Neither. IIRC, I thought that this was needed, and not as a workaround, nor because of a race. However, your analysis appears to be correct to me. This leaves us with the following three possibilities:
- The extra code was never needed. This is unlikely because I was adding those pieces of code based on my testing results.
- The extra code was needed for some older kernel version (maybe older than 2.4), then forward-ported (not carefully enough to spot this). (Most CONFIG_HARDEN_* features 2.4.x-ow date back to my patches for 2.0.)
- We're missing something now.
I think that you don't need to figure out which it is. Rather, you need to implement the functionality for 3.0 and test it. Then do it for RHEL6/OpenVZ as well.
Quoted 1 line
I see no explicit need of such freeing cycle in
do_exit().
Yes, it appears so from your analysis.
Thanks,
Alexander
Solar,
Quoted 1 line · Solar Designer
- We're missing something now.
It is used in case shmem segment was created, but never used. In this case refcount is never incremented and never decremented, shm_clone() isn't called.
If shmctl() sets IPC_RMID, it is deleted, but as you have implemented implicit semantics (like IPC_RMID is always set), actual check in shmget()+exit() is needed.
Spotted by reading -ow README and testing :)
I think forcing IPC_RMID should be configurable via sysctl like other ipc variables. Something like /proc/sys/kernel/shm_orphans_denied. And I think changing it from 0 to 1 should destroy already orphaned segments without users.
A patch implementing the feature for 3.0-rc2 is as follows:
Documentation/sysctl/kernel.txt | 22 ++++++++++ include/linux/ipc_namespace.h | 3 + include/linux/shm.h | 5 ++ ipc/ipc_sysctl.c | 33 +++++++++++++++ ipc/shm.c | 87 +++++++++++++++++++++++++++++++++++++-- kernel/exit.c | 1 + 6 files changed, 147 insertions(+), 4 deletions(-)
---
Documentation/sysctl/kernel.txt +22 -0
@@ -62,6 +62,7 @@ show up in /proc/sys/kernel: - shmall - shmmax [ sysv ipc ] - shmmni +- shm_orphan_denied - stop-a [ SPARC only ] - sysrq ==> Documentation/sysrq.txt - tainted
@@ -475,6 +476,27 @@ kernel. This value defaults to SHMMAX. ============================================================== +shm_orphan_denied: + +Linux lets you set resource limits, including on how much memory one +process can consume, via setrlimit(2). Unfortunately, shared memory +segments are allowed to exist without association with any process, and +thus might not be counted against any resource limits. If enabled, +shared memory segments are automatically destroyed when their attach +count becomes zero after a detach or a process termination. It will +also destroy segments that were created, but never attached to, on exit +from the process. The only use left for IPC_RMID is to immediately +destroy an unattached segment. Of course, this breaks the way things are +defined, so some applications might stop working. Note that this +feature will do you no good unless you also configure your resource +limits (in particular, RLIMIT_AS and RLIMIT_NPROC). Most systems don't +need this. + +Note that if you change this from 0 to 1, already created segments +without users and with a dead originative process will be destroyed. + +============================================================== + softlockup_thresh: This value can be used to lower the softlockup tolerance threshold. The
include/linux/ipc_namespace.h +3 -0
@@ -44,6 +44,7 @@ struct ipc_namespace {size_t shm_ctlall;int shm_ctlmni;int shm_tot; +int shm_orphans_denied;struct notifier_block ipcns_nb;
@@ -72,6 +73,7 @@ extern int register_ipcns_notifier(struct ipc_namespace *);extern int cond_register_ipcns_notifier (struct ipc_namespace *);extern void unregister_ipcns_notifier (struct ipc_namespace *);extern int ipcns_notify (unsigned long ); +extern void shm_destroy_orphaned (struct ipc_namespace *ns);#else /* CONFIG_SYSVIPC */ static inline int register_ipcns_notifier (struct ipc_namespace *ns) {return 0 ; }
@@ -79,6 +81,7 @@ static inline int cond_register_ipcns_notifier(struct ipc_namespace *ns) {return 0 ; }static inline void unregister_ipcns_notifier (struct ipc_namespace *ns) { }static inline int ipcns_notify (unsigned long l) {return 0 ; } +static inline void shm_destroy_orphaned (struct ipc_namespace *ns) {}#endif /* CONFIG_SYSVIPC */ #ifdef CONFIG_POSIX_MQUEUE
include/linux/shm.h +5 -0
@@ -106,6 +106,7 @@ struct shmid_kernel /* private to the kernel */#ifdef CONFIG_SYSVIPC long do_shmat (int shmid,char __user *shmaddr,int shmflg,unsigned long *addr);extern int is_file_shm_hugepages (struct file *file); +extern void exit_shm (struct task_struct *task);#else static inline long do_shmat (int shmid,char __user *shmaddr,int shmflg,unsigned long *addr)
@@ -116,6 +117,10 @@ static inline int is_file_shm_hugepages(struct file *file) {return 0 ; } +static inline void exit_shm (struct task_struct *task) +{ +return 0 ; +}#endif #endif /* __KERNEL__ */
ipc/ipc_sysctl.c +33 -0
@@ -37,6 +37,28 @@ static int proc_ipc_dointvec(ctl_table *table, int write,return proc_dointvec (&ipc_table, write, buffer, lenp, ppos); } +static int proc_ipc_dointvec_minmax (ctl_table *table,int write, +void __user *buffer,size_t *lenp,loff_t *ppos) +{ +struct ctl_table ipc_table; +memcpy (&ipc_table, table,sizeof (ipc_table)); + ipc_table.data =get_ipc (table); + +return proc_dointvec_minmax (&ipc_table, write, buffer, lenp, ppos); +} + +static int proc_ipc_dointvec_minmax_orphans (ctl_table *table,int write, +void __user *buffer,size_t *lenp,loff_t *ppos) +{ +struct ipc_namespace *ns = current->nsproxy->ipc_ns; +int err =proc_ipc_dointvec_minmax (table, write, buffer, lenp, ppos); +if (err <0 ) +return err; +if (ns->shm_orphans_denied) +shm_destroy_orphaned (ns); +return err; +} +static int proc_ipc_callback_dointvec (ctl_table *table,int write,void __user *buffer,size_t *lenp,loff_t *ppos) {
@@ -125,6 +147,8 @@ static int proc_ipcauto_dointvec_minmax(ctl_table *table, int write,#else #define proc_ipc_doulongvec_minmax NULL#define proc_ipc_dointvec NULL +#define proc_ipc_dointvec_minmax NULL +#define proc_ipc_dointvec_minmax_orphans NULL#define proc_ipc_callback_dointvec NULL#define proc_ipcauto_dointvec_minmax NULL#endif
@@ -155,6 +179,15 @@ static struct ctl_table ipc_kern_table[] = { .proc_handler = proc_ipc_dointvec, }, { + .procname = "shm_orphans_denied", + .data = &init_ipc_ns.shm_orphans_denied, + .maxlen = sizeof (init_ipc_ns.shm_orphans_denied), + .mode = 0644, + .proc_handler = proc_ipc_dointvec_minmax_orphans, + .extra1 = &zero, + .extra2 = &one, + }, + { .procname = "msgmax", .data = &init_ipc_ns.msg_ctlmax, .maxlen = sizeof (init_ipc_ns.msg_ctlmax),
ipc/shm.c +83 -4
114 lines not on this page · raw message
kernel/exit.c +1 -0
@@ -991,6 +991,7 @@ NORET_TYPE void do_exit(long code)trace_sched_process_exit (tsk);exit_sem (tsk); +exit_shm (tsk);exit_files (tsk);exit_fs (tsk);check_stack_usage ();
---
Vasiliy,
Quoted 9 lines · Vasiliy Kulikov
It is used in case shmem segment was created, but never used. In this case refcount is never incremented and never decremented,
shm_clone()isn't called.If
shmctl()setsIPC_RMID, it is deleted, but as you have implemented implicit semantics (likeIPC_RMIDis always set), actual check in shmget()+exit() is needed.Spotted by reading -ow README and testing :)
Oh, right. Having documentation is good.
Quoted 4 lines
I think forcing
IPC_RMIDshould be configurable via sysctl like other ipc variables. Something like/proc/sys/kernel/shm_orphans_denied. And I think changing it from 0 to 1 should destroy already orphaned segments without users.
OK.
Thanks,
Alexander