fix(macos): let a scan walk past a page whose pager declines to read - #88

Merged
JeanExtreme002 merged 2 commits into
mainfrom
fix/macos-kern-memory-error
Aug 28, 2026
Merged

fix(macos): let a scan walk past a page whose pager declines to read#88
JeanExtreme002 merged 2 commits into
mainfrom
fix/macos-kern-memory-error

Conversation

@JeanExtreme002

Copy link
Copy Markdown
Owner

Why is this PR necessary, what does it do?

A scan of a live process on macOS aborts part-way through with

MachReadError: mach_vm_read_overwrite failed: (os/kern) memory error (kr=10)

throwing away the rest of the address space. The addresses found before the bad
page survive in the results table, so the app ends up in a confusing half-state:
a populated result list and an error dialog.

kr=10 is KERN_MEMORY_ERROR, and mach/kern_return.h documents it as:

During a page fault, the memory object indicated that the data could not be
returned. This failure may be temporary; future attempts to access this
same data may succeed, as defined by the memory object.

— in deliberate contrast with KERN_MEMORY_FAILURE (9) immediately above it in
that header, whose comment ends "This failure is permanent."

So by the kernel's own definition it belongs in _PAGE_GONE_KRS alongside the
three codes already there. It was simply missed — it wasn't even defined in
macos/types.py.

Nothing else needed changing. The tolerance machinery already exists and is
already wired into all three entry points (search_addresses_by_value,
search_addresses_by_pattern, search_values_by_addresses all pass
transient_error_check=_is_transient). The code was just on the wrong side of
the line _is_transient draws, so iter_search_results and
iter_pattern_results took their raise branch and killed the scan.

Why it shows up on pattern scans first

KERN_MEMORY_ERROR comes from a pager declining to produce a page, so it
turns up on file-backed read-only mappings — code segments, dylibs, the dyld
shared cache. A pattern scan deliberately ignores writeable_only (an AOB
signature normally lives in code), so it walks those; a value scan with the
app's default "writable regions only" never goes near them. Measured on a
trivial Python process:

regions total : 148
pattern scan (regex / AOB) : 135 <- ignores writeable_only
value scan (writable only) : 58 <- the app's default
read-only regions a default value scan never walks: 77

Turning "writable regions only" off reproduces it with any value type, which is
how this was confirmed rather than assumed.

Checklist (complete all items):

  • Added tests as necessary.
  • There is no breaking change for existing features.

References:

No references to be shared.

Notes:

The new test pins both sides of the classification: the four codes a scan may
walk past, and the ones that must still reach the caller (a protection failure
or the permanent KERN_MEMORY_FAILURE) so this doesn't drift into swallowing
real permission problems.

On the machine this was found on, the entire tests/memory and tests/scan
suite failed this way — 15 tests, all with kr=10. They pass now. Worth noting
that they pass legitimately: those tests plant a value or a byte marker and
assert it is found (assert address in hits), so the scans are completing and
matching, not quietly finding nothing.

Linux is unaffected — it has its own _is_transient over a separate errno set.

A scan of a live process aborted with
MachReadError: mach_vm_read_overwrite failed: (os/kern) memory error (kr=10)
part-way through, discarding the rest of the address space. kr=10 is
KERN_MEMORY_ERROR, and mach/kern_return.h documents it as
"During a page fault, the memory object indicated that the data could
not be returned. This failure may be temporary; future attempts to
access this same data may succeed"
— in deliberate contrast with KERN_MEMORY_FAILURE (9) directly above it,
whose comment ends "This failure is permanent." So by the kernel's own
definition it belongs in _PAGE_GONE_KRS beside the three codes already
there; it was simply missed, and wasn't even defined in types.py.
The tolerance machinery was already in place and already wired into all
three entry points — the code was just on the wrong side of the line
_is_transient draws, so iter_search_results and iter_pattern_results
re-raised it and killed the scan.
It surfaces on file-backed read-only mappings — code segments, dylibs,
the dyld shared cache — which is why a pattern scan hit it first: it
deliberately ignores writeable_only (an AOB signature is normally in
code), so it walks 2.3x the regions a default value scan does on a
trivial process. Turning "writable regions only" off reproduces it with
any value type.
On this machine the whole memory/scan suite failed this way; it now
passes, and those tests plant a value and assert they find it, so the
scans are completing rather than quietly matching nothing.
@github-actionsgithub-actionsBot added macOS macOS backend changes (PyMemoryEditor/macos/) lib Library changes (PyMemoryEditor/) tests Test changes (tests/) labels Aug 28, 2026
…ther
Self-review follow-ups, no behaviour change.
The comment justifying the fix leaned on KERN_MEMORY_FAILURE (9) as the
contrast case, but that constant wasn't in types.py — leaving a gap in
the sequence (2, 4, 5, 8, 10) and a comment pointing at something the
file didn't contain. The module's existing convention is to define the
codes it talks about even when only prose refers to them (KERN_FAILURE
is already there on those terms), so the two neighbours are now defined
together with the header text that separates them. The test was
declaring its own copy of 9, which is the kind of local constant that
drifts from the library it describes.
The "must still propagate" case tested KERN_SUCCESS, a state no
MachReadError can carry — MachPartialReadError is raised on success but
passes KERN_INVALID_ADDRESS. Swapped for KERN_FAILURE, which is what
task_for_pid returns without the debugger entitlement and is a real
error this must never swallow.
@JeanExtreme002
JeanExtreme002 merged commit fd5e4a2 into mainAug 28, 2026
13 checks passed
@github-actions
github-actionsBot deleted the fix/macos-kern-memory-error branch August 28, 2026 16:00
@JeanExtreme002JeanExtreme002 mentioned this pull request Aug 28, 2026
2 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

libLibrary changes (PyMemoryEditor/)macOSmacOS backend changes (PyMemoryEditor/macos/)testsTest changes (tests/)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@JeanExtreme002
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(macos): let a scan walk past a page whose pager declines to read - #88

