1 /*- 2 * Copyright (c) 2021-2022 Juniper Networks 3 * 4 * This software was developed by Mitchell Horne <mhorne@FreeBSD.org> 5 * under sponsorship from Juniper Networks and Klara Systems. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include <sys/param.h> 30 #include <sys/systm.h> 31 #include <sys/conf.h> 32 #include <sys/caprights.h> 33 #include <sys/disk.h> 34 #include <sys/eventhandler.h> 35 #include <sys/fcntl.h> 36 #include <sys/file.h> 37 #include <sys/kerneldump.h> 38 #include <sys/limits.h> 39 #include <sys/malloc.h> 40 #include <sys/namei.h> 41 #include <sys/priv.h> 42 #include <sys/proc.h> 43 #include <sys/stat.h> 44 #include <sys/sysctl.h> 45 #include <sys/vnode.h> 46 47 #include <machine/pcb.h> 48 #include <machine/vmparam.h> 49 50 static dumper_start_t vnode_dumper_start; 51 static dumper_t vnode_dump; 52 static dumper_hdr_t vnode_write_headers; 53 54 static struct sx livedump_sx; 55 SX_SYSINIT(livedump, &livedump_sx, "Livedump sx"); 56 57 /* 58 * Invoke a live minidump on the system. 59 */ 60 int 61 livedump_start(int fd, int flags, uint8_t compression) 62 { 63 #if MINIDUMP_PAGE_TRACKING == 1 64 struct file *fp; 65 struct vnode *vp; 66 int error; 67 68 error = priv_check(curthread, PRIV_KMEM_READ); 69 if (error != 0) 70 return (error); 71 72 if (flags != 0) 73 return (EINVAL); 74 75 error = getvnode(curthread, fd, &cap_write_rights, &fp); 76 if (error != 0) 77 return (error); 78 vp = fp->f_vnode; 79 80 if ((fp->f_flag & FWRITE) == 0) { 81 error = EBADF; 82 goto drop; 83 } 84 error = livedump_start_vnode(vp, flags, compression); 85 if (error != 0) 86 goto drop; 87 drop: 88 fdrop(fp, curthread); 89 return (error); 90 #else 91 return (EOPNOTSUPP); 92 #endif /* MINIDUMP_PAGE_TRACKING == 1 */ 93 } 94 95 int 96 livedump_start_vnode(struct vnode *vp, int flags, uint8_t compression) 97 { 98 #if MINIDUMP_PAGE_TRACKING == 1 99 struct dumperinfo di, *livedi; 100 struct diocskerneldump_arg kda; 101 void *rl_cookie; 102 int error; 103 104 /* Set up a new dumper. */ 105 bzero(&di, sizeof(di)); 106 di.dumper_start = vnode_dumper_start; 107 di.dumper = vnode_dump; 108 di.dumper_hdr = vnode_write_headers; 109 di.blocksize = PAGE_SIZE; /* Arbitrary. */ 110 di.maxiosize = MAXDUMPPGS * PAGE_SIZE; 111 112 bzero(&kda, sizeof(kda)); 113 kda.kda_compression = compression; 114 error = dumper_create(&di, "livedump", &kda, &livedi); 115 if (error != 0) 116 return (error); 117 118 /* Only allow one livedump to proceed at a time. */ 119 if (sx_try_xlock(&livedump_sx) == 0) { 120 dumper_destroy(livedi); 121 error = EBUSY; 122 return (error); 123 } 124 125 /* To be used by the callback functions. */ 126 livedi->priv = vp; 127 128 /* Lock the entire file range and vnode. */ 129 rl_cookie = vn_rangelock_wlock(vp, 0, OFF_MAX); 130 vn_lock(vp, LK_EXCLUSIVE | LK_RETRY); 131 132 EVENTHANDLER_INVOKE(livedumper_start, &error); 133 if (error != 0) 134 goto out; 135 136 curthread->td_pflags2 |= TDP2_SAN_QUIET; 137 dump_savectx(); 138 error = minidumpsys(livedi, true); 139 curthread->td_pflags2 &= ~TDP2_SAN_QUIET; 140 141 EVENTHANDLER_INVOKE(livedumper_finish); 142 out: 143 VOP_UNLOCK(vp); 144 vn_rangelock_unlock(vp, rl_cookie); 145 sx_xunlock(&livedump_sx); 146 dumper_destroy(livedi); 147 return (error); 148 #else 149 return (EOPNOTSUPP); 150 #endif /* MINIDUMP_PAGE_TRACKING == 1 */ 151 } 152 153 int 154 vnode_dumper_start(struct dumperinfo *di, void *key, uint32_t keysize) 155 { 156 157 /* Always begin with an offset of zero. */ 158 di->dumpoff = 0; 159 160 KASSERT(keysize == 0, ("encryption not supported for livedumps")); 161 return (0); 162 } 163 164 /* 165 * Callback from dumpsys() to dump a chunk of memory. 166 * 167 * Parameters: 168 * arg Opaque private pointer to vnode 169 * virtual Virtual address (where to read the data from) 170 * offset Offset from start of core file 171 * length Data length 172 * 173 * Return value: 174 * 0 on success 175 * errno on error 176 */ 177 int 178 vnode_dump(void *arg, void *virtual, off_t offset, size_t length) 179 { 180 struct vnode *vp; 181 int error = 0; 182 183 vp = arg; 184 MPASS(vp != NULL); 185 ASSERT_VOP_LOCKED(vp, __func__); 186 187 EVENTHANDLER_INVOKE(livedumper_dump, virtual, offset, length, &error); 188 if (error != 0) 189 return (error); 190 191 /* Done? */ 192 if (virtual == NULL) 193 return (0); 194 195 error = vn_rdwr(UIO_WRITE, vp, virtual, length, offset, UIO_SYSSPACE, 196 IO_NODELOCKED, curthread->td_ucred, NOCRED, NULL, curthread); 197 if (error != 0) 198 uprintf("%s: error writing livedump block at offset %jx: %d\n", 199 __func__, (uintmax_t)offset, error); 200 return (error); 201 } 202 203 /* 204 * Callback from dumpsys() to write out the dump header, placed at the end. 205 */ 206 int 207 vnode_write_headers(struct dumperinfo *di, struct kerneldumpheader *kdh) 208 { 209 struct vnode *vp; 210 int error; 211 off_t offset; 212 213 vp = di->priv; 214 MPASS(vp != NULL); 215 ASSERT_VOP_LOCKED(vp, __func__); 216 217 /* Compensate for compression/encryption adjustment of dumpoff. */ 218 offset = roundup2(di->dumpoff, di->blocksize); 219 220 /* Write the kernel dump header to the end of the file. */ 221 error = vn_rdwr(UIO_WRITE, vp, kdh, sizeof(*kdh), offset, 222 UIO_SYSSPACE, IO_NODELOCKED, curthread->td_ucred, NOCRED, NULL, 223 curthread); 224 if (error != 0) 225 uprintf("%s: error writing livedump header: %d\n", __func__, 226 error); 227 return (error); 228 } 229