debug: Improve '%n' fortify detection (BZ 30932)

The 7bb8045ec0 path made the '%n' fortify check ignore EMFILE errors
while trying to open /proc/self/maps, and this added a security
issue where EMFILE can be attacker-controlled thus making it
ineffective for some cases.

The EMFILE failure is reinstated but with a different error
message.  Also, to improve the false positive of the hardening for
the cases where no new files can be opened, the
_dl_readonly_area now uses  _dl_find_object to check if the
memory area is within a writable ELF segment.  The procfs method is
still used as fallback.

Checked on x86_64-linux-gnu and i686-linux-gnu.
Reviewed-by: Arjun Shankar <arjun@redhat.com>
This commit is contained in:
Adhemerval Zanella 2025-03-14 16:09:57 -03:00
parent 1894e219dc
commit ed6a68bac7
15 changed files with 371 additions and 63 deletions

View File

@ -633,7 +633,7 @@ link-libc-printers-tests = $(link-libc-rpath) \
$(link-libc-tests-after-rpath-link) $(link-libc-tests-after-rpath-link)
# This is how to find at build-time things that will be installed there. # This is how to find at build-time things that will be installed there.
rpath-dirs = math elf dlfcn nss nis rt resolv mathvec support misc rpath-dirs = math elf dlfcn nss nis rt resolv mathvec support misc debug
rpath-link = \ rpath-link = \
$(common-objdir):$(subst $(empty) ,:,$(patsubst ../$(subdir),.,$(rpath-dirs:%=$(common-objpfx)%))) $(common-objdir):$(subst $(empty) ,:,$(patsubst ../$(subdir),.,$(rpath-dirs:%=$(common-objpfx)%)))
else # build-static else # build-static

View File

@ -75,6 +75,7 @@ routines = \
readlink_chk \ readlink_chk \
readlinkat_chk \ readlinkat_chk \
readonly-area \ readonly-area \
readonly-area-fallback \
realpath_chk \ realpath_chk \
recv_chk \ recv_chk \
recvfrom_chk \ recvfrom_chk \
@ -180,9 +181,15 @@ CPPFLAGS-tst-longjmp_chk3.c += $(no-fortify-source) -D_FORTIFY_SOURCE=1
CPPFLAGS-tst-realpath-chk.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2 CPPFLAGS-tst-realpath-chk.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
CPPFLAGS-tst-chk-cancel.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2 CPPFLAGS-tst-chk-cancel.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
CFLAGS-tst-sprintf-fortify-rdonly.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2 CFLAGS-tst-sprintf-fortify-rdonly.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
CFLAGS-tst-sprintf-fortify-rdonly-mod.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
CFLAGS-tst-sprintf-fortify-rdonly-dlopen.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
CFLAGS-tst-fortify-syslog.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2 CFLAGS-tst-fortify-syslog.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
CFLAGS-tst-fortify-wide.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2 CFLAGS-tst-fortify-wide.c += $(no-fortify-source) -D_FORTIFY_SOURCE=2
$(objpfx)tst-sprintf-fortify-rdonly: \
$(objpfx)tst-sprintf-fortify-rdonly-mod.so \
$(objpfx)tst-sprintf-fortify-rdonly-dlopen.so
# _FORTIFY_SOURCE tests. # _FORTIFY_SOURCE tests.
# Auto-generate tests for _FORTIFY_SOURCE for different levels, compilers and # Auto-generate tests for _FORTIFY_SOURCE for different levels, compilers and
# preprocessor conditions based on tst-fortify.c. # preprocessor conditions based on tst-fortify.c.
@ -303,6 +310,11 @@ tests-container += \
tst-fortify-syslog \ tst-fortify-syslog \
# tests-container # tests-container
modules-names += \
tst-sprintf-fortify-rdonly-dlopen \
tst-sprintf-fortify-rdonly-mod \
# modules-names
ifeq ($(have-ssp),yes) ifeq ($(have-ssp),yes)
tests += tst-ssp-1 tests += tst-ssp-1
endif endif