Merged
JeanExtreme002 merged 2 commits into
mainfrom
fix/macos-kern-memory-error
Aug 28, 2026
Merged

fix(macos): let a scan walk past a page whose pager declines to read#88
JeanExtreme002 merged 2 commits into
mainfrom
fix/macos-kern-memory-error

Conversation

@JeanExtreme002

Copy link
Copy Markdown
Owner

Why is this PR necessary, what does it do?

A scan of a live process on macOS aborts part-way through with

MachReadError: mach_vm_read_overwrite failed: (os/kern) memory error (kr=10)

throwing away the rest of the address space. The addresses found before the bad
page survive in the results table, so the app ends up in a confusing half-state:
a populated result list and an error dialog.

kr=10 is KERN_MEMORY_ERROR, and mach/kern_return.h documents it as:

During a page fault, the memory object indicated that the data could not be
returned. This failure may be temporary; future attempts to access this
same data may succeed, as defined by the memory object.

— in deliberate contrast with KERN_MEMORY_FAILURE (9) immediately above it in
that header, whose comment ends "This failure is permanent."

So by the kernel's own definition it belongs in _PAGE_GONE_KRS alongside the
three codes already there. It was simply missed — it wasn't even defined in
macos/types.py.

Nothing else needed changing. The tolerance machinery already exists and is
already wired into all three entry points (search_addresses_by_value,
search_addresses_by_pattern, search_values_by_addresses all pass
transient_error_check=_is_transient). The code was just on the wrong side of
the line _is_transient draws, so iter_search_results and
iter_pattern_results took their raise branch and killed the scan.

Why it shows up on pattern scans first

KERN_MEMORY_ERROR comes from a pager declining to produce a page, so it
turns up on file-backed read-only mappings — code segments, dylibs, the dyld
shared cache. A pattern scan deliberately ignores writeable_only (an AOB
signature normally lives in code), so it walks those; a value scan with the
app's default "writable regions only" never goes near them. Measured on a
trivial Python process:

regions total : 148
pattern scan (regex / AOB) : 135 <- ignores writeable_only
value scan (writable only) : 58 <- the app's default
read-only regions a default value scan never walks: 77

Turning "writable regions only" off reproduces it with any value type, which is
how this was confirmed rather than assumed.

Checklist (complete all items):

  • Added tests as necessary.
  • There is no breaking change for existing features.

References:

No references to be shared.

Notes:

The new test pins both sides of the classification: the four codes a scan may
walk past, and the ones that must still reach the caller (a protection failure
or the permanent KERN_MEMORY_FAILURE) so this doesn't drift into swallowing
real permission problems.

On the machine this was found on, the entire tests/memory and tests/scan
suite failed this way — 15 tests, all with kr=10. They pass now. Worth noting
that they pass legitimately: those tests plant a value or a byte marker and
assert it is found (assert address in hits), so the scans are completing and
matching, not quietly finding nothing.

Linux is unaffected — it has its own _is_transient over a separate errno set.

A scan of a live process aborted with
MachReadError: mach_vm_read_overwrite failed: (os/kern) memory error (kr=10)
part-way through, discarding the rest of the address space. kr=10 is
KERN_MEMORY_ERROR, and mach/kern_return.h documents it as
"During a page fault, the memory object indicated that the data could
not be returned. This failure may be temporary; future attempts to
access this same data may succeed"
— in deliberate contrast with KERN_MEMORY_FAILURE (9) directly above it,
whose comment ends "This failure is permanent." So by the kernel's own
definition it belongs in _PAGE_GONE_KRS beside the three codes already
there; it was simply missed, and wasn't even defined in types.py.
The tolerance machinery was already in place and already wired into all
three entry points — the code was just on the wrong side of the line
_is_transient draws, so iter_search_results and iter_pattern_results
re-raised it and killed the scan.
It surfaces on file-backed read-only mappings — code segments, dylibs,
the dyld shared cache — which is why a pattern scan hit it first: it
deliberately ignores writeable_only (an AOB signature is normally in
code), so it walks 2.3x the regions a default value scan does on a
trivial process. Turning "writable regions only" off reproduces it with
any value type.
On this machine the whole memory/scan suite failed this way; it now
passes, and those tests plant a value and assert they find it, so the
scans are completing rather than quietly matching nothing.
@github-actionsgithub-actionsBot added macOS macOS backend changes (PyMemoryEditor/macos/) lib Library changes (PyMemoryEditor/) tests Test changes (tests/) labels Aug 28, 2026
…ther
Self-review follow-ups, no behaviour change.
The comment justifying the fix leaned on KERN_MEMORY_FAILURE (9) as the
contrast case, but that constant wasn't in types.py — leaving a gap in
the sequence (2, 4, 5, 8, 10) and a comment pointing at something the
file didn't contain. The module's existing convention is to define the
codes it talks about even when only prose refers to them (KERN_FAILURE
is already there on those terms), so the two neighbours are now defined
together with the header text that separates them. The test was
declaring its own copy of 9, which is the kind of local constant that
drifts from the library it describes.
The "must still propagate" case tested KERN_SUCCESS, a state no
MachReadError can carry — MachPartialReadError is raised on success but
passes KERN_INVALID_ADDRESS. Swapped for KERN_FAILURE, which is what
task_for_pid returns without the debugger entitlement and is a real
error this must never swallow.
@JeanExtreme002
JeanExtreme002 merged commit fd5e4a2 into mainAug 28, 2026
13 checks passed
@github-actions
github-actionsBot deleted the fix/macos-kern-memory-error branch August 28, 2026 16:00
@JeanExtreme002JeanExtreme002 mentioned this pull request Aug 28, 2026
2 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

libLibrary changes (PyMemoryEditor/)macOSmacOS backend changes (PyMemoryEditor/macos/)testsTest changes (tests/)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@JeanExtreme002
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(macos): let a scan walk past a page whose pager declines to read - #88

