|
| 1 | +# Copyright 2025 The Bazel Authors. All rights reserved. |
| 2 | +# |
| 3 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +# you may not use this file except in compliance with the License. |
| 5 | +# You may obtain a copy of the License at |
| 6 | +# |
| 7 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +# |
| 9 | +# Unless required by applicable law or agreed to in writing, software |
| 10 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +# See the License for the specific language governing permissions and |
| 13 | +# limitations under the License. |
| 14 | + |
| 15 | +"""Enhanced security feature support methods.""" |
| 16 | + |
| 17 | +visibility([ |
| 18 | + "//apple/internal/...", |
| 19 | +]) |
| 20 | + |
| 21 | +# TODO: b/449684779 - Stand up a solution for allowing arm64e as a target arch in a transition when |
| 22 | +# building for devices. Simulators don't have adequate support yet (FB20484613), so consider |
| 23 | +# fail-ing or warning until that's resolved. |
| 24 | + |
| 25 | +# The name of the secure feature that's required for opting into any set of enhanced security |
| 26 | +# features on Xcode 26.0 or later. |
| 27 | +# |
| 28 | +# TODO: b/449684779 - Use this for a mandatory check for the Xcode 26 opt-in feature, since that |
| 29 | +# should always be set if any entitlements are required. |
| 30 | +_REQUIRED_XCODE_26_OPT_IN = "apple.xcode_26_minimum_opt_in" |
| 31 | + |
| 32 | +# A map of all of the secure features that requires crosstool support and the entitlements that they |
| 33 | +# enable. If a secure feature does not enable any entitlements, it should be mapped to an empty |
| 34 | +# object. |
| 35 | +_ENTITLEMENTS_FROM_SECURE_FEATURES = { |
| 36 | + # A subset of "secure features" will not be mapped to any crosstool features, but they do still |
| 37 | + # provide required entitlements for Xcode 26 and later. These are prefixed with "apple." to |
| 38 | + # separate them from the crosstool namespace. |
| 39 | + "apple.additional_runtime_platform_restrictions": { |
| 40 | + "com.apple.security.hardened-process.platform-restrictions": True, |
| 41 | + }, |
| 42 | + "apple.read_only_platform_memory": { |
| 43 | + "com.apple.security.hardened-process.dyld-ro": True, |
| 44 | + }, |
| 45 | + "c_bounds_safety": {}, |
| 46 | + "c_typed_allocator_support": { |
| 47 | + "com.apple.security.hardened-process.hardened-heap": True, |
| 48 | + }, |
| 49 | + "cpp_bounds_safe_buffers": {}, |
| 50 | + "cpp_typed_allocator_support": { |
| 51 | + "com.apple.security.hardened-process.hardened-heap": True, |
| 52 | + }, |
| 53 | + "libcxx_hardened_mode": {}, |
| 54 | + "pointer_authentication": {}, |
| 55 | + "security_compiler_warnings": {}, |
| 56 | + "trivial_auto_var_init": {}, |
| 57 | + "typed_allocator_support": { |
| 58 | + "com.apple.security.hardened-process.hardened-heap": True, |
| 59 | + }, |
| 60 | + "warn_unsafe_buffer_usage": {}, |
| 61 | + _REQUIRED_XCODE_26_OPT_IN: { |
| 62 | + "com.apple.security.hardened-process": True, |
| 63 | + "com.apple.security.hardened-process.enhanced-security-version": 1, |
| 64 | + }, |
| 65 | +} |
| 66 | + |
| 67 | +# All of the possible values for `--features` reserved for Apple Enhanced Security. |
| 68 | +_SUPPORTED_SECURE_FEATURES = set(list(_ENTITLEMENTS_FROM_SECURE_FEATURES.keys())) |
| 69 | + |
| 70 | +_NONE_TYPE = type(None) |
| 71 | + |
| 72 | +def _crosstool_features_from_secure_features(*, features, secure_features): |
| 73 | + # If this rule does not allow for enhanced security features to be specified as an attribute, |
| 74 | + # which is interpreted as "secure_features" being exactly "None", return the features as-is, |
| 75 | + # allowing any secure features that might be in "features" to remain as-is. |
| 76 | + if type(secure_features) == _NONE_TYPE: |
| 77 | + return features |
| 78 | + |
| 79 | + requested_secure_features = set(secure_features) |
| 80 | + |
| 81 | + # Check that all of the requested secure features are supported. |
| 82 | + unsupported_secure_features = requested_secure_features - _SUPPORTED_SECURE_FEATURES |
| 83 | + if unsupported_secure_features: |
| 84 | + fail(""" |
| 85 | +Unsupported secure_features requested: |
| 86 | +{unsupported_features} |
| 87 | +
|
| 88 | +Please remove these from this target's "secure_features" attribute. |
| 89 | +
|
| 90 | +The full list of supported secure_features is: |
| 91 | +{all_supported_features} |
| 92 | + """.format( |
| 93 | + unsupported_features = str(list(unsupported_secure_features)), |
| 94 | + all_supported_features = str(list(_SUPPORTED_SECURE_FEATURES)), |
| 95 | + )) |
| 96 | + |
| 97 | + # Start building the set of features to build with, starting from the set of features already |
| 98 | + # requested by the user. |
| 99 | + requested_features = set(features) |
| 100 | + |
| 101 | + # TODO: b/449684779 - See if we can do anything to account for `-` prefixed features here before |
| 102 | + # the entitlements check because those are supposed to be features disabled by the bazel |
| 103 | + # invocation. One approach is to consider fail(...)-ing if any `-` prefixed features are |
| 104 | + # requested with `secure_features`, because that incoming configuration on the bazel invocation |
| 105 | + # would invalidate what is requested from the target definition. |
| 106 | + |
| 107 | + # Remove any secure features from "features" that were not explicitly requested at the top level |
| 108 | + # from "secure_features". This is what prevents a command line --features or raw features on the |
| 109 | + # rule or package from applying to top level targets that can have "secure_features" specified. |
| 110 | + secure_features_not_requested = _SUPPORTED_SECURE_FEATURES - requested_secure_features |
| 111 | + requested_features -= secure_features_not_requested |
| 112 | + |
| 113 | + # Amend the list of requested features to include the secure_features within the list of |
| 114 | + # features to build with, if they're not already present. Since we deal with sets, this is a |
| 115 | + # no-op if they're already present. |
| 116 | + requested_features |= requested_secure_features |
| 117 | + |
| 118 | + # If we don't need to make any changes, return the features as-is. |
| 119 | + if requested_features == set(features): |
| 120 | + return features |
| 121 | + |
| 122 | + # Return the full, sorted list of requested crosstool-relevant features. |
| 123 | + return sorted(list(requested_features)) |
| 124 | + |
| 125 | +def _entitlements_from_secure_features(*, secure_features, xcode_version): |
| 126 | + if not secure_features: |
| 127 | + return [] |
| 128 | + |
| 129 | + # Check that we're building with Xcode 26.0 or later. If not, return an empty list to signal |
| 130 | + # that no entitlements are supported or needed for this build. |
| 131 | + if not xcode_version >= apple_common.dotted_version("26.0"): |
| 132 | + return [] |
| 133 | + |
| 134 | + # TODO: b/449684779 - Check via cc_common.is_enabled(...) for each of the crosstool features to |
| 135 | + # see if they're set via the build configuration, assuming that they might be set outside of the |
| 136 | + # configuration as seen by the transition. As I understand it, this should be able to determine |
| 137 | + # if the features are actually enabled for the build, or if there was an effort made to |
| 138 | + # explicitly disable them, which can't be fully determined at transition time. |
| 139 | + |
| 140 | + # Build a set of all of the entitlements that are required by the requested secure features. |
| 141 | + required_entitlements = dict() |
| 142 | + for feature in secure_features: |
| 143 | + required_entitlements |= _ENTITLEMENTS_FROM_SECURE_FEATURES[feature] |
| 144 | + |
| 145 | + # TODO: b/449684779 - Add a mandatory check for the Xcode 26 opt-in feature, since that should |
| 146 | + # always be set if any entitlements are required. |
| 147 | + |
| 148 | + return required_entitlements |
| 149 | + |
| 150 | +secure_features_support = struct( |
| 151 | + crosstool_features_from_secure_features = _crosstool_features_from_secure_features, |
| 152 | + entitlements_from_secure_features = _entitlements_from_secure_features, |
| 153 | +) |
0 commit comments