View File

@ -16,18 +16,19 @@
<https://www.gnu.org/licenses/>. */ <https://www.gnu.org/licenses/>. */
#include <stdlib.h> #include <stdlib.h>
#include <ldsodefs.h>
/* Return 1 if the whole area PTR .. PTR+SIZE is not writable. enum readonly_error_type
Return -1 if it is writable. */
int
__readonly_area (const void *ptr, size_t size) __readonly_area (const void *ptr, size_t size)
{ {
/* We cannot determine in general whether memory is writable or not. switch (GLRO(dl_readonly_area (ptr, size)))
This must be handled in a system-dependent manner. to not {
unconditionally break code we need to return here a positive case dl_readonly_area_rdonly:
answer. This disables this security measure but that is the return readonly_noerror;
price people have to pay for using systems without a real case dl_readonly_area_writable:
implementation of this interface. */ return readonly_area_writable;
return 1; default:
break;
}
return __readonly_area_fallback (ptr, size);
} }

View File

@ -0,0 +1 @@
#include "tst-sprintf-fortify-rdonly-mod.c"

View File

@ -0,0 +1,56 @@
/* Testcase for BZ 30932.
Copyright (C) 2025 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
static const char *str2 = "F";
static char writeable_format[10] = "%s";
static char relro_format[10] __attribute__ ((section (".data.rel.ro"))) =
"%s%n%s%n";
void
init_writable (void)
{
strcpy (writeable_format + 2, "%n%s%n");
}
int
sprintf_writable (int *n1, int *n2)
{
char buf[128];
return sprintf (buf, writeable_format, str2, n1, str2, n2);
}
int
sprintf_relro (int *n1, int *n2)
{
char buf[128];
return sprintf (buf, relro_format, str2, n1, str2, n2);
}
int
sprintf_writable_malloc (int *n1, int *n2)
{
char buf[128];
char *buf2_malloc = strdup (writeable_format);
if (buf2_malloc == NULL)
abort ();
return sprintf (buf, buf2_malloc, str2, n1, str2, n2);
}

View File

@ -27,16 +27,64 @@
#include <support/check.h> #include <support/check.h>
#include <support/support.h> #include <support/support.h>
#include <support/temp_file.h> #include <support/temp_file.h>
#include <support/xdlfcn.h>
jmp_buf chk_fail_buf; static sigjmp_buf chk_fail_buf;
bool chk_fail_ok; static volatile int ret;
static bool chk_fail_ok;
const char *str2 = "F"; static void
char buf2[10] = "%s"; handler (int sig)
{
if (chk_fail_ok)
{
chk_fail_ok = false;
longjmp (chk_fail_buf, 1);
}
else
_exit (127);
}
#define FORTIFY_FAIL \
do { printf ("Failure on line %d\n", __LINE__); ret = 1; } while (0)
#define CHK_FAIL_START \
chk_fail_ok = true; \
if (! sigsetjmp (chk_fail_buf, 1)) \
{
#define CHK_FAIL_END \
chk_fail_ok = false; \
FORTIFY_FAIL; \
}
static const char *str2 = "F";
static char writeable_format[10] = "%s";
static char relro_format[10] __attribute__ ((section (".data.rel.ro"))) =
"%s%n%s%n";
extern void init_writable (void);
extern int sprintf_writable (int *, int *);
extern int sprintf_relro (int *, int *);
extern int sprintf_writable_malloc (int *, int *);
#define str(__x) # __x
void (*init_writable_dlopen)(void);
int (*sprintf_writable_dlopen)(int *, int *);
int (*sprintf_rdonly_dlopen)(int *, int *);
int (*sprintf_writable_malloc_dlopen)(int *, int *);
static int static int
do_test (void) do_test (void)
{ {
set_fortify_handler (handler);
{
void *h = xdlopen ("tst-sprintf-fortify-rdonly-dlopen.so", RTLD_NOW);
init_writable_dlopen = xdlsym (h, str(init_writable));
sprintf_writable_dlopen = xdlsym (h, str(sprintf_writable));
sprintf_rdonly_dlopen = xdlsym (h, str(sprintf_relro));
sprintf_writable_malloc_dlopen = xdlsym (h, str(sprintf_writable_malloc));
}
struct rlimit rl; struct rlimit rl;
int max_fd = 24; int max_fd = 24;
@ -63,20 +111,94 @@ do_test (void)
} }
TEST_VERIFY_EXIT (nfiles != 0); TEST_VERIFY_EXIT (nfiles != 0);
/* When the format string is writable and contains %n, strcpy (writeable_format + 2, "%n%s%n");
with -D_FORTIFY_SOURCE=2 it causes __chk_fail. However, if libc can not init_writable ();
open procfs to check if the input format string in within a writable init_writable_dlopen ();
memory segment, the fortify version can not perform the check. */
char buf[128];
int n1;
int n2;
strcpy (buf2 + 2, "%n%s%n"); /* writeable_format is at a writable part of .bss segment, so libc should be
if (sprintf (buf, buf2, str2, &n1, str2, &n2) != 2 able to check it without resorting to procfs. */
|| n1 != 1 || n2 != 2) {
FAIL_EXIT1 ("sprintf failed: %s %d %d", buf, n1, n2); char buf[128];
int n1;
int n2;
CHK_FAIL_START
sprintf (buf, writeable_format, str2, &n1, str2, &n2);
CHK_FAIL_END
}
return 0; /* Same as before, but from an library. */
{
int n1;
int n2;
CHK_FAIL_START
sprintf_writable (&n1, &n2);
CHK_FAIL_END
}
{
int n1;
int n2;
CHK_FAIL_START
sprintf_writable_dlopen (&n1, &n2);
CHK_FAIL_END
}
/* relro_format is at a readonly part of .bss segment, so '%n' in format input
should not trigger a fortify failure. */
{
char buf[128];
int n1;
int n2;
if (sprintf (buf, relro_format, str2, &n1, str2, &n2) != 2
|| n1 != 1 || n2 != 2)
FAIL_EXIT1 ("sprintf failed: %s %d %d", buf, n1, n2);
}
/* Same as before, but from an library. */
{
int n1;
int n2;
if (sprintf_relro (&n1, &n2) != 2 || n1 != 1 || n2 != 2)
FAIL_EXIT1 ("sprintf failed: %d %d", n1, n2);
}
{
int n1;
int n2;
if (sprintf_rdonly_dlopen (&n1, &n2) != 2 || n1 != 1 || n2 != 2)
FAIL_EXIT1 ("sprintf failed: %d %d", n1, n2);
}
/* However if the format string is placed on a writable memory not covered
by ELF segments, libc needs to resort to procfs. */
{
char buf[128];
int n1;
int n2;
char *buf2_malloc = xstrdup (writeable_format);
CHK_FAIL_START
sprintf (buf, buf2_malloc, str2, &n1, str2, &n2);
CHK_FAIL_END
}
/* Same as before, but from an library. */
{
int n1;
int n2;
CHK_FAIL_START
sprintf_writable_malloc (&n1, &n2);
CHK_FAIL_END
}
{
int n1;
int n2;
CHK_FAIL_START
sprintf_writable_malloc_dlopen (&n1, &n2);
CHK_FAIL_END
}
return ret;
} }
#include <support/test-driver.c> #include <support/test-driver.c>