Merged
JeanExtreme002 merged 2 commits into
mainfrom
fix/macos-kern-memory-error
Aug 28, 2026
Merged

fix(macos): let a scan walk past a page whose pager declines to read#88
JeanExtreme002 merged 2 commits into
mainfrom
fix/macos-kern-memory-error

Conversation

@JeanExtreme002

Copy link
Copy Markdown
Owner

Why is this PR necessary, what does it do?

A scan of a live process on macOS aborts part-way through with

MachReadError: mach_vm_read_overwrite failed: (os/kern) memory error (kr=10)

throwing away the rest of the address space. The addresses found before the bad
page survive in the results table, so the app ends up in a confusing half-state:
a populated result list and an error dialog.

kr=10 is KERN_MEMORY_ERROR, and mach/kern_return.h documents it as:

During a page fault, the memory object indicated that the data could not be
returned. This failure may be temporary; future attempts to access this
same data may succeed, as defined by the memory object.

— in deliberate contrast with KERN_MEMORY_FAILURE (9) immediately above it in
that header, whose comment ends "This failure is permanent."

So by the kernel's own definition it belongs in _PAGE_GONE_KRS alongside the
three codes already there. It was simply missed — it wasn't even defined in
macos/types.py.

Nothing else needed changing. The tolerance machinery already exists and is
already wired into all three entry points (search_addresses_by_value,
search_addresses_by_pattern, search_values_by_addresses all pass
transient_error_check=_is_transient). The code was just on the wrong side of
the line _is_transient draws, so iter_search_results and
iter_pattern_results took their raise branch and killed the scan.

Why it shows up on pattern scans first

KERN_MEMORY_ERROR comes from a pager declining to produce a page, so it
turns up on file-backed read-only mappings — code segments, dylibs, the dyld
shared cache. A pattern scan deliberately ignores writeable_only (an AOB
signature normally lives in code), so it walks those; a value scan with the
app's default "writable regions only" never goes near them. Measured on a
trivial Python process:

regions total : 148
pattern scan (regex / AOB) : 135 <- ignores writeable_only
value scan (writable only) : 58 <- the app's default
read-only regions a default value scan never walks: 77

Turning "writable regions only" off reproduces it with any value type, which is
how this was confirmed rather than assumed.

Checklist (complete all items):

  • Added tests as necessary.
  • There is no breaking change for existing features.

References:

No references to be shared.

Notes:

The new test pins both sides of the classification: the four codes a scan may
walk past, and the ones that must still reach the caller (a protection failure
or the permanent KERN_MEMORY_FAILURE) so this doesn't drift into swallowing
real permission problems.

On the machine this was found on, the entire tests/memory and tests/scan
suite failed this way — 15 tests, all with kr=10. They pass now. Worth noting
that they pass legitimately: those tests plant a value or a byte marker and
assert it is found (assert address in hits), so the scans are completing and
matching, not quietly finding nothing.

Linux is unaffected — it has its own _is_transient over a separate errno set.

A scan of a live process aborted with
MachReadError: mach_vm_read_overwrite failed: (os/kern) memory error (kr=10)
part-way through, discarding the rest of the address space. kr=10 is
KERN_MEMORY_ERROR, and mach/kern_return.h documents it as
"During a page fault, the memory object indicated that the data could
not be returned. This failure may be temporary; future attempts to
access this same data may succeed"
— in deliberate contrast with KERN_MEMORY_FAILURE (9) directly above it,
whose comment ends "This failure is permanent." So by the kernel's own
definition it belongs in _PAGE_GONE_KRS beside the three codes already
there; it was simply missed, and wasn't even defined in types.py.
The tolerance machinery was already in place and already wired into all
three entry points — the code was just on the wrong side of the line
_is_transient draws, so iter_search_results and iter_pattern_results
re-raised it and killed the scan.
It surfaces on file-backed read-only mappings — code segments, dylibs,
the dyld shared cache — which is why a pattern scan hit it first: it
deliberately ignores writeable_only (an AOB signature is normally in
code), so it walks 2.3x the regions a default value scan does on a
trivial process. Turning "writable regions only" off reproduces it with
any value type.
On this machine the whole memory/scan suite failed this way; it now
passes, and those tests plant a value and assert they find it, so the
scans are completing rather than quietly matching nothing.
@github-actionsgithub-actionsBot added macOS macOS backend changes (PyMemoryEditor/macos/) lib Library changes (PyMemoryEditor/) tests Test changes (tests/) labels Aug 28, 2026
…ther
Self-review follow-ups, no behaviour change.
The comment justifying the fix leaned on KERN_MEMORY_FAILURE (9) as the
contrast case, but that constant wasn't in types.py — leaving a gap in
the sequence (2, 4, 5, 8, 10) and a comment pointing at something the
file didn't contain. The module's existing convention is to define the
codes it talks about even when only prose refers to them (KERN_FAILURE
is already there on those terms), so the two neighbours are now defined
together with the header text that separates them. The test was
declaring its own copy of 9, which is the kind of local constant that
drifts from the library it describes.
The "must still propagate" case tested KERN_SUCCESS, a state no
MachReadError can carry — MachPartialReadError is raised on success but
passes KERN_INVALID_ADDRESS. Swapped for KERN_FAILURE, which is what
task_for_pid returns without the debugger entitlement and is a real
error this must never swallow.
@JeanExtreme002
JeanExtreme002 merged commit fd5e4a2 into mainAug 28, 2026
13 checks passed
@github-actions
github-actionsBot deleted the fix/macos-kern-memory-error branch August 28, 2026 16:00
@JeanExtreme002JeanExtreme002 mentioned this pull request Aug 28, 2026
2 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

libLibrary changes (PyMemoryEditor/)macOSmacOS backend changes (PyMemoryEditor/macos/)testsTest changes (tests/)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@JeanExtreme002
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(macos): let a scan walk past a page whose pager declines to read - #88

