blob: d0643ef2b292b13023ea9b12ff4fd1adf3eb9ace [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/socket/socket_apple.h"
#if defined(WORK_AROUND_CRBUG_40064248)
#include <stdint.h>
#include <sys/syscall.h>
#include "build/build_config.h"
namespace net {
namespace {
// A 2-integer struct to give access to the secondary return value, normally
// hidden, that the kernel sets for every system call return.
struct ReturnPair {
ssize_t primary; // x0, rax
uintptr_t secondary; // x1, rdx
};
// A declaration of `sendto` with a ReturnPair return value in place of ssize_t.
// asm("_sendto") is like an alias: it means that calls to `sendto_returnpair`
// will actually emit calls to `sendto`.
extern "C" ReturnPair sendto_returnpair(int,
void const*,
size_t,
int,
sockaddr const*,
socklen_t) asm("_sendto");
} // namespace
ssize_t SendtoAndDetectBogusReturnValue(int const fd,
void const* const buffer,
size_t const size,
int const flags,
sockaddr const* const address,
socklen_t const address_size) {
// TODO(mark): In the future, when a version of macOS with a fix for
// FB19384824 is published, limit this workaround at run-time to only function
// on OS versions older than the OS version that contains the fix (such as via
// base::mac::MacOSVersion and base::ios::IsRunningOnOrLater). On newer OS
// versions, call `sendto` directly.
ReturnPair const rp =
sendto_returnpair(fd, buffer, size, flags, address, address_size);
#if defined(ARCH_CPU_ARM64)
uintptr_t const param_shared_with_secondary =
reinterpret_cast<uintptr_t>(buffer);
constexpr ssize_t kSuspiciousRv_x86_64 = 0;
#elif defined(ARCH_CPU_X86_64)
uintptr_t const param_shared_with_secondary = size;
constexpr ssize_t kSuspiciousRv_x86_64 = (2 << 24) | SYS_sendto; // 0x2000085
#endif // ARCH_CPU_*
// When the bug occurs, the apparent (primary) return value will not be
// negative, so check rp.primary first.
//
// For a successful return when the bug hasn’t occurred, rp.secondary will be
// set to 0. If rp.secondary is not 0 on a successful return, the bug has
// definitely occurred.
//
// It’s possible that rp.secondary will be 0 on a successful return even when
// the bug has occurred, if the register shared with rp.secondary,
// param_shared_with_secondary, contained 0 on system call entry. In that
// case, `size` must be 0. (arm64: param_shared_with_secondary is `buffer`,
// and the use of a null pointer here can only be tolerated if `size` is 0;
// x86_64: param_shared_with_secondary is `size` itself.) The bug can’t occur
// for a meaningless 0-byte TCP send but it can occur for a meaningful 0-byte
// UDP send. Since `size` is 0, the bug’s occurrence can be detected by
// comparing rp.primary to the known suspicious return value.
//
// The suspicious return value is `fd` on arm64 kernels and
// kSuspiciousRv_x86_64 on x86_64 kernels. Since x86_64 user code can run atop
// an arm64 kernel via Rosetta binary translation, check rp.primary against
// `fd` regardless of architecture.
//
// Since the suspicious return value is `fd` on arm64 kernels, it’s not
// possible to detect the bug’s occurrence with a 0-byte UDP send to file
// descriptor 0. But this isn’t expected to ever occur practically, as 0 is
// STDIN_FILENO, and this code isn’t likely to operate on standard streams,
// and is even less likely to attempt to send to an input stream.
if (rp.primary != -1 &&
(rp.secondary != 0 ||
(param_shared_with_secondary == 0 &&
((rp.primary == fd && fd != 0) ||
(kSuspiciousRv_x86_64 != 0 && rp.primary == kSuspiciousRv_x86_64))))) {
return kSendBogusReturnValueDetected;
}
return rp.primary;
}
} // namespace net
#endif // WORK_AROUND_CRBUG_40064248