summaryrefslogtreecommitdiffstats
path: root/fs/btrfs/zlib.c
diff options
context:
space:
mode:
authorJinke Han <jinkehan@didiglobal.com>2026-09-08 15:37:42 +0800
committerIngo Molnar <mingo@kernel.org>2026-09-17 09:48:42 +0200
commita5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 (patch)
tree73fef824f9a846cf935869d0dbc5070ef6e2f741 /fs/btrfs/zlib.c
downloadlinux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.tar.gz
linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.zip
x86/kprobes: Fix crash when probing CS CALL instructionsgrafted
When using eBPF to probe CS CALL instructions within a function, a crash can be triggered. The eBPF tool probes offset 257 of the __hrtimer_run_queues() function: <__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1) <__hrtimer_run_queues+254>: mov %r14,%rdi <__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12> <__hrtimer_run_queues+263>: mov %eax,%r12d <__hrtimer_run_queues+266>: xchg %ax,%ax <__hrtimer_run_queues+268>: mov %r13,%rdi Which triggers this crash: BUG: unable to handle page fault for address: 00000000000f41c9 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page PGD 0 P4D 0 Oops: 0002 [#1] SMP NOPTI CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P RIP: 0010:__hrtimer_run_queues+0x106/0x230 Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is at the 6th byte of the above CS CALL instruction. Since the CS CALL instruction occupies 6 bytes, the exception occurred in the middle of that call instruction. The root cause is that when using eBPF tools to probe in the middle of a function, a kprobe with INT3 is used as the underlying implementation. During single-step emulation of the original CALL instruction, int3_emulate_call() assumes that the probed CALL instruction is 5 bytes long. However, the actual CS-prefixed CALL instruction occupies 6 bytes, so it constructs an incorrect exception return address. When the CPU returns from the kprobe handler, the next instruction to be executed is at the address of the last byte of that CS CALL instruction. Coincidentally, starting from that address, the CPU fetches and decodes a completely different instruction, which ultimately triggers a kernel crash. Fix the issue by using the actual instruction length obtained from the instruction decoder when constructing the exception return address, rather than relying on the hardcoded CALL_INSN_SIZE macro. [ mingo: Refined the changelog ] Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step") Suggested-by: Masami Hiramatsu (Google) <mhiramat@kernel.org> Signed-off-by: Jinke Han <jinkehan@didiglobal.com> Signed-off-by: Ingo Molnar <mingo@kernel.org> Reviewed-by: Masami Hiramatsu (Google) <mhiramat@kernel.org> Acked-by: Yafang Shao <laoar.shao@gmail.com> Acked-by: Borislav Petkov <bp@alien8.de> Cc: Peter Zijlstra <peterz@infradead.org> Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000
Diffstat (limited to 'fs/btrfs/zlib.c')
-rw-r--r--fs/btrfs/zlib.c514
1 files changed, 514 insertions, 0 deletions
diff --git a/fs/btrfs/zlib.c b/fs/btrfs/zlib.c
new file mode 100644
index 000000000..486b52db5
--- /dev/null
+++ b/fs/btrfs/zlib.c
@@ -0,0 +1,514 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2008 Oracle. All rights reserved.
+ *
+ * Based on jffs2 zlib code:
+ * Copyright © 2001-2007 Red Hat, Inc.
+ * Created by David Woodhouse <dwmw2@infradead.org>
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/zlib.h>
+#include <linux/zutil.h>
+#include <linux/mm.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/sched.h>
+#include <linux/pagemap.h>
+#include <linux/bio.h>
+#include <linux/refcount.h>
+#include "btrfs_inode.h"
+#include "compression.h"
+#include "fs.h"
+#include "subpage.h"
+
+/* workspace buffer size for s390 zlib hardware support */
+#define ZLIB_DFLTCC_BUF_SIZE (4 * PAGE_SIZE)
+
+struct workspace {
+ z_stream strm;
+ char *buf;
+ unsigned int buf_size;
+ struct list_head list;
+ int level;
+};
+
+struct list_head *zlib_get_workspace(struct btrfs_fs_info *fs_info, unsigned int level)
+{
+ struct list_head *ws = btrfs_get_workspace(fs_info, BTRFS_COMPRESS_ZLIB, level);
+ struct workspace *workspace = list_entry(ws, struct workspace, list);
+
+ workspace->level = level;
+
+ return ws;
+}
+
+void zlib_free_workspace(struct list_head *ws)
+{
+ struct workspace *workspace = list_entry(ws, struct workspace, list);
+
+ kvfree(workspace->strm.workspace);
+ kfree(workspace->buf);
+ kfree(workspace);
+}
+
+/*
+ * For s390 hardware acceleration, the buffer size should be at least
+ * ZLIB_DFLTCC_BUF_SIZE to achieve the best performance.
+ *
+ * But if bs > ps we can have large enough folios that meet the s390 hardware
+ * handling.
+ */
+static bool need_special_buffer(struct btrfs_fs_info *fs_info)
+{
+ if (!zlib_deflate_dfltcc_enabled())
+ return false;
+ if (btrfs_min_folio_size(fs_info) >= ZLIB_DFLTCC_BUF_SIZE)
+ return false;
+ return true;
+}
+
+struct list_head *zlib_alloc_workspace(struct btrfs_fs_info *fs_info, unsigned int level)
+{
+ struct workspace *workspace;
+ int workspacesize;
+
+ workspace = kzalloc_obj(*workspace);
+ if (!workspace)
+ return ERR_PTR(-ENOMEM);
+
+ workspacesize = max(zlib_deflate_workspacesize(MAX_WBITS, MAX_MEM_LEVEL),
+ zlib_inflate_workspacesize());
+ workspace->strm.workspace = kvzalloc(workspacesize, GFP_KERNEL | __GFP_NOWARN);
+ workspace->level = level;
+ workspace->buf = NULL;
+ if (need_special_buffer(fs_info)) {
+ workspace->buf = kmalloc(ZLIB_DFLTCC_BUF_SIZE,
+ __GFP_NOMEMALLOC | __GFP_NORETRY |
+ __GFP_NOWARN | GFP_NOIO);
+ workspace->buf_size = ZLIB_DFLTCC_BUF_SIZE;
+ }
+ if (!workspace->buf) {
+ workspace->buf = kmalloc(fs_info->sectorsize, GFP_KERNEL);
+ workspace->buf_size = fs_info->sectorsize;
+ }
+ if (!workspace->strm.workspace || !workspace->buf)
+ goto fail;
+
+ INIT_LIST_HEAD(&workspace->list);
+
+ return &workspace->list;
+fail:
+ zlib_free_workspace(&workspace->list);
+ return ERR_PTR(-ENOMEM);
+}
+
+/*
+ * Helper for S390x with hardware zlib compression support.
+ *
+ * That hardware acceleration requires a buffer size larger than a single page
+ * to get ideal performance, thus we need to do the memory copy rather than
+ * use the page cache directly as input buffer.
+ */
+static int copy_data_into_buffer(struct address_space *mapping,
+ struct workspace *workspace, u64 filepos,
+ unsigned long length)
+{
+ u64 cur = filepos;
+
+ /* It's only for hardware accelerated zlib code. */
+ ASSERT(zlib_deflate_dfltcc_enabled());
+
+ while (cur < filepos + length) {
+ struct folio *folio;
+ void *data_in;
+ unsigned int offset;
+ unsigned long copy_length;
+ int ret;
+
+ ret = btrfs_compress_filemap_get_folio(mapping, cur, &folio);
+ if (ret < 0)
+ return ret;
+
+ offset = offset_in_folio(folio, cur);
+ copy_length = min(folio_size(folio) - offset,
+ filepos + length - cur);
+
+ data_in = kmap_local_folio(folio, offset);
+ memcpy(workspace->buf + cur - filepos, data_in, copy_length);
+ kunmap_local(data_in);
+ folio_put(folio);
+ cur += copy_length;
+ }
+ return 0;
+}
+
+int zlib_compress_bio(struct list_head *ws, struct compressed_bio *cb)
+{
+ struct btrfs_inode *inode = cb->bbio.inode;
+ struct btrfs_fs_info *fs_info = inode->root->fs_info;
+ struct workspace *workspace = list_entry(ws, struct workspace, list);
+ struct address_space *mapping = inode->vfs_inode.i_mapping;
+ struct bio *bio = &cb->bbio.bio;
+ u64 start = cb->start;
+ u32 len = cb->len;
+ const u32 min_folio_size = btrfs_min_folio_size(fs_info);
+ int ret;
+ char *data_in = NULL;
+ struct folio *in_folio = NULL;
+ struct folio *out_folio = NULL;
+ const u64 orig_end = start + len;
+
+ ret = zlib_deflateInit(&workspace->strm, workspace->level);
+ if (unlikely(ret != Z_OK)) {
+ btrfs_err(fs_info,
+ "zlib compression init failed, error %d root %llu inode %llu offset %llu",
+ ret, btrfs_root_id(inode->root), btrfs_ino(inode), start);
+ ret = -EIO;
+ goto out;
+ }
+
+ workspace->strm.total_in = 0;
+ workspace->strm.total_out = 0;
+
+ out_folio = btrfs_alloc_compr_folio(fs_info, GFP_NOFS);
+ if (out_folio == NULL) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ workspace->strm.next_in = workspace->buf;
+ workspace->strm.avail_in = 0;
+ workspace->strm.next_out = folio_address(out_folio);
+ workspace->strm.avail_out = min_folio_size;
+
+ while (workspace->strm.total_in < len) {
+ /*
+ * Get next input pages and copy the contents to the workspace
+ * buffer if required.
+ */
+ if (workspace->strm.avail_in == 0) {
+ unsigned long bytes_left = len - workspace->strm.total_in;
+ unsigned int copy_length = min(bytes_left, workspace->buf_size);
+
+ /*
+ * For s390 hardware accelerated zlib, and our folio is smaller
+ * than the copy_length, we need to fill the buffer so that
+ * we can take full advantage of hardware acceleration.
+ */
+ if (need_special_buffer(fs_info)) {
+ ret = copy_data_into_buffer(mapping, workspace,
+ start, copy_length);
+ if (ret < 0)
+ goto out;
+ start += copy_length;
+ workspace->strm.next_in = workspace->buf;
+ workspace->strm.avail_in = copy_length;
+ } else {
+ unsigned int cur_len;
+
+ if (data_in) {
+ kunmap_local(data_in);
+ folio_put(in_folio);
+ data_in = NULL;
+ }
+ ret = btrfs_compress_filemap_get_folio(mapping,
+ start, &in_folio);
+ if (ret < 0)
+ goto out;
+ cur_len = btrfs_calc_input_length(in_folio, orig_end, start);
+ data_in = kmap_local_folio(in_folio,
+ offset_in_folio(in_folio, start));
+ start += cur_len;
+ workspace->strm.next_in = data_in;
+ workspace->strm.avail_in = cur_len;
+ }
+ }
+
+ ret = zlib_deflate(&workspace->strm, Z_SYNC_FLUSH);
+ if (unlikely(ret != Z_OK)) {
+ btrfs_warn(fs_info,
+ "zlib compression failed, error %d root %llu inode %llu offset %llu",
+ ret, btrfs_root_id(inode->root), btrfs_ino(inode),
+ start);
+ zlib_deflateEnd(&workspace->strm);
+ ret = -EIO;
+ goto out;
+ }
+
+ /* We're making it bigger, give up. */
+ if (workspace->strm.total_in > fs_info->sectorsize * 2 &&
+ workspace->strm.total_in < workspace->strm.total_out) {
+ ret = -E2BIG;
+ goto out;
+ }
+ if (workspace->strm.total_out >= len) {
+ ret = -E2BIG;
+ goto out;
+ }
+ /* Queue the full folio and allocate a new one. */
+ if (workspace->strm.avail_out == 0) {
+ if (!bio_add_folio(bio, out_folio, folio_size(out_folio), 0)) {
+ ret = -E2BIG;
+ goto out;
+ }
+
+ out_folio = btrfs_alloc_compr_folio(fs_info, GFP_NOFS);
+ if (out_folio == NULL) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ workspace->strm.avail_out = min_folio_size;
+ workspace->strm.next_out = folio_address(out_folio);
+ }
+ /* We're all done. */
+ if (workspace->strm.total_in >= len)
+ break;
+ }
+
+ workspace->strm.avail_in = 0;
+
+ /*
+ * Call deflate with Z_FINISH flush parameter providing more output
+ * space but no more input data, until it returns with Z_STREAM_END.
+ */
+ while (ret != Z_STREAM_END) {
+ ret = zlib_deflate(&workspace->strm, Z_FINISH);
+ if (ret == Z_STREAM_END)
+ break;
+ if (unlikely(ret != Z_OK && ret != Z_BUF_ERROR)) {
+ zlib_deflateEnd(&workspace->strm);
+ ret = -EIO;
+ goto out;
+ } else if (workspace->strm.avail_out == 0) {
+ if (workspace->strm.total_out >= len) {
+ ret = -E2BIG;
+ goto out;
+ }
+ if (!bio_add_folio(bio, out_folio, folio_size(out_folio), 0)) {
+ ret = -E2BIG;
+ goto out;
+ }
+ /* Get another folio for the stream end. */
+ out_folio = btrfs_alloc_compr_folio(fs_info, GFP_NOFS);
+ if (out_folio == NULL) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ workspace->strm.avail_out = min_folio_size;
+ workspace->strm.next_out = folio_address(out_folio);
+ }
+ }
+ /* Queue the remaining part of the folio. */
+ if (workspace->strm.total_out > bio->bi_iter.bi_size) {
+ const u32 cur_len = workspace->strm.total_out - bio->bi_iter.bi_size;
+
+ ASSERT(cur_len <= folio_size(out_folio));
+
+ if (!bio_add_folio(bio, out_folio, cur_len, 0)) {
+ ret = -E2BIG;
+ goto out;
+ }
+ } else {
+ /* The last folio hasn't' been utilized. */
+ btrfs_free_compr_folio(out_folio);
+ }
+ out_folio = NULL;
+ ASSERT(bio->bi_iter.bi_size == workspace->strm.total_out);
+ zlib_deflateEnd(&workspace->strm);
+
+ if (workspace->strm.total_out >= workspace->strm.total_in) {
+ ret = -E2BIG;
+ goto out;
+ }
+
+ ret = 0;
+out:
+ if (out_folio)
+ btrfs_free_compr_folio(out_folio);
+ if (data_in) {
+ kunmap_local(data_in);
+ folio_put(in_folio);
+ }
+
+ return ret;
+}
+
+int zlib_decompress_bio(struct list_head *ws, struct compressed_bio *cb)
+{
+ struct btrfs_fs_info *fs_info = cb_to_fs_info(cb);
+ struct workspace *workspace = list_entry(ws, struct workspace, list);
+ struct folio_iter fi;
+ const u32 min_folio_size = btrfs_min_folio_size(fs_info);
+ int ret = 0, ret2;
+ int wbits = MAX_WBITS;
+ char *data_in;
+ size_t total_out = 0;
+ const size_t srclen = bio_get_size(&cb->bbio.bio);
+ unsigned long buf_start;
+
+ bio_first_folio(&fi, &cb->bbio.bio, 0);
+
+ /* We must have at least one folio here, that has the correct size. */
+ if (unlikely(!fi.folio))
+ return -EINVAL;
+ ASSERT(folio_size(fi.folio) == min_folio_size);
+
+ data_in = kmap_local_folio(fi.folio, 0);
+ workspace->strm.next_in = data_in;
+ workspace->strm.avail_in = min_t(size_t, srclen, min_folio_size);
+ workspace->strm.total_in = 0;
+
+ workspace->strm.total_out = 0;
+ workspace->strm.next_out = workspace->buf;
+ workspace->strm.avail_out = workspace->buf_size;
+
+ /* If it's deflate, and it's got no preset dictionary, then
+ we can tell zlib to skip the adler32 check. */
+ if (srclen > 2 && !(data_in[1] & PRESET_DICT) &&
+ ((data_in[0] & 0x0f) == Z_DEFLATED) &&
+ !(((data_in[0]<<8) + data_in[1]) % 31)) {
+
+ wbits = -((data_in[0] >> 4) + 8);
+ workspace->strm.next_in += 2;
+ workspace->strm.avail_in -= 2;
+ }
+
+ ret = zlib_inflateInit2(&workspace->strm, wbits);
+ if (unlikely(ret != Z_OK)) {
+ struct btrfs_inode *inode = cb->bbio.inode;
+
+ kunmap_local(data_in);
+ btrfs_err(inode->root->fs_info,
+ "zlib decompression init failed, error %d root %llu inode %llu offset %llu",
+ ret, btrfs_root_id(inode->root), btrfs_ino(inode), cb->start);
+ return -EIO;
+ }
+ while (workspace->strm.total_in < srclen) {
+ ret = zlib_inflate(&workspace->strm, Z_NO_FLUSH);
+ if (ret != Z_OK && ret != Z_STREAM_END)
+ break;
+
+ buf_start = total_out;
+ total_out = workspace->strm.total_out;
+
+ /* we didn't make progress in this inflate call, we're done */
+ if (buf_start == total_out)
+ break;
+
+ ret2 = btrfs_decompress_buf2page(workspace->buf,
+ total_out - buf_start, cb, buf_start);
+ if (ret2 == 0) {
+ ret = 0;
+ goto done;
+ }
+
+ workspace->strm.next_out = workspace->buf;
+ workspace->strm.avail_out = workspace->buf_size;
+
+ if (workspace->strm.avail_in == 0) {
+ unsigned long tmp;
+ kunmap_local(data_in);
+ bio_next_folio(&fi, &cb->bbio.bio);
+ if (!fi.folio) {
+ data_in = NULL;
+ break;
+ }
+ ASSERT(folio_size(fi.folio) == min_folio_size);
+ data_in = kmap_local_folio(fi.folio, 0);
+ workspace->strm.next_in = data_in;
+ tmp = srclen - workspace->strm.total_in;
+ workspace->strm.avail_in = min(tmp, min_folio_size);
+ }
+ }
+ if (unlikely(ret != Z_STREAM_END)) {
+ btrfs_err(cb->bbio.inode->root->fs_info,
+ "zlib decompression failed, error %d root %llu inode %llu offset %llu",
+ ret, btrfs_root_id(cb->bbio.inode->root),
+ btrfs_ino(cb->bbio.inode), cb->start);
+ ret = -EIO;
+ } else {
+ ret = 0;
+ }
+done:
+ zlib_inflateEnd(&workspace->strm);
+ if (data_in)
+ kunmap_local(data_in);
+ return ret;
+}
+
+int zlib_decompress(struct list_head *ws, const u8 *data_in,
+ struct folio *dest_folio, unsigned long dest_pgoff, size_t srclen,
+ size_t destlen)
+{
+ struct workspace *workspace = list_entry(ws, struct workspace, list);
+ int ret = 0;
+ int wbits = MAX_WBITS;
+ unsigned long to_copy;
+
+ workspace->strm.next_in = data_in;
+ workspace->strm.avail_in = srclen;
+ workspace->strm.total_in = 0;
+
+ workspace->strm.next_out = workspace->buf;
+ workspace->strm.avail_out = workspace->buf_size;
+ workspace->strm.total_out = 0;
+ /* If it's deflate, and it's got no preset dictionary, then
+ we can tell zlib to skip the adler32 check. */
+ if (srclen > 2 && !(data_in[1] & PRESET_DICT) &&
+ ((data_in[0] & 0x0f) == Z_DEFLATED) &&
+ !(((data_in[0]<<8) + data_in[1]) % 31)) {
+
+ wbits = -((data_in[0] >> 4) + 8);
+ workspace->strm.next_in += 2;
+ workspace->strm.avail_in -= 2;
+ }
+
+ ret = zlib_inflateInit2(&workspace->strm, wbits);
+ if (unlikely(ret != Z_OK)) {
+ struct btrfs_inode *inode = folio_to_inode(dest_folio);
+
+ btrfs_err(inode->root->fs_info,
+ "zlib decompression init failed, error %d root %llu inode %llu offset %llu",
+ ret, btrfs_root_id(inode->root), btrfs_ino(inode),
+ folio_pos(dest_folio));
+ return -EIO;
+ }
+
+ /*
+ * Everything (in/out buf) should be at most one sector, there should
+ * be no need to switch any input/output buffer.
+ */
+ ret = zlib_inflate(&workspace->strm, Z_FINISH);
+ to_copy = min(workspace->strm.total_out, destlen);
+ if (ret != Z_STREAM_END)
+ goto out;
+
+ memcpy_to_folio(dest_folio, dest_pgoff, workspace->buf, to_copy);
+
+out:
+ if (unlikely(to_copy != destlen)) {
+ struct btrfs_inode *inode = folio_to_inode(dest_folio);
+
+ btrfs_err(inode->root->fs_info,
+"zlib decompression failed, error %d root %llu inode %llu offset %llu decompressed %lu expected %zu",
+ ret, btrfs_root_id(inode->root), btrfs_ino(inode),
+ folio_pos(dest_folio), to_copy, destlen);
+ ret = -EIO;
+ } else {
+ ret = 0;
+ }
+
+ zlib_inflateEnd(&workspace->strm);
+
+ if (unlikely(to_copy < destlen))
+ folio_zero_range(dest_folio, dest_pgoff + to_copy, destlen - to_copy);
+ return ret;
+}
+
+const struct btrfs_compress_levels btrfs_zlib_compress = {
+ .min_level = 1,
+ .max_level = 9,
+ .default_level = BTRFS_ZLIB_DEFAULT_LEVEL,
+};