Merged
JeanExtreme002 merged 2 commits into
mainfrom
fix/macos-kern-memory-error
Aug 28, 2026
Merged

fix(macos): let a scan walk past a page whose pager declines to read#88
JeanExtreme002 merged 2 commits into
mainfrom
fix/macos-kern-memory-error

Conversation

@JeanExtreme002

Copy link
Copy Markdown
Owner

Why is this PR necessary, what does it do?

A scan of a live process on macOS aborts part-way through with

MachReadError: mach_vm_read_overwrite failed: (os/kern) memory error (kr=10)

throwing away the rest of the address space. The addresses found before the bad
page survive in the results table, so the app ends up in a confusing half-state:
a populated result list and an error dialog.

kr=10 is KERN_MEMORY_ERROR, and mach/kern_return.h documents it as:

During a page fault, the memory object indicated that the data could not be
returned. This failure may be temporary; future attempts to access this
same data may succeed, as defined by the memory object.

— in deliberate contrast with KERN_MEMORY_FAILURE (9) immediately above it in
that header, whose comment ends "This failure is permanent."

So by the kernel's own definition it belongs in _PAGE_GONE_KRS alongside the
three codes already there. It was simply missed — it wasn't even defined in
macos/types.py.

Nothing else needed changing. The tolerance machinery already exists and is
already wired into all three entry points (search_addresses_by_value,
search_addresses_by_pattern, search_values_by_addresses all pass
transient_error_check=_is_transient). The code was just on the wrong side of
the line _is_transient draws, so iter_search_results and
iter_pattern_results took their raise branch and killed the scan.

Why it shows up on pattern scans first

KERN_MEMORY_ERROR comes from a pager declining to produce a page, so it
turns up on file-backed read-only mappings — code segments, dylibs, the dyld
shared cache. A pattern scan deliberately ignores writeable_only (an AOB
signature normally lives in code), so it walks those; a value scan with the
app's default "writable regions only" never goes near them. Measured on a
trivial Python process:

regions total : 148
pattern scan (regex / AOB) : 135 <- ignores writeable_only
value scan (writable only) : 58 <- the app's default
read-only regions a default value scan never walks: 77

Turning "writable regions only" off reproduces it with any value type, which is
how this was confirmed rather than assumed.

Checklist (complete all items):

  • Added tests as necessary.
  • There is no breaking change for existing features.

References:

No references to be shared.

Notes:

The new test pins both sides of the classification: the four codes a scan may
walk past, and the ones that must still reach the caller (a protection failure
or the permanent KERN_MEMORY_FAILURE) so this doesn't drift into swallowing
real permission problems.

On the machine this was found on, the entire tests/memory and tests/scan
suite failed this way — 15 tests, all with kr=10. They pass now. Worth noting
that they pass legitimately: those tests plant a value or a byte marker and
assert it is found (assert address in hits), so the scans are completing and
matching, not quietly finding nothing.

Linux is unaffected — it has its own _is_transient over a separate errno set.

A scan of a live process aborted with
MachReadError: mach_vm_read_overwrite failed: (os/kern) memory error (kr=10)
part-way through, discarding the rest of the address space. kr=10 is
KERN_MEMORY_ERROR, and mach/kern_return.h documents it as
"During a page fault, the memory object indicated that the data could
not be returned. This failure may be temporary; future attempts to
access this same data may succeed"
— in deliberate contrast with KERN_MEMORY_FAILURE (9) directly above it,
whose comment ends "This failure is permanent." So by the kernel's own
definition it belongs in _PAGE_GONE_KRS beside the three codes already
there; it was simply missed, and wasn't even defined in types.py.
The tolerance machinery was already in place and already wired into all
three entry points — the code was just on the wrong side of the line
_is_transient draws, so iter_search_results and iter_pattern_results
re-raised it and killed the scan.
It surfaces on file-backed read-only mappings — code segments, dylibs,
the dyld shared cache — which is why a pattern scan hit it first: it
deliberately ignores writeable_only (an AOB signature is normally in
code), so it walks 2.3x the regions a default value scan does on a
trivial process. Turning "writable regions only" off reproduces it with
any value type.
On this machine the whole memory/scan suite failed this way; it now
passes, and those tests plant a value and assert they find it, so the
scans are completing rather than quietly matching nothing.
@github-actionsgithub-actionsBot added macOS macOS backend changes (PyMemoryEditor/macos/) lib Library changes (PyMemoryEditor/) tests Test changes (tests/) labels Aug 28, 2026
…ther
Self-review follow-ups, no behaviour change.
The comment justifying the fix leaned on KERN_MEMORY_FAILURE (9) as the
contrast case, but that constant wasn't in types.py — leaving a gap in
the sequence (2, 4, 5, 8, 10) and a comment pointing at something the
file didn't contain. The module's existing convention is to define the
codes it talks about even when only prose refers to them (KERN_FAILURE
is already there on those terms), so the two neighbours are now defined
together with the header text that separates them. The test was
declaring its own copy of 9, which is the kind of local constant that
drifts from the library it describes.
The "must still propagate" case tested KERN_SUCCESS, a state no
MachReadError can carry — MachPartialReadError is raised on success but
passes KERN_INVALID_ADDRESS. Swapped for KERN_FAILURE, which is what
task_for_pid returns without the debugger entitlement and is a real
error this must never swallow.
@JeanExtreme002
JeanExtreme002 merged commit fd5e4a2 into mainAug 28, 2026
13 checks passed
@github-actions
github-actionsBot deleted the fix/macos-kern-memory-error branch August 28, 2026 16:00
@JeanExtreme002JeanExtreme002 mentioned this pull request Aug 28, 2026
2 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

libLibrary changes (PyMemoryEditor/)macOSmacOS backend changes (PyMemoryEditor/macos/)testsTest changes (tests/)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@JeanExtreme002
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(macos): let a scan walk past a page whose pager declines to read - #88

