From d15801816cc716b56ada92df72ee04b7b891291c Mon Sep 17 00:00:00 2001 From: Bryan Cantrill Date: Mon, 11 Nov 2019 15:28:43 -0500 Subject: [PATCH] 11927 Log, or optionally panic, on zero-length kmem allocations Reviewed by: Dan McDonald Reviewed by: Jason King Approved by: Gordon Ross --- .../cmd/mdb/common/modules/genunix/genunix.c | 5 ++- usr/src/cmd/mdb/common/modules/genunix/kmem.c | 2 + usr/src/man/man9f/kmem_alloc.9f | 25 +++-------- usr/src/uts/common/fs/nfs/nfs_auth.c | 26 ++++++++---- usr/src/uts/common/fs/portfs/port.c | 36 +++++++++++----- usr/src/uts/common/fs/zfs/sa.c | 11 +++-- .../uts/common/io/fibre-channel/impl/fctl.c | 6 +++ usr/src/uts/common/os/fio.c | 9 ++-- usr/src/uts/common/os/kmem.c | 41 ++++++++++++++++++- usr/src/uts/common/os/smb_subr.c | 8 ++-- usr/src/uts/intel/promif/prom_emul.c | 6 ++- 11 files changed, 121 insertions(+), 54 deletions(-) diff --git a/usr/src/cmd/mdb/common/modules/genunix/genunix.c b/usr/src/cmd/mdb/common/modules/genunix/genunix.c index 9bd74ff0dd..f8d2de5f12 100644 --- a/usr/src/cmd/mdb/common/modules/genunix/genunix.c +++ b/usr/src/cmd/mdb/common/modules/genunix/genunix.c @@ -4293,8 +4293,9 @@ static const mdb_dcmd_t dcmds[] = { { "bufctl", ":[-vh] [-a addr] [-c caller] [-e earliest] [-l latest] " "[-t thd]", "print or filter a bufctl", bufctl, bufctl_help }, { "freedby", ":", "given a thread, print its freed buffers", freedby }, - { "kmalog", "?[ fail | slab ]", - "display kmem transaction log and stack traces", kmalog }, + { "kmalog", "?[ fail | slab | zerosized ]", + "display kmem transaction log and stack traces for specified type", + kmalog }, { "kmastat", "[-kmg]", "kernel memory allocator stats", kmastat }, { "kmausers", "?[-ef] [cache ...]", "current medium and large users " diff --git a/usr/src/cmd/mdb/common/modules/genunix/kmem.c b/usr/src/cmd/mdb/common/modules/genunix/kmem.c index c615af3c66..146317e012 100644 --- a/usr/src/cmd/mdb/common/modules/genunix/kmem.c +++ b/usr/src/cmd/mdb/common/modules/genunix/kmem.c @@ -3917,6 +3917,8 @@ kmalog(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) logname = "kmem_failure_log"; else if (strcmp(argv->a_un.a_str, "slab") == 0) logname = "kmem_slab_log"; + else if (strcmp(argv->a_un.a_str, "zerosized") == 0) + logname = "kmem_zerosized_log"; else return (DCMD_USAGE); } diff --git a/usr/src/man/man9f/kmem_alloc.9f b/usr/src/man/man9f/kmem_alloc.9f index 9c4f8ccb0c..56c4fb6a2d 100644 --- a/usr/src/man/man9f/kmem_alloc.9f +++ b/usr/src/man/man9f/kmem_alloc.9f @@ -5,11 +5,10 @@ .\" The contents of this file are subject to the terms of the Common Development and Distribution License (the "License"). You may not use this file except in compliance with the License. .\" You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE or http://www.opensolaris.org/os/licensing. See the License for the specific language governing permissions and limitations under the License. .\" When distributing Covered Code, include this CDDL HEADER in each file and include the License file at usr/src/OPENSOLARIS.LICENSE. If applicable, add the following below this CDDL HEADER, with the fields enclosed by brackets "[]" replaced with your own identifying information: Portions Copyright [yyyy] [name of copyright owner] -.TH KMEM_ALLOC 9F "Oct 22, 2014" +.TH KMEM_ALLOC 9F "Nov 20, 2019" .SH NAME kmem_alloc, kmem_zalloc, kmem_free \- allocate kernel memory .SH SYNOPSIS -.LP .nf #include #include @@ -30,11 +29,8 @@ kmem_alloc, kmem_zalloc, kmem_free \- allocate kernel memory .fi .SH INTERFACE LEVEL -.sp -.LP Architecture independent level 1 (DDI/DKI). .SH PARAMETERS -.sp .ne 2 .na \fB\fIsize\fR\fR @@ -64,8 +60,6 @@ Pointer to allocated memory. .RE .SH DESCRIPTION -.sp -.LP The \fBkmem_alloc()\fR function allocates \fIsize\fR bytes of kernel memory and returns a pointer to the allocated memory. The allocated memory is at least double-word aligned, so it can hold any C data structure. No greater alignment @@ -84,28 +78,20 @@ The \fBkmem_free()\fR function frees previously allocated kernel memory. The buffer address and size must exactly match the original allocation. Memory cannot be returned piecemeal. .SH RETURN VALUES -.sp -.LP If successful, \fBkmem_alloc()\fR and \fBkmem_zalloc()\fR return a pointer to the allocated memory. If \fBKM_NOSLEEP\fR is set and memory cannot be allocated without sleeping, \fBkmem_alloc()\fR and \fBkmem_zalloc()\fR return \fINULL\fR. .SH CONTEXT -.sp -.LP The \fBkmem_alloc()\fR and \fBkmem_zalloc()\fR functions can be called from interrupt context only if the \fBKM_NOSLEEP\fR flag is set. They can be called from user context with any valid \fIflag\fR. The \fBkmem_free()\fR function can be called from from user, interrupt, or kernel context. .SH SEE ALSO -.sp -.LP \fBcopyout\fR(9F), \fBfreerbuf\fR(9F), \fBgetrbuf\fR(9F) .sp .LP \fIWriting Device Drivers\fR .SH WARNINGS -.sp -.LP Memory allocated using \fBkmem_alloc()\fR is not paged. Available memory is therefore limited by the total physical memory on the system. It is also limited by the available kernel virtual address space, which is often the more @@ -127,7 +113,8 @@ garbage. This random garbage may include secure kernel data. Therefore, uninitialized kernel memory should be handled carefully. For example, never \fBcopyout\fR(9F) a potentially uninitialized buffer. .SH NOTES -.sp -.LP -\fBkmem_alloc(0\fR, \fIflag\fR\fB)\fR always returns \fINULL\fR. -\fBkmem_free(NULL, 0)\fR is legal. +\fBkmem_alloc(0\fR, \fIflag\fR\fB)\fR always returns \fINULL\fR, but +if \fBKM_SLEEP\fR is set, this behavior is considered to be deprecated; +the system may be configured to explicitly panic in this case in lieu +of returning \fINULL\fR. +\fBkmem_free(NULL, 0)\fR is legal, however. diff --git a/usr/src/uts/common/fs/nfs/nfs_auth.c b/usr/src/uts/common/fs/nfs/nfs_auth.c index 2851f8bef9..b363ba37d3 100644 --- a/usr/src/uts/common/fs/nfs/nfs_auth.c +++ b/usr/src/uts/common/fs/nfs/nfs_auth.c @@ -23,6 +23,7 @@ * Copyright 2016 Nexenta Systems, Inc. All rights reserved. * Copyright (c) 1995, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2015 by Delphix. All rights reserved. + * Copyright (c) 2015 Joyent, Inc. All rights reserved. */ #include @@ -561,11 +562,16 @@ retry: *access = res.ares.auth_perm; *srv_uid = res.ares.auth_srv_uid; *srv_gid = res.ares.auth_srv_gid; - *srv_gids_cnt = res.ares.auth_srv_gids.len; - *srv_gids = kmem_alloc(*srv_gids_cnt * sizeof (gid_t), - KM_SLEEP); - bcopy(res.ares.auth_srv_gids.val, *srv_gids, - *srv_gids_cnt * sizeof (gid_t)); + + if ((*srv_gids_cnt = res.ares.auth_srv_gids.len) != 0) { + *srv_gids = kmem_alloc(*srv_gids_cnt * + sizeof (gid_t), KM_SLEEP); + bcopy(res.ares.auth_srv_gids.val, *srv_gids, + *srv_gids_cnt * sizeof (gid_t)); + } else { + *srv_gids = NULL; + } + break; case NFSAUTH_DR_EFAIL: @@ -1054,9 +1060,13 @@ nfsauth_cache_get(struct exportinfo *exi, struct svc_req *req, int flavor, if (gid != NULL) *gid = p->auth_srv_gid; if (ngids != NULL && gids != NULL) { - *ngids = p->auth_srv_ngids; - *gids = kmem_alloc(*ngids * sizeof (gid_t), KM_SLEEP); - bcopy(p->auth_srv_gids, *gids, *ngids * sizeof (gid_t)); + if ((*ngids = p->auth_srv_ngids) != 0) { + size_t sz = *ngids * sizeof (gid_t); + *gids = kmem_alloc(sz, KM_SLEEP); + bcopy(p->auth_srv_gids, *gids, sz); + } else { + *gids = NULL; + } } access = p->auth_access; diff --git a/usr/src/uts/common/fs/portfs/port.c b/usr/src/uts/common/fs/portfs/port.c index 14d44af07c..91d998b4b5 100644 --- a/usr/src/uts/common/fs/portfs/port.c +++ b/usr/src/uts/common/fs/portfs/port.c @@ -24,6 +24,10 @@ * Use is subject to license terms. */ +/* + * Copyright (c) 2015 Joyent, Inc. All rights reserved. + */ + #include #include #include @@ -1379,12 +1383,18 @@ portnowait: if (model == DATAMODEL_NATIVE) { eventsz = sizeof (port_event_t); - kevp = kmem_alloc(eventsz * nmax, KM_NOSLEEP); - if (kevp == NULL) { - if (nmax > pp->port_max_list) - nmax = pp->port_max_list; - kevp = kmem_alloc(eventsz * nmax, KM_SLEEP); + + if (nmax == 0) { + kevp = NULL; + } else { + kevp = kmem_alloc(eventsz * nmax, KM_NOSLEEP); + if (kevp == NULL) { + if (nmax > pp->port_max_list) + nmax = pp->port_max_list; + kevp = kmem_alloc(eventsz * nmax, KM_SLEEP); + } } + results = kevp; lev = NULL; /* start with first event in the queue */ for (nevents = 0; nevents < nmax; ) { @@ -1421,12 +1431,18 @@ portnowait: port_event32_t *kevp32; eventsz = sizeof (port_event32_t); - kevp32 = kmem_alloc(eventsz * nmax, KM_NOSLEEP); - if (kevp32 == NULL) { - if (nmax > pp->port_max_list) - nmax = pp->port_max_list; - kevp32 = kmem_alloc(eventsz * nmax, KM_SLEEP); + + if (nmax == 0) { + kevp32 = NULL; + } else { + kevp32 = kmem_alloc(eventsz * nmax, KM_NOSLEEP); + if (kevp32 == NULL) { + if (nmax > pp->port_max_list) + nmax = pp->port_max_list; + kevp32 = kmem_alloc(eventsz * nmax, KM_SLEEP); + } } + results = kevp32; lev = NULL; /* start with first event in the queue */ for (nevents = 0; nevents < nmax; ) { diff --git a/usr/src/uts/common/fs/zfs/sa.c b/usr/src/uts/common/fs/zfs/sa.c index 6328d3fac3..48c48acbed 100644 --- a/usr/src/uts/common/fs/zfs/sa.c +++ b/usr/src/uts/common/fs/zfs/sa.c @@ -405,15 +405,18 @@ sa_add_layout_entry(objset_t *os, sa_attr_type_t *attrs, int attr_count, { sa_os_t *sa = os->os_sa; sa_lot_t *tb, *findtb; - int i; + int i, size; avl_index_t loc; ASSERT(MUTEX_HELD(&sa->sa_lock)); tb = kmem_zalloc(sizeof (sa_lot_t), KM_SLEEP); tb->lot_attr_count = attr_count; - tb->lot_attrs = kmem_alloc(sizeof (sa_attr_type_t) * attr_count, - KM_SLEEP); - bcopy(attrs, tb->lot_attrs, sizeof (sa_attr_type_t) * attr_count); + + if ((size = sizeof (sa_attr_type_t) * attr_count) != 0) { + tb->lot_attrs = kmem_alloc(size, KM_SLEEP); + bcopy(attrs, tb->lot_attrs, size); + } + tb->lot_num = lot_num; tb->lot_hash = hash; tb->lot_instance = 0; diff --git a/usr/src/uts/common/io/fibre-channel/impl/fctl.c b/usr/src/uts/common/io/fibre-channel/impl/fctl.c index 4c2a39013a..eb2a0c2ec5 100644 --- a/usr/src/uts/common/io/fibre-channel/impl/fctl.c +++ b/usr/src/uts/common/io/fibre-channel/impl/fctl.c @@ -24,6 +24,7 @@ */ /* * Copyright 2012 Garrett D'Amore . All rights reserved. + * Copyright (c) 2015 Joyent, Inc. All rights reserved. */ /* * Fibre channel Transport Library (fctl) @@ -5500,6 +5501,11 @@ fc_ulp_get_adapter_paths(char *pathList, int count) maxPorts ++; } + if (maxPorts == 0) { + mutex_exit(&fctl_port_lock); + return (0); + } + /* Now allocate a buffer to store all the pointers for comparisons */ portList = kmem_zalloc(sizeof (fc_local_port_t *) * maxPorts, KM_SLEEP); diff --git a/usr/src/uts/common/os/fio.c b/usr/src/uts/common/os/fio.c index 76eddd4e50..b9c6a87153 100644 --- a/usr/src/uts/common/os/fio.c +++ b/usr/src/uts/common/os/fio.c @@ -852,7 +852,8 @@ flist_fork(uf_info_t *pfip, uf_info_t *cfip) */ cfip->fi_nfiles = nfiles = flist_minsize(pfip); - cfip->fi_list = kmem_zalloc(nfiles * sizeof (uf_entry_t), KM_SLEEP); + cfip->fi_list = nfiles == 0 ? NULL : + kmem_zalloc(nfiles * sizeof (uf_entry_t), KM_SLEEP); for (fd = 0, pufp = pfip->fi_list, cufp = cfip->fi_list; fd < nfiles; fd++, pufp++, cufp++) { @@ -1492,8 +1493,8 @@ int fgetstartvp(int fd, char *path, vnode_t **startvpp) { vnode_t *startvp; - file_t *startfp; - char startchar; + file_t *startfp; + char startchar; if (fd == AT_FDCWD && path == NULL) return (EFAULT); @@ -1539,7 +1540,7 @@ fsetattrat(int fd, char *path, int flags, struct vattr *vap) { vnode_t *startvp; vnode_t *vp; - int error; + int error; /* * Since we are never called to set the size of a file, we don't diff --git a/usr/src/uts/common/os/kmem.c b/usr/src/uts/common/os/kmem.c index bc0cda418b..c125b43c6c 100644 --- a/usr/src/uts/common/os/kmem.c +++ b/usr/src/uts/common/os/kmem.c @@ -1011,6 +1011,7 @@ size_t kmem_transaction_log_size; /* transaction log size [2% of memory] */ size_t kmem_content_log_size; /* content log size [2% of memory] */ size_t kmem_failure_log_size; /* failure log [4 pages per CPU] */ size_t kmem_slab_log_size; /* slab create log [4 pages per CPU] */ +size_t kmem_zerosized_log_size; /* zero-sized log [4 pages per CPU] */ size_t kmem_content_maxsave = 256; /* KMF_CONTENTS max bytes to log */ size_t kmem_lite_minsize = 0; /* minimum buffer size for KMF_LITE */ size_t kmem_lite_maxalign = 1024; /* maximum buffer alignment for KMF_LITE */ @@ -1018,6 +1019,14 @@ int kmem_lite_pcs = 4; /* number of PCs to store in KMF_LITE mode */ size_t kmem_maxverify; /* maximum bytes to inspect in debug routines */ size_t kmem_minfirewall; /* hardware-enforced redzone threshold */ +#ifdef DEBUG +int kmem_warn_zerosized = 1; /* whether to warn on zero-sized KM_SLEEP */ +#else +int kmem_warn_zerosized = 0; /* whether to warn on zero-sized KM_SLEEP */ +#endif + +int kmem_panic_zerosized = 0; /* whether to panic on zero-sized KM_SLEEP */ + #ifdef _LP64 size_t kmem_max_cached = KMEM_BIG_MAXBUF; /* maximum kmem_alloc cache */ #else @@ -1051,6 +1060,8 @@ static vmem_t *kmem_default_arena; static vmem_t *kmem_firewall_va_arena; static vmem_t *kmem_firewall_arena; +static int kmem_zerosized; /* # of zero-sized allocs */ + /* * kmem slab consolidator thresholds (tunables) */ @@ -1098,6 +1109,7 @@ kmem_log_header_t *kmem_transaction_log; kmem_log_header_t *kmem_content_log; kmem_log_header_t *kmem_failure_log; kmem_log_header_t *kmem_slab_log; +kmem_log_header_t *kmem_zerosized_log; static int kmem_lite_count; /* # of PCs in kmem_buftag_lite_t */ @@ -2853,8 +2865,33 @@ kmem_alloc(size_t size, int kmflag) /* fall through to kmem_cache_alloc() */ } else { - if (size == 0) + if (size == 0) { + if (kmflag != KM_SLEEP && !(kmflag & KM_PANIC)) + return (NULL); + + /* + * If this is a sleeping allocation or one that has + * been specified to panic on allocation failure, we + * consider it to be deprecated behavior to allocate + * 0 bytes. If we have been configured to panic under + * this condition, we panic; if to warn, we warn -- and + * regardless, we log to the kmem_zerosized_log that + * that this condition has occurred (which gives us + * enough information to be able to debug it). + */ + if (kmem_panic && kmem_panic_zerosized) + panic("attempted to kmem_alloc() size of 0"); + + if (kmem_warn_zerosized) { + cmn_err(CE_WARN, "kmem_alloc(): sleeping " + "allocation with size of 0; " + "see kmem_zerosized_log for details"); + } + + kmem_log_event(kmem_zerosized_log, NULL, NULL, NULL); + return (NULL); + } buf = vmem_alloc(kmem_oversize_arena, size, kmflag & KM_VMFLAGS); @@ -4397,8 +4434,8 @@ kmem_init(void) } kmem_failure_log = kmem_log_init(kmem_failure_log_size); - kmem_slab_log = kmem_log_init(kmem_slab_log_size); + kmem_zerosized_log = kmem_log_init(kmem_zerosized_log_size); /* * Initialize STREAMS message caches so allocb() is available. diff --git a/usr/src/uts/common/os/smb_subr.c b/usr/src/uts/common/os/smb_subr.c index 6084676b17..6dc7230bed 100644 --- a/usr/src/uts/common/os/smb_subr.c +++ b/usr/src/uts/common/os/smb_subr.c @@ -25,7 +25,9 @@ * Use is subject to license terms. */ -#pragma ident "%Z%%M% %I% %E% SMI" +/* + * Copyright (c) 2015 Joyent, Inc. All rights reserved. + */ #include #include @@ -43,13 +45,13 @@ smb_strerror(int err) void * smb_alloc(size_t len) { - return (kmem_alloc(len, KM_SLEEP)); + return (len > 0 ? kmem_alloc(len, KM_SLEEP) : NULL); } void * smb_zalloc(size_t len) { - return (kmem_zalloc(len, KM_SLEEP)); + return (len > 0 ? kmem_zalloc(len, KM_SLEEP) : NULL); } void diff --git a/usr/src/uts/intel/promif/prom_emul.c b/usr/src/uts/intel/promif/prom_emul.c index 5497d9eab8..cdf190ec6a 100644 --- a/usr/src/uts/intel/promif/prom_emul.c +++ b/usr/src/uts/intel/promif/prom_emul.c @@ -24,7 +24,9 @@ * Use is subject to license terms. */ -#pragma ident "%Z%%M% %I% %E% SMI" +/* + * Copyright (c) 2015 Joyent, Inc. All rights reserved. + */ #include #include @@ -46,7 +48,7 @@ promif_create_prop(prom_node_t *pnp, char *name, void *val, int len, int flags) q = kmem_zalloc(sizeof (*q), KM_SLEEP); q->pp_name = kmem_zalloc(strlen(name) + 1, KM_SLEEP); (void) strcpy(q->pp_name, name); - q->pp_val = kmem_alloc(len, KM_SLEEP); + q->pp_val = len > 0 ? kmem_alloc(len, KM_SLEEP) : NULL; q->pp_len = len; switch (flags) { case DDI_PROP_TYPE_INT: -- 2.45.2