View File

@ -72,6 +72,7 @@ dl-routines = \
dl-open \ dl-open \
dl-origin \ dl-origin \
dl-printf \ dl-printf \
dl-readonly-area \
dl-reloc \ dl-reloc \
dl-runtime \ dl-runtime \
dl-scope \ dl-scope \

86
elf/dl-readonly-area.c Normal file
View File

@ -0,0 +1,86 @@
/* Check if range is within a read-only from a loaded ELF object.
Copyright (C) 2025 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
#include <ldsodefs.h>
static bool
check_relro (const struct link_map *l, uintptr_t start, uintptr_t end)
{
if (l->l_relro_addr != 0)
{
uintptr_t relro_start = ALIGN_DOWN (l->l_addr + l->l_relro_addr,
GLRO(dl_pagesize));
uintptr_t relro_end = ALIGN_DOWN (l->l_addr + l->l_relro_addr
+ l->l_relro_size,
GLRO(dl_pagesize));
/* RELRO is caved out from a RW segment, so the next range is either
RW or nonexistent. */
return relro_start <= start && end <= relro_end
? dl_readonly_area_rdonly : dl_readonly_area_writable;
}
return dl_readonly_area_writable;
}
enum dl_readonly_area_error_type
_dl_readonly_area (const void *ptr, size_t size)
{
struct dl_find_object dlfo;
if (_dl_find_object ((void *)ptr, &dlfo) != 0)
return dl_readonly_area_not_found;
const struct link_map *l = dlfo.dlfo_link_map;
uintptr_t ptr_start = (uintptr_t) ptr;
uintptr_t ptr_end = ptr_start + size;
for (const ElfW(Phdr) *ph = l->l_phdr; ph < &l->l_phdr[l->l_phnum]; ++ph)
if (ph->p_type == PT_LOAD)
{
/* For segments with alignment larger than the page size,
_dl_map_segment allocates additional space that is mark as
PROT_NONE (so we can ignore). */
uintptr_t from = l->l_addr
+ ALIGN_DOWN (ph->p_vaddr, GLRO(dl_pagesize));
uintptr_t to = l->l_addr
+ ALIGN_UP (ph->p_vaddr + ph->p_filesz, GLRO(dl_pagesize));
/* Found an entry that at least partially covers the area. */
if (from < ptr_end && to > ptr_start)
{
if (ph->p_flags & PF_W)
return check_relro (l, ptr_start, ptr_end);
if ((ph->p_flags & PF_R) == 0)
return dl_readonly_area_writable;
if (from <= ptr_start && to >= ptr_end)
return dl_readonly_area_rdonly;
else if (from <= ptr_start)
size -= to - ptr_start;
else if (to >= ptr_end)
size -= ptr_end - from;
else
size -= to - from;
if (size == 0)
break;
}
}
return size == 0 ? dl_readonly_area_rdonly : dl_readonly_area_not_found;
}