Merged
JeanExtreme002 merged 2 commits into
mainfrom
fix/macos-kern-memory-error
Aug 28, 2026
Merged

fix(macos): let a scan walk past a page whose pager declines to read#88
JeanExtreme002 merged 2 commits into
mainfrom
fix/macos-kern-memory-error

Conversation

@JeanExtreme002

Copy link
Copy Markdown
Owner

Why is this PR necessary, what does it do?

A scan of a live process on macOS aborts part-way through with

MachReadError: mach_vm_read_overwrite failed: (os/kern) memory error (kr=10)

throwing away the rest of the address space. The addresses found before the bad
page survive in the results table, so the app ends up in a confusing half-state:
a populated result list and an error dialog.

kr=10 is KERN_MEMORY_ERROR, and mach/kern_return.h documents it as:

During a page fault, the memory object indicated that the data could not be
returned. This failure may be temporary; future attempts to access this
same data may succeed, as defined by the memory object.

— in deliberate contrast with KERN_MEMORY_FAILURE (9) immediately above it in
that header, whose comment ends "This failure is permanent."

So by the kernel's own definition it belongs in _PAGE_GONE_KRS alongside the
three codes already there. It was simply missed — it wasn't even defined in
macos/types.py.

Nothing else needed changing. The tolerance machinery already exists and is
already wired into all three entry points (search_addresses_by_value,
search_addresses_by_pattern, search_values_by_addresses all pass
transient_error_check=_is_transient). The code was just on the wrong side of
the line _is_transient draws, so iter_search_results and
iter_pattern_results took their raise branch and killed the scan.

Why it shows up on pattern scans first

KERN_MEMORY_ERROR comes from a pager declining to produce a page, so it
turns up on file-backed read-only mappings — code segments, dylibs, the dyld
shared cache. A pattern scan deliberately ignores writeable_only (an AOB
signature normally lives in code), so it walks those; a value scan with the
app's default "writable regions only" never goes near them. Measured on a
trivial Python process:

regions total : 148
pattern scan (regex / AOB) : 135 <- ignores writeable_only
value scan (writable only) : 58 <- the app's default
read-only regions a default value scan never walks: 77

Turning "writable regions only" off reproduces it with any value type, which is
how this was confirmed rather than assumed.

Checklist (complete all items):

  • Added tests as necessary.
  • There is no breaking change for existing features.

References:

No references to be shared.

Notes:

The new test pins both sides of the classification: the four codes a scan may
walk past, and the ones that must still reach the caller (a protection failure
or the permanent KERN_MEMORY_FAILURE) so this doesn't drift into swallowing
real permission problems.

On the machine this was found on, the entire tests/memory and tests/scan
suite failed this way — 15 tests, all with kr=10. They pass now. Worth noting
that they pass legitimately: those tests plant a value or a byte marker and
assert it is found (assert address in hits), so the scans are completing and
matching, not quietly finding nothing.

Linux is unaffected — it has its own _is_transient over a separate errno set.

A scan of a live process aborted with
MachReadError: mach_vm_read_overwrite failed: (os/kern) memory error (kr=10)
part-way through, discarding the rest of the address space. kr=10 is
KERN_MEMORY_ERROR, and mach/kern_return.h documents it as
"During a page fault, the memory object indicated that the data could
not be returned. This failure may be temporary; future attempts to
access this same data may succeed"
— in deliberate contrast with KERN_MEMORY_FAILURE (9) directly above it,
whose comment ends "This failure is permanent." So by the kernel's own
definition it belongs in _PAGE_GONE_KRS beside the three codes already
there; it was simply missed, and wasn't even defined in types.py.
The tolerance machinery was already in place and already wired into all
three entry points — the code was just on the wrong side of the line
_is_transient draws, so iter_search_results and iter_pattern_results
re-raised it and killed the scan.
It surfaces on file-backed read-only mappings — code segments, dylibs,
the dyld shared cache — which is why a pattern scan hit it first: it
deliberately ignores writeable_only (an AOB signature is normally in
code), so it walks 2.3x the regions a default value scan does on a
trivial process. Turning "writable regions only" off reproduces it with
any value type.
On this machine the whole memory/scan suite failed this way; it now
passes, and those tests plant a value and assert they find it, so the
scans are completing rather than quietly matching nothing.
@github-actionsgithub-actionsBot added macOS macOS backend changes (PyMemoryEditor/macos/) lib Library changes (PyMemoryEditor/) tests Test changes (tests/) labels Aug 28, 2026
…ther
Self-review follow-ups, no behaviour change.
The comment justifying the fix leaned on KERN_MEMORY_FAILURE (9) as the
contrast case, but that constant wasn't in types.py — leaving a gap in
the sequence (2, 4, 5, 8, 10) and a comment pointing at something the
file didn't contain. The module's existing convention is to define the
codes it talks about even when only prose refers to them (KERN_FAILURE
is already there on those terms), so the two neighbours are now defined
together with the header text that separates them. The test was
declaring its own copy of 9, which is the kind of local constant that
drifts from the library it describes.
The "must still propagate" case tested KERN_SUCCESS, a state no
MachReadError can carry — MachPartialReadError is raised on success but
passes KERN_INVALID_ADDRESS. Swapped for KERN_FAILURE, which is what
task_for_pid returns without the debugger entitlement and is a real
error this must never swallow.
@JeanExtreme002
JeanExtreme002 merged commit fd5e4a2 into mainAug 28, 2026
13 checks passed
@github-actions
github-actionsBot deleted the fix/macos-kern-memory-error branch August 28, 2026 16:00
@JeanExtreme002JeanExtreme002 mentioned this pull request Aug 28, 2026
2 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

libLibrary changes (PyMemoryEditor/)macOSmacOS backend changes (PyMemoryEditor/macos/)testsTest changes (tests/)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@JeanExtreme002
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(macos): let a scan walk past a page whose pager declines to read - #88

