fs/quota: handle overflows of sysctl fs.quota.* and report as unsigned long
authorKonstantin Khlebnikov <khlebnikov@yandex-team.ru>
Sun, 10 Nov 2019 09:49:06 +0000 (12:49 +0300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 4 Jan 2020 18:12:53 +0000 (19:12 +0100)
[ Upstream commit 6fcbcec9cfc7b3c6a2c1f1a23ebacedff7073e0a ]

Quota statistics counted as 64-bit per-cpu counter. Reading sums per-cpu
fractions as signed 64-bit int, filters negative values and then reports
lower half as signed 32-bit int.

Result may looks like:

fs.quota.allocated_dquots = 22327
fs.quota.cache_hits = -489852115
fs.quota.drops = -487288718
fs.quota.free_dquots = 22083
fs.quota.lookups = -486883485
fs.quota.reads = 22327
fs.quota.syncs = 335064
fs.quota.writes = 3088689

Values bigger than 2^31-1 reported as negative.

All counters except "allocated_dquots" and "free_dquots" are monotonic,
thus they should be reported as is without filtering negative values.

Kernel doesn't have generic helper for 64-bit sysctl yet,
let's use at least unsigned long.

Link: https://lore.kernel.org/r/157337934693.2078.9842146413181153727.stgit@buzz
Signed-off-by: Konstantin Khlebnikov <khlebnikov@yandex-team.ru>
Signed-off-by: Jan Kara <jack@suse.cz>
Signed-off-by: Sasha Levin <sashal@kernel.org>
fs/quota/dquot.c
include/linux/quota.h

index 59b00d8..154f175 100644 (file)
@@ -2853,68 +2853,73 @@ EXPORT_SYMBOL(dquot_quotactl_sysfile_ops);
 static int do_proc_dqstats(struct ctl_table *table, int write,
                     void __user *buffer, size_t *lenp, loff_t *ppos)
 {
-       unsigned int type = (int *)table->data - dqstats.stat;
+       unsigned int type = (unsigned long *)table->data - dqstats.stat;
+       s64 value = percpu_counter_sum(&dqstats.counter[type]);
+
+       /* Filter negative values for non-monotonic counters */
+       if (value < 0 && (type == DQST_ALLOC_DQUOTS ||
+                         type == DQST_FREE_DQUOTS))
+               value = 0;
 
        /* Update global table */
-       dqstats.stat[type] =
-                       percpu_counter_sum_positive(&dqstats.counter[type]);
-       return proc_dointvec(table, write, buffer, lenp, ppos);
+       dqstats.stat[type] = value;
+       return proc_doulongvec_minmax(table, write, buffer, lenp, ppos);
 }
 
 static struct ctl_table fs_dqstats_table[] = {
        {
                .procname       = "lookups",
                .data           = &dqstats.stat[DQST_LOOKUPS],
-               .maxlen         = sizeof(int),
+               .maxlen         = sizeof(unsigned long),
                .mode           = 0444,
                .proc_handler   = do_proc_dqstats,
        },
        {
                .procname       = "drops",
                .data           = &dqstats.stat[DQST_DROPS],
-               .maxlen         = sizeof(int),
+               .maxlen         = sizeof(unsigned long),
                .mode           = 0444,
                .proc_handler   = do_proc_dqstats,
        },
        {
                .procname       = "reads",
                .data           = &dqstats.stat[DQST_READS],
-               .maxlen         = sizeof(int),
+               .maxlen         = sizeof(unsigned long),
                .mode           = 0444,
                .proc_handler   = do_proc_dqstats,
        },
        {
                .procname       = "writes",
                .data           = &dqstats.stat[DQST_WRITES],
-               .maxlen         = sizeof(int),
+               .maxlen         = sizeof(unsigned long),
                .mode           = 0444,
                .proc_handler   = do_proc_dqstats,
        },
        {
                .procname       = "cache_hits",
                .data           = &dqstats.stat[DQST_CACHE_HITS],
-               .maxlen         = sizeof(int),
+               .maxlen         = sizeof(unsigned long),
                .mode           = 0444,
                .proc_handler   = do_proc_dqstats,
        },
        {
                .procname       = "allocated_dquots",
                .data           = &dqstats.stat[DQST_ALLOC_DQUOTS],
-               .maxlen         = sizeof(int),
+               .maxlen         = sizeof(unsigned long),
                .mode           = 0444,
                .proc_handler   = do_proc_dqstats,
        },
        {
                .procname       = "free_dquots",
                .data           = &dqstats.stat[DQST_FREE_DQUOTS],
-               .maxlen         = sizeof(int),
+               .maxlen         = sizeof(unsigned long),
                .mode           = 0444,
                .proc_handler   = do_proc_dqstats,
        },
        {
                .procname       = "syncs",
                .data           = &dqstats.stat[DQST_SYNCS],
-               .maxlen         = sizeof(int),
+               .maxlen         = sizeof(unsigned long),
                .mode           = 0444,
                .proc_handler   = do_proc_dqstats,
        },
index f32dd27..27aab84 100644 (file)
@@ -263,7 +263,7 @@ enum {
 };
 
 struct dqstats {
-       int stat[_DQST_DQSTAT_LAST];
+       unsigned long stat[_DQST_DQSTAT_LAST];
        struct percpu_counter counter[_DQST_DQSTAT_LAST];
 };