View File

@ -371,6 +371,7 @@ struct rtld_global_ro _rtld_global_ro attribute_relro =
._dl_error_free = _dl_error_free, ._dl_error_free = _dl_error_free,
._dl_tls_get_addr_soft = _dl_tls_get_addr_soft, ._dl_tls_get_addr_soft = _dl_tls_get_addr_soft,
._dl_libc_freeres = __rtld_libc_freeres, ._dl_libc_freeres = __rtld_libc_freeres,
._dl_readonly_area = _dl_readonly_area,
}; };
/* If we would use strong_alias here the compiler would see a /* If we would use strong_alias here the compiler would see a
non-hidden definition. This would undo the effect of the previous non-hidden definition. This would undo the effect of the previous

View File

@ -368,6 +368,21 @@ struct abort_msg_s
extern struct abort_msg_s *__abort_msg; extern struct abort_msg_s *__abort_msg;
libc_hidden_proto (__abort_msg) libc_hidden_proto (__abort_msg)
enum readonly_error_type
{
readonly_noerror,
readonly_area_writable,
readonly_procfs_inaccessible,
readonly_procfs_open_fail,
};
extern enum readonly_error_type __readonly_area (const void *ptr,
size_t size)
attribute_hidden;
extern enum readonly_error_type __readonly_area_fallback (const void *ptr,
size_t size)
attribute_hidden;
# if IS_IN (rtld) # if IS_IN (rtld)
extern __typeof (unsetenv) unsetenv attribute_hidden; extern __typeof (unsetenv) unsetenv attribute_hidden;
extern __typeof (__strtoul_internal) __strtoul_internal attribute_hidden; extern __typeof (__strtoul_internal) __strtoul_internal attribute_hidden;

View File

@ -576,7 +576,8 @@ static const uint8_t jump_table[] =
/* Handle positional format specifiers. */ /* Handle positional format specifiers. */
static void printf_positional (struct Xprintf_buffer *buf, static void printf_positional (struct Xprintf_buffer *buf,
const CHAR_T *format, int readonly_format, const CHAR_T *format,
enum readonly_error_type readonly_format,
va_list ap, va_list *ap_savep, va_list ap, va_list *ap_savep,
int nspecs_done, const UCHAR_T *lead_str_end, int nspecs_done, const UCHAR_T *lead_str_end,
CHAR_T *work_buffer, int save_errno, CHAR_T *work_buffer, int save_errno,
@ -626,9 +627,7 @@ Xprintf_buffer (struct Xprintf_buffer *buf, const CHAR_T *format,
/* For the %m format we may need the current `errno' value. */ /* For the %m format we may need the current `errno' value. */
int save_errno = errno; int save_errno = errno;
/* 1 if format is in read-only memory, -1 if it is in writable memory, enum readonly_error_type readonly_format = readonly_noerror;
0 if unknown. */
int readonly_format = 0;
/* Initialize local variables. */ /* Initialize local variables. */
grouping = (const char *) -1; grouping = (const char *) -1;
@ -1045,7 +1044,7 @@ do_positional:
static void static void
printf_positional (struct Xprintf_buffer * buf, const CHAR_T *format, printf_positional (struct Xprintf_buffer * buf, const CHAR_T *format,
int readonly_format, enum readonly_error_type readonly_format,
va_list ap, va_list *ap_savep, int nspecs_done, va_list ap, va_list *ap_savep, int nspecs_done,
const UCHAR_T *lead_str_end, const UCHAR_T *lead_str_end,
CHAR_T *work_buffer, int save_errno, CHAR_T *work_buffer, int save_errno,

View File

@ -324,16 +324,24 @@ LABEL (form_pointer):
LABEL (form_number): LABEL (form_number):
if ((mode_flags & PRINTF_FORTIFY) != 0) if ((mode_flags & PRINTF_FORTIFY) != 0)
{ {
if (! readonly_format) if (readonly_format == readonly_noerror)
{ readonly_format = __readonly_area (format, ((STR_LEN (format) + 1)
extern int __readonly_area (const void *, size_t) * sizeof (CHAR_T)));
attribute_hidden; switch (readonly_format)
readonly_format {
= __readonly_area (format, ((STR_LEN (format) + 1) case readonly_area_writable:
* sizeof (CHAR_T))); __libc_fatal ("*** %n in writable segments detected ***\n");
} /* The format is not within ELF segments and opening /proc/self/maps
if (readonly_format < 0) failed because there are too many files. */
__libc_fatal ("*** %n in writable segment detected ***\n"); case readonly_procfs_open_fail:
__libc_fatal ("*** procfs could not open ***\n");
/* The /proc/self/maps can not be opened either because it is not
available or the process does not have the right permission. Since
it should not be attacker-controlled we can avoid failure. */
case readonly_procfs_inaccessible:
case readonly_noerror:
break;
}
} }
/* Answer the count of characters written. */ /* Answer the count of characters written. */
void *ptrptr = process_arg_pointer (); void *ptrptr = process_arg_pointer ();

View File

@ -276,6 +276,12 @@ struct audit_ifaces
struct audit_ifaces *next; struct audit_ifaces *next;
}; };
enum dl_readonly_area_error_type
{
dl_readonly_area_rdonly,
dl_readonly_area_writable,
dl_readonly_area_not_found,
};
/* Test whether given NAME matches any of the names of the given object. */ /* Test whether given NAME matches any of the names of the given object. */
extern int _dl_name_match_p (const char *__name, const struct link_map *__map) extern int _dl_name_match_p (const char *__name, const struct link_map *__map)
@ -676,6 +682,10 @@ struct rtld_global_ro
dlopen. */ dlopen. */
int (*_dl_find_object) (void *, struct dl_find_object *); int (*_dl_find_object) (void *, struct dl_find_object *);
/* Implementation of _dl_readonly_area, used in fortify routines to check
if memory area is within a read-only ELF segment. */
enum dl_readonly_area_error_type (*_dl_readonly_area) (const void *, size_t);
/* Dynamic linker operations used after static dlopen. */ /* Dynamic linker operations used after static dlopen. */
const struct dlfcn_hook *_dl_dlfcn_hook; const struct dlfcn_hook *_dl_dlfcn_hook;
@ -1284,6 +1294,10 @@ extern void _dl_show_scope (struct link_map *new, int from)
extern struct link_map *_dl_find_dso_for_object (const ElfW(Addr) addr); extern struct link_map *_dl_find_dso_for_object (const ElfW(Addr) addr);
rtld_hidden_proto (_dl_find_dso_for_object) rtld_hidden_proto (_dl_find_dso_for_object)
extern enum dl_readonly_area_error_type _dl_readonly_area (const void *ptr,
size_t size)
attribute_hidden;
/* Initialization which is normally done by the dynamic linker. */ /* Initialization which is normally done by the dynamic linker. */
extern void _dl_non_dynamic_init (void) extern void _dl_non_dynamic_init (void)
attribute_hidden; attribute_hidden;

View File

@ -20,11 +20,8 @@
#include <stdint.h> #include <stdint.h>
#include <mach.h> #include <mach.h>
/* Return 1 if the whole area PTR .. PTR+SIZE is not writable. enum readonly_error_type
Return -1 if it is writable. */ __readonly_area_fallback (const void *ptr, size_t size)
int
__readonly_area (const char *ptr, size_t size)
{ {
vm_address_t region_address = (uintptr_t) ptr; vm_address_t region_address = (uintptr_t) ptr;
vm_size_t region_length = size; vm_size_t region_length = size;
@ -46,11 +43,11 @@ __readonly_area (const char *ptr, size_t size)
continue; continue;
if (protection & VM_PROT_WRITE) if (protection & VM_PROT_WRITE)
return -1; return readonly_area_writable;
if (region_address - (uintptr_t) ptr >= size) if (region_address - (uintptr_t) ptr >= size)
break; break;
} }
return 1; return readonly_noerror;
} }

View File

@ -23,11 +23,8 @@
#include <string.h> #include <string.h>
#include "libio/libioP.h" #include "libio/libioP.h"
/* Return 1 if the whole area PTR .. PTR+SIZE is not writable. enum readonly_error_type
Return -1 if it is writable. */ __readonly_area_fallback (const void *ptr, size_t size)
int
__readonly_area (const char *ptr, size_t size)
{ {
const void *ptr_end = ptr + size; const void *ptr_end = ptr + size;
@ -42,11 +39,11 @@ __readonly_area (const char *ptr, size_t size)
to the /proc filesystem if it is set[ug]id. There has to the /proc filesystem if it is set[ug]id. There has
been no willingness to change this in the kernel so been no willingness to change this in the kernel so
far. */ far. */
|| errno == EACCES || errno == EACCES)
/* Process has reached the maximum number of open files. */ return readonly_procfs_inaccessible;
|| errno == EMFILE) /* Process has reached the maximum number of open files or another
return 1; unusual error. */
return -1; return readonly_procfs_open_fail;
} }
/* We need no locking. */ /* We need no locking. */
@ -98,7 +95,5 @@ __readonly_area (const char *ptr, size_t size)
fclose (fp); fclose (fp);
free (line); free (line);
/* If the whole area between ptr and ptr_end is covered by read-only return size == 0 ? readonly_noerror : readonly_area_writable;
VMAs, return 1. Otherwise return -1. */
return size == 0 ? 1 : -1;
} }