Merged
JeanExtreme002 merged 2 commits into
mainfrom
fix/macos-kern-memory-error
Aug 28, 2026
Merged

fix(macos): let a scan walk past a page whose pager declines to read#88
JeanExtreme002 merged 2 commits into
mainfrom
fix/macos-kern-memory-error

Conversation

@JeanExtreme002

Copy link
Copy Markdown
Owner

Why is this PR necessary, what does it do?

A scan of a live process on macOS aborts part-way through with

MachReadError: mach_vm_read_overwrite failed: (os/kern) memory error (kr=10)

throwing away the rest of the address space. The addresses found before the bad
page survive in the results table, so the app ends up in a confusing half-state:
a populated result list and an error dialog.

kr=10 is KERN_MEMORY_ERROR, and mach/kern_return.h documents it as:

During a page fault, the memory object indicated that the data could not be
returned. This failure may be temporary; future attempts to access this
same data may succeed, as defined by the memory object.

— in deliberate contrast with KERN_MEMORY_FAILURE (9) immediately above it in
that header, whose comment ends "This failure is permanent."

So by the kernel's own definition it belongs in _PAGE_GONE_KRS alongside the
three codes already there. It was simply missed — it wasn't even defined in
macos/types.py.

Nothing else needed changing. The tolerance machinery already exists and is
already wired into all three entry points (search_addresses_by_value,
search_addresses_by_pattern, search_values_by_addresses all pass
transient_error_check=_is_transient). The code was just on the wrong side of
the line _is_transient draws, so iter_search_results and
iter_pattern_results took their raise branch and killed the scan.

Why it shows up on pattern scans first

KERN_MEMORY_ERROR comes from a pager declining to produce a page, so it
turns up on file-backed read-only mappings — code segments, dylibs, the dyld
shared cache. A pattern scan deliberately ignores writeable_only (an AOB
signature normally lives in code), so it walks those; a value scan with the
app's default "writable regions only" never goes near them. Measured on a
trivial Python process:

regions total : 148
pattern scan (regex / AOB) : 135 <- ignores writeable_only
value scan (writable only) : 58 <- the app's default
read-only regions a default value scan never walks: 77

Turning "writable regions only" off reproduces it with any value type, which is
how this was confirmed rather than assumed.

Checklist (complete all items):

  • Added tests as necessary.
  • There is no breaking change for existing features.

References:

No references to be shared.

Notes:

The new test pins both sides of the classification: the four codes a scan may
walk past, and the ones that must still reach the caller (a protection failure
or the permanent KERN_MEMORY_FAILURE) so this doesn't drift into swallowing
real permission problems.

On the machine this was found on, the entire tests/memory and tests/scan
suite failed this way — 15 tests, all with kr=10. They pass now. Worth noting
that they pass legitimately: those tests plant a value or a byte marker and
assert it is found (assert address in hits), so the scans are completing and
matching, not quietly finding nothing.

Linux is unaffected — it has its own _is_transient over a separate errno set.

A scan of a live process aborted with
MachReadError: mach_vm_read_overwrite failed: (os/kern) memory error (kr=10)
part-way through, discarding the rest of the address space. kr=10 is
KERN_MEMORY_ERROR, and mach/kern_return.h documents it as
"During a page fault, the memory object indicated that the data could
not be returned. This failure may be temporary; future attempts to
access this same data may succeed"
— in deliberate contrast with KERN_MEMORY_FAILURE (9) directly above it,
whose comment ends "This failure is permanent." So by the kernel's own
definition it belongs in _PAGE_GONE_KRS beside the three codes already
there; it was simply missed, and wasn't even defined in types.py.
The tolerance machinery was already in place and already wired into all
three entry points — the code was just on the wrong side of the line
_is_transient draws, so iter_search_results and iter_pattern_results
re-raised it and killed the scan.
It surfaces on file-backed read-only mappings — code segments, dylibs,
the dyld shared cache — which is why a pattern scan hit it first: it
deliberately ignores writeable_only (an AOB signature is normally in
code), so it walks 2.3x the regions a default value scan does on a
trivial process. Turning "writable regions only" off reproduces it with
any value type.
On this machine the whole memory/scan suite failed this way; it now
passes, and those tests plant a value and assert they find it, so the
scans are completing rather than quietly matching nothing.
@github-actionsgithub-actionsBot added macOS macOS backend changes (PyMemoryEditor/macos/) lib Library changes (PyMemoryEditor/) tests Test changes (tests/) labels Aug 28, 2026
…ther
Self-review follow-ups, no behaviour change.
The comment justifying the fix leaned on KERN_MEMORY_FAILURE (9) as the
contrast case, but that constant wasn't in types.py — leaving a gap in
the sequence (2, 4, 5, 8, 10) and a comment pointing at something the
file didn't contain. The module's existing convention is to define the
codes it talks about even when only prose refers to them (KERN_FAILURE
is already there on those terms), so the two neighbours are now defined
together with the header text that separates them. The test was
declaring its own copy of 9, which is the kind of local constant that
drifts from the library it describes.
The "must still propagate" case tested KERN_SUCCESS, a state no
MachReadError can carry — MachPartialReadError is raised on success but
passes KERN_INVALID_ADDRESS. Swapped for KERN_FAILURE, which is what
task_for_pid returns without the debugger entitlement and is a real
error this must never swallow.
@JeanExtreme002
JeanExtreme002 merged commit fd5e4a2 into mainAug 28, 2026
13 checks passed
@github-actions
github-actionsBot deleted the fix/macos-kern-memory-error branch August 28, 2026 16:00
@JeanExtreme002JeanExtreme002 mentioned this pull request Aug 28, 2026
2 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

libLibrary changes (PyMemoryEditor/)macOSmacOS backend changes (PyMemoryEditor/macos/)testsTest changes (tests/)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@JeanExtreme002
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(macos): let a scan walk past a page whose pager declines to read - #88

