{"record":{"id":"0c9db959e5b72484","repo":"niri-wm/niri","slug":"buffer-does-not-fit-in-its-shm-pool","errorCode":null,"errorMessage":"buffer does not fit in its shm pool","messagePattern":"buffer does not fit in its shm pool","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/render_helpers/mod.rs","lineNumber":335,"sourceCode":"                && buffer_data.width == size.w\n                && buffer_data.height == size.h,\n            \"invalid buffer format or size\"\n        );\n\n        // The client chooses the stride and may pad rows, so only the first\n        // row_len bytes can be used here.\n        let row_len = size.w as usize * 4;\n        let height = size.h as usize;\n        let offset = usize::try_from(buffer_data.offset).context(\"negative buffer offset\")?;\n        let stride = usize::try_from(buffer_data.stride).context(\"negative buffer stride\")?;\n\n        // This should have already been validated by wl_shm, and a pool can\n        // only grow, but check again just in case.\n        let end = stride\n            .checked_mul(height.saturating_sub(1))\n            .and_then(|len| len.checked_add(row_len))\n            .and_then(|len| len.checked_add(offset));\n        ensure!(\n            stride >= row_len && end.is_some_and(|end| end <= pool_len),\n            \"buffer does not fit in its shm pool\"\n        );\n\n        let mut texture =\n            create_texture(renderer, size, fourcc).context(\"error creating texture\")?;\n        let mut target = renderer\n            .bind(&mut texture)\n            .context(\"error binding texture\")?;\n\n        let _res = damage_tracker\n            .render_output_with_states(\n                renderer,\n                &mut target,\n                0,\n                elements,\n                Color32F::TRANSPARENT,\n                states,","sourceCodeStart":317,"sourceCodeEnd":353,"githubUrl":"https://github.com/niri-wm/niri/blob/9e72e4917ca31baf4010496bf7f4aaf78d34d236/src/render_helpers/mod.rs#L317-L353","documentation":"render_to_shm re-validates that the source wl_shm buffer's declared layout (offset, stride, height, row length) actually fits within the pool's mapped length. Although wl_shm protocol should have rejected oversized buffers and pools can only grow, this is a defensive internal invariant check before creating a texture from the pool memory. It fires when the buffer's computed end address exceeds pool_len, or when stride is smaller than the per-row byte length.","triggerScenarios":"Calling render_to_shm (directly or via render_for_screencopy_internal) with an shm buffer whose offset + stride*(height-1) + row_len exceeds the pool's current size, or whose stride < row_len (row_len is derived from the fourcc format's bytes-per-pixel times width).","commonSituations":"A screencopy/compositor client attaching a wl_buffer whose wl_shm_pool was resized after buffer creation metadata was computed; buggy clients advertising a height/width/stride inconsistent with the pool they mapped; format mismatches (e.g. claiming XRGB8888, 4 bytes/px, but computing stride from a smaller bpp); race conditions where pool shrink logic (invalid per protocol) is attempted.","solutions":["Fix the client to size its wl_shm_pool to at least offset + stride * (height - 1) + width * bytes_per_pixel for the chosen fourcc format before attaching the buffer.","Verify the buffer's stride is at least width * bytes-per-pixel for the advertised format (e.g. >= width*4 for XRGB8888/ARGB8888).","Re-check that height/width/stride/offset passed into render_to_shm come from the wl_buffer, not stale or hand-computed values.","Ensure pool resize (wl_shm_pool.resize) is called before the buffer is rendered, not after metadata validation."],"exampleFix":"// before: pool too small for declared geometry\nlet pool = shm.create_pool(fd, (height - 1) * stride as i32);\nlet buffer = pool.create_buffer(offset, width, height, stride, format);\n\n// after: pool sized to full buffer extent\nlet row_len = width * 4; // bytes per pixel for the format\nlet size = offset + stride * (height - 1) + row_len;\nlet pool = shm.create_pool(fd, size as i32);\nlet buffer = pool.create_buffer(offset, width, height, stride, format);","handlingStrategy":"validation","validationCode":"// Validate before calling render_to_shm\nfn shm_buffer_fits(pool_len: usize, offset: usize, stride: usize, row_len: usize, height: usize) -> bool {\n    if stride < row_len { return false; }\n    stride\n        .checked_mul(height.saturating_sub(1))\n        .and_then(|len| len.checked_add(row_len))\n        .and_then(|len| len.checked_add(offset))\n        .is_some_and(|end| end <= pool_len)\n}\nif !shm_buffer_fits(pool_len, offset, stride, row_len, height) {\n    return Err(\"shm buffer exceeds pool size\");\n}","typeGuard":"fn fits_in_pool(end: Option<usize>, pool_len: usize) -> bool {\n    end.is_some_and(|end| end <= pool_len)\n}","tryCatchPattern":null,"preventionTips":["Always allocate the shm pool with offset + stride*(height-1) + width*bpp bytes of headroom.","Derive stride from width * bytes-per-pixel of the actual fourcc format, never from assumptions.","Resize the pool via wl_shm_pool.resize before attaching/reusing buffers, and re-validate offsets afterwards.","Never shrink a pool below the extent of any live buffer attached to it (protocol forbids shrinking)."],"tags":["shm","wayland","buffer","rendering","validation"],"backgroundTag":"value-out-of-range","analyzedSha":"9e72e4917ca31baf4010496bf7f4aaf78d34d236","analyzedAt":"2026-09-12T15:03:56.013Z","contentChangedAt":"2026-09-12T15:03:56.013Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}