* [PATCH v9 1/4] ipc: IPCMNI limit check for msgmni and shmmni
2018-09-07 20:28 [PATCH v9 0/4] ipc: IPCMNI limit check for *mni & increase that limit Waiman Long
@ 2018-09-07 20:28 ` Waiman Long
2018-09-07 20:28 ` [PATCH v9 2/4] ipc: IPCMNI limit check for semmni Waiman Long
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Waiman Long @ 2018-09-07 20:28 UTC (permalink / raw)
To: Luis R. Rodriguez, Kees Cook, Andrew Morton, Jonathan Corbet
Cc: linux-kernel, linux-fsdevel, linux-doc, Al Viro, Matthew Wilcox,
Eric W. Biederman, Takashi Iwai, Davidlohr Bueso, Waiman Long
A user can write arbitrary integer values to msgmni and shmmni sysctl
parameters without getting error, but the actual limit is really
IPCMNI (32k). This can mislead users as they think they can get a
value that is not real.
The right limits are now set for msgmni and shmmni so that the users
will become aware if they set a value outside of the acceptable range.
Signed-off-by: Waiman Long <longman@redhat.com>
Acked-by: Luis R. Rodriguez <mcgrof@kernel.org>
Reviewed-by: Davidlohr Bueso <dave@stgolabs.net>
---
ipc/ipc_sysctl.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/ipc/ipc_sysctl.c b/ipc/ipc_sysctl.c
index 8ad93c2..f87cb29 100644
--- a/ipc/ipc_sysctl.c
+++ b/ipc/ipc_sysctl.c
@@ -99,6 +99,7 @@ static int proc_ipc_auto_msgmni(struct ctl_table *table, int write,
static int zero;
static int one = 1;
static int int_max = INT_MAX;
+static int ipc_mni = IPCMNI;
static struct ctl_table ipc_kern_table[] = {
{
@@ -120,7 +121,9 @@ static int proc_ipc_auto_msgmni(struct ctl_table *table, int write,
.data = &init_ipc_ns.shm_ctlmni,
.maxlen = sizeof(init_ipc_ns.shm_ctlmni),
.mode = 0644,
- .proc_handler = proc_ipc_dointvec,
+ .proc_handler = proc_ipc_dointvec_minmax,
+ .extra1 = &zero,
+ .extra2 = &ipc_mni,
},
{
.procname = "shm_rmid_forced",
@@ -147,7 +150,7 @@ static int proc_ipc_auto_msgmni(struct ctl_table *table, int write,
.mode = 0644,
.proc_handler = proc_ipc_dointvec_minmax,
.extra1 = &zero,
- .extra2 = &int_max,
+ .extra2 = &ipc_mni,
},
{
.procname = "auto_msgmni",
--
1.8.3.1
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v9 2/4] ipc: IPCMNI limit check for semmni
2018-09-07 20:28 [PATCH v9 0/4] ipc: IPCMNI limit check for *mni & increase that limit Waiman Long
2018-09-07 20:28 ` [PATCH v9 1/4] ipc: IPCMNI limit check for msgmni and shmmni Waiman Long
@ 2018-09-07 20:28 ` Waiman Long
2018-09-07 20:28 ` [PATCH v9 3/4] ipc: Allow boot time extension of IPCMNI from 32k to 8M Waiman Long
2018-09-07 20:28 ` [PATCH v9 4/4] ipc: Conserve sequence numbers in extended IPCMNI mode Waiman Long
3 siblings, 0 replies; 5+ messages in thread
From: Waiman Long @ 2018-09-07 20:28 UTC (permalink / raw)
To: Luis R. Rodriguez, Kees Cook, Andrew Morton, Jonathan Corbet
Cc: linux-kernel, linux-fsdevel, linux-doc, Al Viro, Matthew Wilcox,
Eric W. Biederman, Takashi Iwai, Davidlohr Bueso, Waiman Long
For SysV semaphores, the semmni value is the last part of the 4-element
sem number array. To make semmni behave in a similar way to msgmni and
shmmni, we can't directly use the _minmax handler. Instead, a special
sem specific handler is added to check the last argument to make sure
that it is limited to the [0, IPCMNI] range. An error will be returned
if this is not the case.
Signed-off-by: Waiman Long <longman@redhat.com>
Reviewed-by: Davidlohr Bueso <dave@stgolabs.net>
---
ipc/ipc_sysctl.c | 23 ++++++++++++++++++++++-
ipc/util.h | 9 +++++++++
2 files changed, 31 insertions(+), 1 deletion(-)
diff --git a/ipc/ipc_sysctl.c b/ipc/ipc_sysctl.c
index f87cb29..49f9bf4 100644
--- a/ipc/ipc_sysctl.c
+++ b/ipc/ipc_sysctl.c
@@ -88,12 +88,33 @@ static int proc_ipc_auto_msgmni(struct ctl_table *table, int write,
return proc_dointvec_minmax(&ipc_table, write, buffer, lenp, ppos);
}
+static int proc_ipc_sem_dointvec(struct ctl_table *table, int write,
+ void __user *buffer, size_t *lenp, loff_t *ppos)
+{
+ int ret, semmni;
+ struct ipc_namespace *ns = current->nsproxy->ipc_ns;
+
+ semmni = ns->sem_ctls[3];
+ ret = proc_ipc_dointvec(table, write, buffer, lenp, ppos);
+
+ if (!ret)
+ ret = sem_check_semmni(current->nsproxy->ipc_ns);
+
+ /*
+ * Reset the semmni value if an error happens.
+ */
+ if (ret)
+ ns->sem_ctls[3] = semmni;
+ return ret;
+}
+
#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_auto_msgmni NULL
+#define proc_ipc_sem_dointvec NULL
#endif
static int zero;
@@ -175,7 +196,7 @@ static int proc_ipc_auto_msgmni(struct ctl_table *table, int write,
.data = &init_ipc_ns.sem_ctls,
.maxlen = 4*sizeof(int),
.mode = 0644,
- .proc_handler = proc_ipc_dointvec,
+ .proc_handler = proc_ipc_sem_dointvec,
},
#ifdef CONFIG_CHECKPOINT_RESTORE
{
diff --git a/ipc/util.h b/ipc/util.h
index 0a159f6..65108c1 100644
--- a/ipc/util.h
+++ b/ipc/util.h
@@ -217,6 +217,15 @@ int ipcget(struct ipc_namespace *ns, struct ipc_ids *ids,
void free_ipcs(struct ipc_namespace *ns, struct ipc_ids *ids,
void (*free)(struct ipc_namespace *, struct kern_ipc_perm *));
+static inline int sem_check_semmni(struct ipc_namespace *ns) {
+ /*
+ * Check semmni range [0, IPCMNI]
+ * semmni is the last element of sem_ctls[4] array
+ */
+ return ((ns->sem_ctls[3] < 0) || (ns->sem_ctls[3] > IPCMNI))
+ ? -ERANGE : 0;
+}
+
#ifdef CONFIG_COMPAT
#include <linux/compat.h>
struct compat_ipc_perm {
--
1.8.3.1
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v9 3/4] ipc: Allow boot time extension of IPCMNI from 32k to 8M
2018-09-07 20:28 [PATCH v9 0/4] ipc: IPCMNI limit check for *mni & increase that limit Waiman Long
2018-09-07 20:28 ` [PATCH v9 1/4] ipc: IPCMNI limit check for msgmni and shmmni Waiman Long
2018-09-07 20:28 ` [PATCH v9 2/4] ipc: IPCMNI limit check for semmni Waiman Long
@ 2018-09-07 20:28 ` Waiman Long
2018-09-07 20:28 ` [PATCH v9 4/4] ipc: Conserve sequence numbers in extended IPCMNI mode Waiman Long
3 siblings, 0 replies; 5+ messages in thread
From: Waiman Long @ 2018-09-07 20:28 UTC (permalink / raw)
To: Luis R. Rodriguez, Kees Cook, Andrew Morton, Jonathan Corbet
Cc: linux-kernel, linux-fsdevel, linux-doc, Al Viro, Matthew Wilcox,
Eric W. Biederman, Takashi Iwai, Davidlohr Bueso, Waiman Long
The maximum number of unique System V IPC identifiers was limited to
32k. That limit should be big enough for most use cases.
However, there are some users out there requesting for more, especially
those that are migrating from Solaris which uses 24 bits for unique
identifiers. To satisfy the need of those users, a new boot time kernel
option "ipcmni_extend" is added to extend the IPCMNI value to 8M. This
is a 256X increase which hopefully is big enough for them.
The use of this new option will change the pattern of the IPC identifiers
returned by functions like shmget(2). An application that depends on
such pattern may not work properly. So it should only be used if the
users really need more than 32k of unique IPC numbers.
This new option does have the side effect of reducing the maximum number
of unique sequence numbers from 64k down to 256. So it is a trade-off.
The computation of a new IPC id is not done in the performance critical
path. So a little bit of additional overhead shouldn't have any real
performance impact.
Signed-off-by: Waiman Long <longman@redhat.com>
---
Documentation/admin-guide/kernel-parameters.txt | 3 ++
ipc/ipc_sysctl.c | 12 ++++++-
ipc/util.c | 10 +++---
ipc/util.h | 44 ++++++++++++++++++++-----
4 files changed, 54 insertions(+), 15 deletions(-)
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 9871e64..9bd184d 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -1773,6 +1773,9 @@
ip= [IP_PNP]
See Documentation/filesystems/nfs/nfsroot.txt.
+ ipcmni_extend [KNL] Extend the maximum number of unique System V
+ IPC identifiers from 32,768 to 8,388,608.
+
irqaffinity= [SMP] Set the default irq affinity mask
The argument is a cpu list, as described above.
diff --git a/ipc/ipc_sysctl.c b/ipc/ipc_sysctl.c
index 49f9bf4..73b7782 100644
--- a/ipc/ipc_sysctl.c
+++ b/ipc/ipc_sysctl.c
@@ -120,7 +120,8 @@ static int proc_ipc_sem_dointvec(struct ctl_table *table, int write,
static int zero;
static int one = 1;
static int int_max = INT_MAX;
-static int ipc_mni = IPCMNI;
+int ipc_mni = IPCMNI;
+int ipc_mni_shift = IPCMNI_SHIFT;
static struct ctl_table ipc_kern_table[] = {
{
@@ -246,3 +247,12 @@ static int __init ipc_sysctl_init(void)
}
device_initcall(ipc_sysctl_init);
+
+static int __init ipc_mni_extend(char *str)
+{
+ ipc_mni = IPCMNI_EXTEND;
+ ipc_mni_shift = IPCMNI_EXTEND_SHIFT;
+ pr_info("IPCMNI extended to %d.\n", ipc_mni);
+ return 0;
+}
+early_param("ipcmni_extend", ipc_mni_extend);
diff --git a/ipc/util.c b/ipc/util.c
index 0af0575..07ae117 100644
--- a/ipc/util.c
+++ b/ipc/util.c
@@ -110,7 +110,7 @@ static int __init ipc_init(void)
* @ids: ipc identifier set
*
* Set up the sequence range to use for the ipc identifier range (limited
- * below IPCMNI) then initialise the keys hashtable and ids idr.
+ * below ipc_mni) then initialise the keys hashtable and ids idr.
*/
void ipc_init_ids(struct ipc_ids *ids)
{
@@ -226,7 +226,7 @@ static inline int ipc_idr_alloc(struct ipc_ids *ids, struct kern_ipc_perm *new)
0, GFP_NOWAIT);
}
if (idx >= 0)
- new->id = SEQ_MULTIPLIER * new->seq + idx;
+ new->id = (new->seq << IPCMNI_SEQ_SHIFT) + idx;
return idx;
}
@@ -254,8 +254,8 @@ int ipc_addid(struct ipc_ids *ids, struct kern_ipc_perm *new, int limit)
/* 1) Initialize the refcount so that ipc_rcu_putref works */
refcount_set(&new->refcount, 1);
- if (limit > IPCMNI)
- limit = IPCMNI;
+ if (limit > ipc_mni)
+ limit = ipc_mni;
if (ids->in_use >= limit)
return -ENOSPC;
@@ -738,7 +738,7 @@ static struct kern_ipc_perm *sysvipc_find_ipc(struct ipc_ids *ids, loff_t pos,
if (total >= ids->in_use)
return NULL;
- for (; pos < IPCMNI; pos++) {
+ for (; pos < ipc_mni; pos++) {
ipc = idr_find(&ids->ipcs_idr, pos);
if (ipc != NULL) {
*new_pos = pos + 1;
diff --git a/ipc/util.h b/ipc/util.h
index 65108c1..f11a25a 100644
--- a/ipc/util.h
+++ b/ipc/util.h
@@ -15,8 +15,34 @@
#include <linux/err.h>
#include <linux/ipc_namespace.h>
-#define IPCMNI 32768 /* <= MAX_INT limit for ipc arrays (including sysctl changes) */
-#define SEQ_MULTIPLIER (IPCMNI)
+/*
+ * The IPC ID contains 2 separate numbers - index and sequence number.
+ * By default,
+ * bits 0-14: index (32k, 15 bits)
+ * bits 15-30: sequence number (64k, 16 bits)
+ *
+ * When IPCMNI extension mode is turned on, the composition changes:
+ * bits 0-22: index (8M, 23 bits)
+ * bits 23-30: sequence number (256, 8 bits)
+ */
+#define IPCMNI_SHIFT 15
+#define IPCMNI_EXTEND_SHIFT 23
+#define IPCMNI (1 << IPCMNI_SHIFT)
+#define IPCMNI_EXTEND (1 << IPCMNI_EXTEND_SHIFT)
+
+#ifdef CONFIG_SYSVIPC_SYSCTL
+extern int ipc_mni;
+extern int ipc_mni_shift;
+
+#define IPCMNI_SEQ_SHIFT ipc_mni_shift
+#define IPCMNI_IDX_MASK ((1 << ipc_mni_shift) - 1)
+
+#else /* CONFIG_SYSVIPC_SYSCTL */
+
+#define ipc_mni IPCMNI
+#define IPCMNI_SEQ_SHIFT IPCMNI_SHIFT
+#define IPCMNI_IDX_MASK ((1 << IPCMNI_SHIFT) - 1)
+#endif /* CONFIG_SYSVIPC_SYSCTL */
void sem_init(void);
void msg_init(void);
@@ -96,9 +122,9 @@ void __init ipc_init_proc_interface(const char *path, const char *header,
#define IPC_MSG_IDS 1
#define IPC_SHM_IDS 2
-#define ipcid_to_idx(id) ((id) % SEQ_MULTIPLIER)
-#define ipcid_to_seqx(id) ((id) / SEQ_MULTIPLIER)
-#define IPCID_SEQ_MAX min_t(int, INT_MAX/SEQ_MULTIPLIER, USHRT_MAX)
+#define ipcid_to_idx(id) ((id) & IPCMNI_IDX_MASK)
+#define ipcid_to_seqx(id) ((id) >> IPCMNI_SEQ_SHIFT)
+#define IPCID_SEQ_MAX (INT_MAX >> IPCMNI_SEQ_SHIFT)
/* must be called with ids->rwsem acquired for writing */
int ipc_addid(struct ipc_ids *, struct kern_ipc_perm *, int);
@@ -123,8 +149,8 @@ static inline int ipc_get_maxidx(struct ipc_ids *ids)
if (ids->in_use == 0)
return -1;
- if (ids->in_use == IPCMNI)
- return IPCMNI - 1;
+ if (ids->in_use == ipc_mni)
+ return ipc_mni - 1;
return ids->max_idx;
}
@@ -219,10 +245,10 @@ void free_ipcs(struct ipc_namespace *ns, struct ipc_ids *ids,
static inline int sem_check_semmni(struct ipc_namespace *ns) {
/*
- * Check semmni range [0, IPCMNI]
+ * Check semmni range [0, ipc_mni]
* semmni is the last element of sem_ctls[4] array
*/
- return ((ns->sem_ctls[3] < 0) || (ns->sem_ctls[3] > IPCMNI))
+ return ((ns->sem_ctls[3] < 0) || (ns->sem_ctls[3] > ipc_mni))
? -ERANGE : 0;
}
--
1.8.3.1
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v9 4/4] ipc: Conserve sequence numbers in extended IPCMNI mode
2018-09-07 20:28 [PATCH v9 0/4] ipc: IPCMNI limit check for *mni & increase that limit Waiman Long
` (2 preceding siblings ...)
2018-09-07 20:28 ` [PATCH v9 3/4] ipc: Allow boot time extension of IPCMNI from 32k to 8M Waiman Long
@ 2018-09-07 20:28 ` Waiman Long
3 siblings, 0 replies; 5+ messages in thread
From: Waiman Long @ 2018-09-07 20:28 UTC (permalink / raw)
To: Luis R. Rodriguez, Kees Cook, Andrew Morton, Jonathan Corbet
Cc: linux-kernel, linux-fsdevel, linux-doc, Al Viro, Matthew Wilcox,
Eric W. Biederman, Takashi Iwai, Davidlohr Bueso, Waiman Long
The mixing in of a sequence number into the IPC IDs is probably to
avoid ID reuse in userspace as much as possible. With extended IPCMNI
mode, the number of usable sequence numbers is greatly reduced leading
to higher chance of ID reuse.
To address this issue, we need to conserve the sequence number space
as much as possible. Right now, the sequence number is incremented
for every new ID created. In reality, we only need to increment the
sequence number when one or more IDs have been removed previously to
make sure that those IDs will not be reused when a new one is built.
This is being done only in the new extended IPCMNI mode.
Signed-off-by: Waiman Long <longman@redhat.com>
---
include/linux/ipc_namespace.h | 1 +
ipc/ipc_sysctl.c | 2 ++
ipc/util.c | 19 +++++++++++++++----
ipc/util.h | 2 ++
4 files changed, 20 insertions(+), 4 deletions(-)
diff --git a/include/linux/ipc_namespace.h b/include/linux/ipc_namespace.h
index 6ab8c1b..7d5f553 100644
--- a/include/linux/ipc_namespace.h
+++ b/include/linux/ipc_namespace.h
@@ -16,6 +16,7 @@
struct ipc_ids {
int in_use;
unsigned short seq;
+ unsigned short deleted;
struct rw_semaphore rwsem;
struct idr ipcs_idr;
int max_idx;
diff --git a/ipc/ipc_sysctl.c b/ipc/ipc_sysctl.c
index 73b7782..d9ac6ca 100644
--- a/ipc/ipc_sysctl.c
+++ b/ipc/ipc_sysctl.c
@@ -122,6 +122,7 @@ static int proc_ipc_sem_dointvec(struct ctl_table *table, int write,
static int int_max = INT_MAX;
int ipc_mni = IPCMNI;
int ipc_mni_shift = IPCMNI_SHIFT;
+bool ipc_mni_extended;
static struct ctl_table ipc_kern_table[] = {
{
@@ -252,6 +253,7 @@ static int __init ipc_mni_extend(char *str)
{
ipc_mni = IPCMNI_EXTEND;
ipc_mni_shift = IPCMNI_EXTEND_SHIFT;
+ ipc_mni_extended = true;
pr_info("IPCMNI extended to %d.\n", ipc_mni);
return 0;
}
diff --git a/ipc/util.c b/ipc/util.c
index 07ae117..3f11a81 100644
--- a/ipc/util.c
+++ b/ipc/util.c
@@ -115,7 +115,8 @@ static int __init ipc_init(void)
void ipc_init_ids(struct ipc_ids *ids)
{
ids->in_use = 0;
- ids->seq = 0;
+ ids->deleted = false;
+ ids->seq = ipc_mni_extended ? 0 : -1; /* seq # is pre-incremented */
init_rwsem(&ids->rwsem);
rhashtable_init(&ids->key_ht, &ipc_kht_params);
idr_init(&ids->ipcs_idr);
@@ -198,6 +199,11 @@ static inline int ipc_idr_alloc(struct ipc_ids *ids, struct kern_ipc_perm *new)
{
int idx, next_id = -1;
+/*
+ * To conserve sequence number space with extended ipc_mni when new ID
+ * is built, the sequence number is incremented only when one or more
+ * IDs have been removed previously.
+ */
#ifdef CONFIG_CHECKPOINT_RESTORE
next_id = ids->next_id;
ids->next_id = -1;
@@ -216,9 +222,13 @@ static inline int ipc_idr_alloc(struct ipc_ids *ids, struct kern_ipc_perm *new)
*/
if (next_id < 0) { /* !CHECKPOINT_RESTORE or next_id is unset */
- new->seq = ids->seq++;
- if (ids->seq > IPCID_SEQ_MAX)
- ids->seq = 0;
+ if (!ipc_mni_extended || ids->deleted) {
+ ids->seq++;
+ if (ids->seq > IPCID_SEQ_MAX)
+ ids->seq = 0;
+ ids->deleted = false;
+ }
+ new->seq = ids->seq;
idx = idr_alloc(&ids->ipcs_idr, new, 0, 0, GFP_NOWAIT);
} else {
new->seq = ipcid_to_seqx(next_id);
@@ -436,6 +446,7 @@ void ipc_rmid(struct ipc_ids *ids, struct kern_ipc_perm *ipcp)
idr_remove(&ids->ipcs_idr, idx);
ipc_kht_remove(ids, ipcp);
ids->in_use--;
+ ids->deleted = true;
ipcp->deleted = true;
if (unlikely(idx == ids->max_idx)) {
diff --git a/ipc/util.h b/ipc/util.h
index f11a25a..d34ea18 100644
--- a/ipc/util.h
+++ b/ipc/util.h
@@ -33,6 +33,7 @@
#ifdef CONFIG_SYSVIPC_SYSCTL
extern int ipc_mni;
extern int ipc_mni_shift;
+extern bool ipc_mni_extended;
#define IPCMNI_SEQ_SHIFT ipc_mni_shift
#define IPCMNI_IDX_MASK ((1 << ipc_mni_shift) - 1)
@@ -40,6 +41,7 @@
#else /* CONFIG_SYSVIPC_SYSCTL */
#define ipc_mni IPCMNI
+#define ipc_mni_extended false
#define IPCMNI_SEQ_SHIFT IPCMNI_SHIFT
#define IPCMNI_IDX_MASK ((1 << IPCMNI_SHIFT) - 1)
#endif /* CONFIG_SYSVIPC_SYSCTL */
--
1.8.3.1
^ permalink raw reply related [flat|nested] 5+ messages in thread