Merged
JeanExtreme002 merged 2 commits into
mainfrom
fix/macos-kern-memory-error
Aug 28, 2026
Merged

fix(macos): let a scan walk past a page whose pager declines to read#88
JeanExtreme002 merged 2 commits into
mainfrom
fix/macos-kern-memory-error

Conversation

@JeanExtreme002

Copy link
Copy Markdown
Owner

Why is this PR necessary, what does it do?

A scan of a live process on macOS aborts part-way through with

MachReadError: mach_vm_read_overwrite failed: (os/kern) memory error (kr=10)

throwing away the rest of the address space. The addresses found before the bad
page survive in the results table, so the app ends up in a confusing half-state:
a populated result list and an error dialog.

kr=10 is KERN_MEMORY_ERROR, and mach/kern_return.h documents it as:

During a page fault, the memory object indicated that the data could not be
returned. This failure may be temporary; future attempts to access this
same data may succeed, as defined by the memory object.

— in deliberate contrast with KERN_MEMORY_FAILURE (9) immediately above it in
that header, whose comment ends "This failure is permanent."

So by the kernel's own definition it belongs in _PAGE_GONE_KRS alongside the
three codes already there. It was simply missed — it wasn't even defined in
macos/types.py.

Nothing else needed changing. The tolerance machinery already exists and is
already wired into all three entry points (search_addresses_by_value,
search_addresses_by_pattern, search_values_by_addresses all pass
transient_error_check=_is_transient). The code was just on the wrong side of
the line _is_transient draws, so iter_search_results and
iter_pattern_results took their raise branch and killed the scan.

Why it shows up on pattern scans first

KERN_MEMORY_ERROR comes from a pager declining to produce a page, so it
turns up on file-backed read-only mappings — code segments, dylibs, the dyld
shared cache. A pattern scan deliberately ignores writeable_only (an AOB
signature normally lives in code), so it walks those; a value scan with the
app's default "writable regions only" never goes near them. Measured on a
trivial Python process:

regions total : 148
pattern scan (regex / AOB) : 135 <- ignores writeable_only
value scan (writable only) : 58 <- the app's default
read-only regions a default value scan never walks: 77

Turning "writable regions only" off reproduces it with any value type, which is
how this was confirmed rather than assumed.

Checklist (complete all items):

  • Added tests as necessary.
  • There is no breaking change for existing features.

References:

No references to be shared.

Notes:

The new test pins both sides of the classification: the four codes a scan may
walk past, and the ones that must still reach the caller (a protection failure
or the permanent KERN_MEMORY_FAILURE) so this doesn't drift into swallowing
real permission problems.

On the machine this was found on, the entire tests/memory and tests/scan
suite failed this way — 15 tests, all with kr=10. They pass now. Worth noting
that they pass legitimately: those tests plant a value or a byte marker and
assert it is found (assert address in hits), so the scans are completing and
matching, not quietly finding nothing.

Linux is unaffected — it has its own _is_transient over a separate errno set.

A scan of a live process aborted with
MachReadError: mach_vm_read_overwrite failed: (os/kern) memory error (kr=10)
part-way through, discarding the rest of the address space. kr=10 is
KERN_MEMORY_ERROR, and mach/kern_return.h documents it as
"During a page fault, the memory object indicated that the data could
not be returned. This failure may be temporary; future attempts to
access this same data may succeed"
— in deliberate contrast with KERN_MEMORY_FAILURE (9) directly above it,
whose comment ends "This failure is permanent." So by the kernel's own
definition it belongs in _PAGE_GONE_KRS beside the three codes already
there; it was simply missed, and wasn't even defined in types.py.
The tolerance machinery was already in place and already wired into all
three entry points — the code was just on the wrong side of the line
_is_transient draws, so iter_search_results and iter_pattern_results
re-raised it and killed the scan.
It surfaces on file-backed read-only mappings — code segments, dylibs,
the dyld shared cache — which is why a pattern scan hit it first: it
deliberately ignores writeable_only (an AOB signature is normally in
code), so it walks 2.3x the regions a default value scan does on a
trivial process. Turning "writable regions only" off reproduces it with
any value type.
On this machine the whole memory/scan suite failed this way; it now
passes, and those tests plant a value and assert they find it, so the
scans are completing rather than quietly matching nothing.
@github-actionsgithub-actionsBot added macOS macOS backend changes (PyMemoryEditor/macos/) lib Library changes (PyMemoryEditor/) tests Test changes (tests/) labels Aug 28, 2026
…ther
Self-review follow-ups, no behaviour change.
The comment justifying the fix leaned on KERN_MEMORY_FAILURE (9) as the
contrast case, but that constant wasn't in types.py — leaving a gap in
the sequence (2, 4, 5, 8, 10) and a comment pointing at something the
file didn't contain. The module's existing convention is to define the
codes it talks about even when only prose refers to them (KERN_FAILURE
is already there on those terms), so the two neighbours are now defined
together with the header text that separates them. The test was
declaring its own copy of 9, which is the kind of local constant that
drifts from the library it describes.
The "must still propagate" case tested KERN_SUCCESS, a state no
MachReadError can carry — MachPartialReadError is raised on success but
passes KERN_INVALID_ADDRESS. Swapped for KERN_FAILURE, which is what
task_for_pid returns without the debugger entitlement and is a real
error this must never swallow.
@JeanExtreme002
JeanExtreme002 merged commit fd5e4a2 into mainAug 28, 2026
13 checks passed
@github-actions
github-actionsBot deleted the fix/macos-kern-memory-error branch August 28, 2026 16:00
@JeanExtreme002JeanExtreme002 mentioned this pull request Aug 28, 2026
2 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

libLibrary changes (PyMemoryEditor/)macOSmacOS backend changes (PyMemoryEditor/macos/)testsTest changes (tests/)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@JeanExtreme002
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(macos): let a scan walk past a page whose pager declines to read - #88

Merged
JeanExtreme002 merged 2 commits into
mainfrom
fix/macos-kern-memory-error
Aug 28, 2026
Merged

fix(macos): let a scan walk past a page whose pager declines to read#88
JeanExtreme002 merged 2 commits into
mainfrom
fix/macos-kern-memory-error

Conversation

@JeanExtreme002

Copy link
Copy Markdown
Owner

Why is this PR necessary, what does it do?

A scan of a live process on macOS aborts part-way through with

MachReadError: mach_vm_read_overwrite failed: (os/kern) memory error (kr=10)

throwing away the rest of the address space. The addresses found before the bad
page survive in the results table, so the app ends up in a confusing half-state:
a populated result list and an error dialog.

kr=10 is KERN_MEMORY_ERROR, and mach/kern_return.h documents it as:

During a page fault, the memory object indicated that the data could not be
returned. This failure may be temporary; future attempts to access this
same data may succeed, as defined by the memory object.

— in deliberate contrast with KERN_MEMORY_FAILURE (9) immediately above it in
that header, whose comment ends "This failure is permanent."

So by the kernel's own definition it belongs in _PAGE_GONE_KRS alongside the
three codes already there. It was simply missed — it wasn't even defined in
macos/types.py.

Nothing else needed changing. The tolerance machinery already exists and is
already wired into all three entry points (search_addresses_by_value,
search_addresses_by_pattern, search_values_by_addresses all pass
transient_error_check=_is_transient). The code was just on the wrong side of
the line _is_transient draws, so iter_search_results and
iter_pattern_results took their raise branch and killed the scan.

Why it shows up on pattern scans first

KERN_MEMORY_ERROR comes from a pager declining to produce a page, so it
turns up on file-backed read-only mappings — code segments, dylibs, the dyld
shared cache. A pattern scan deliberately ignores writeable_only (an AOB
signature normally lives in code), so it walks those; a value scan with the
app's default "writable regions only" never goes near them. Measured on a
trivial Python process:

regions total : 148
pattern scan (regex / AOB) : 135 <- ignores writeable_only
value scan (writable only) : 58 <- the app's default
read-only regions a default value scan never walks: 77

Turning "writable regions only" off reproduces it with any value type, which is
how this was confirmed rather than assumed.

Checklist (complete all items):

  • Added tests as necessary.
  • There is no breaking change for existing features.

References:

No references to be shared.

Notes:

The new test pins both sides of the classification: the four codes a scan may
walk past, and the ones that must still reach the caller (a protection failure
or the permanent KERN_MEMORY_FAILURE) so this doesn't drift into swallowing
real permission problems.

On the machine this was found on, the entire tests/memory and tests/scan
suite failed this way — 15 tests, all with kr=10. They pass now. Worth noting
that they pass legitimately: those tests plant a value or a byte marker and
assert it is found (assert address in hits), so the scans are completing and
matching, not quietly finding nothing.

Linux is unaffected — it has its own _is_transient over a separate errno set.

A scan of a live process aborted with
MachReadError: mach_vm_read_overwrite failed: (os/kern) memory error (kr=10)
part-way through, discarding the rest of the address space. kr=10 is
KERN_MEMORY_ERROR, and mach/kern_return.h documents it as
"During a page fault, the memory object indicated that the data could
not be returned. This failure may be temporary; future attempts to
access this same data may succeed"
— in deliberate contrast with KERN_MEMORY_FAILURE (9) directly above it,
whose comment ends "This failure is permanent." So by the kernel's own
definition it belongs in _PAGE_GONE_KRS beside the three codes already
there; it was simply missed, and wasn't even defined in types.py.
The tolerance machinery was already in place and already wired into all
three entry points — the code was just on the wrong side of the line
_is_transient draws, so iter_search_results and iter_pattern_results
re-raised it and killed the scan.
It surfaces on file-backed read-only mappings — code segments, dylibs,
the dyld shared cache — which is why a pattern scan hit it first: it
deliberately ignores writeable_only (an AOB signature is normally in
code), so it walks 2.3x the regions a default value scan does on a
trivial process. Turning "writable regions only" off reproduces it with
any value type.
On this machine the whole memory/scan suite failed this way; it now
passes, and those tests plant a value and assert they find it, so the
scans are completing rather than quietly matching nothing.
@github-actionsgithub-actionsBot added macOS macOS backend changes (PyMemoryEditor/macos/) lib Library changes (PyMemoryEditor/) tests Test changes (tests/) labels Aug 28, 2026
…ther
Self-review follow-ups, no behaviour change.
The comment justifying the fix leaned on KERN_MEMORY_FAILURE (9) as the
contrast case, but that constant wasn't in types.py — leaving a gap in
the sequence (2, 4, 5, 8, 10) and a comment pointing at something the
file didn't contain. The module's existing convention is to define the
codes it talks about even when only prose refers to them (KERN_FAILURE
is already there on those terms), so the two neighbours are now defined
together with the header text that separates them. The test was
declaring its own copy of 9, which is the kind of local constant that
drifts from the library it describes.
The "must still propagate" case tested KERN_SUCCESS, a state no
MachReadError can carry — MachPartialReadError is raised on success but
passes KERN_INVALID_ADDRESS. Swapped for KERN_FAILURE, which is what
task_for_pid returns without the debugger entitlement and is a real
error this must never swallow.
@JeanExtreme002
JeanExtreme002 merged commit fd5e4a2 into mainAug 28, 2026
13 checks passed
@github-actions
github-actionsBot deleted the fix/macos-kern-memory-error branch August 28, 2026 16:00
@JeanExtreme002JeanExtreme002 mentioned this pull request Aug 28, 2026
2 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

libLibrary changes (PyMemoryEditor/)macOSmacOS backend changes (PyMemoryEditor/macos/)testsTest changes (tests/)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@JeanExtreme002