diff options
-rw-r--r-- | libs/binder/Android.bp | 4 | ||||
-rw-r--r-- | libs/binder/include/binder/SafeInterface.h | 621 | ||||
-rw-r--r-- | libs/binder/tests/Android.bp | 24 | ||||
-rw-r--r-- | libs/binder/tests/binderSafeInterfaceTest.cpp | 514 |
4 files changed, 1163 insertions, 0 deletions
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp index 93b8684ec4..b225128618 100644 --- a/libs/binder/Android.bp +++ b/libs/binder/Android.bp @@ -71,6 +71,10 @@ cc_library { "libutils", ], + export_include_dirs: [ + "include", + ], + clang: true, sanitize: { misc_undefined: ["integer"], diff --git a/libs/binder/include/binder/SafeInterface.h b/libs/binder/include/binder/SafeInterface.h new file mode 100644 index 0000000000..0e723c59ee --- /dev/null +++ b/libs/binder/include/binder/SafeInterface.h @@ -0,0 +1,621 @@ +/* + * Copyright 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <binder/IInterface.h> +#include <binder/Parcel.h> +#include <cutils/compiler.h> + +// Set to 1 to enable CallStacks when logging errors +#define SI_DUMP_CALLSTACKS 0 +#if SI_DUMP_CALLSTACKS +#include <utils/CallStack.h> +#endif + +#include <functional> +#include <type_traits> + +namespace android { +namespace SafeInterface { + +// ParcelHandler is responsible for writing/reading various types to/from a Parcel in a generic way +class ParcelHandler { +public: + explicit ParcelHandler(const char* logTag) : mLogTag(logTag) {} + + // Specializations for types with dedicated handling in Parcel + status_t read(const Parcel& parcel, bool* b) const { + return callParcel("readBool", [&]() { return parcel.readBool(b); }); + } + status_t write(Parcel* parcel, bool b) const { + return callParcel("writeBool", [&]() { return parcel->writeBool(b); }); + } + template <typename T> + typename std::enable_if<std::is_base_of<LightFlattenable<T>, T>::value, status_t>::type read( + const Parcel& parcel, T* t) const { + return callParcel("read(LightFlattenable)", [&]() { return parcel.read(*t); }); + } + template <typename T> + typename std::enable_if<std::is_base_of<LightFlattenable<T>, T>::value, status_t>::type write( + Parcel* parcel, const T& t) const { + return callParcel("write(LightFlattenable)", [&]() { return parcel->write(t); }); + } + template <typename T> + typename std::enable_if<std::is_base_of<Parcelable, T>::value, status_t>::type read( + const Parcel& parcel, T* t) const { + return callParcel("readParcelable", [&]() { return parcel.readParcelable(t); }); + } + template <typename T> + typename std::enable_if<std::is_base_of<Parcelable, T>::value, status_t>::type write( + Parcel* parcel, const T& t) const { + return callParcel("writeParcelable", [&]() { return parcel->writeParcelable(t); }); + } + status_t read(const Parcel& parcel, String8* str) const { + return callParcel("readString8", [&]() { return parcel.readString8(str); }); + } + status_t write(Parcel* parcel, const String8& str) const { + return callParcel("writeString8", [&]() { return parcel->writeString8(str); }); + } + template <typename T> + status_t read(const Parcel& parcel, sp<T>* pointer) const { + return callParcel("readNullableStrongBinder", + [&]() { return parcel.readNullableStrongBinder(pointer); }); + } + template <typename T> + typename std::enable_if<std::is_same<IBinder, T>::value, status_t>::type write( + Parcel* parcel, const sp<T>& pointer) const { + return callParcel("writeStrongBinder", + [&]() { return parcel->writeStrongBinder(pointer); }); + } + template <typename T> + typename std::enable_if<std::is_base_of<IInterface, T>::value, status_t>::type write( + Parcel* parcel, const sp<T>& interface) const { + return write(parcel, IInterface::asBinder(interface)); + } + + // Templates to handle integral types. We use a struct template to require that the called + // function exactly matches the signedness and size of the argument (e.g., the argument isn't + // silently widened). + template <bool isSigned, size_t size, typename I> + struct HandleInt; + template <typename I> + struct HandleInt<true, 4, I> { + static status_t read(const ParcelHandler& handler, const Parcel& parcel, I* i) { + return handler.callParcel("readInt32", [&]() { return parcel.readInt32(i); }); + } + static status_t write(const ParcelHandler& handler, Parcel* parcel, I i) { + return handler.callParcel("writeInt32", [&]() { return parcel->writeInt32(i); }); + } + }; + template <typename I> + struct HandleInt<false, 4, I> { + static status_t read(const ParcelHandler& handler, const Parcel& parcel, I* i) { + return handler.callParcel("readUint32", [&]() { return parcel.readUint32(i); }); + } + static status_t write(const ParcelHandler& handler, Parcel* parcel, I i) { + return handler.callParcel("writeUint32", [&]() { return parcel->writeUint32(i); }); + } + }; + template <typename I> + typename std::enable_if<std::is_integral<I>::value, status_t>::type read(const Parcel& parcel, + I* i) const { + return HandleInt<std::is_signed<I>::value, sizeof(I), I>::read(*this, parcel, i); + } + template <typename I> + typename std::enable_if<std::is_integral<I>::value, status_t>::type write(Parcel* parcel, + I i) const { + return HandleInt<std::is_signed<I>::value, sizeof(I), I>::write(*this, parcel, i); + } + +private: + const char* const mLogTag; + + // Helper to encapsulate error handling while calling the various Parcel methods + template <typename Function> + status_t callParcel(const char* name, Function f) const { + status_t error = f(); + if (CC_UNLIKELY(error != NO_ERROR)) { + ALOG(LOG_ERROR, mLogTag, "Failed to %s, (%d: %s)", name, error, strerror(-error)); +#if SI_DUMP_CALLSTACKS + CallStack callStack(mLogTag); +#endif + } + return error; + } +}; + +// Utility struct template which allows us to retrieve the types of the parameters of a member +// function pointer +template <typename T> +struct ParamExtractor; +template <typename Class, typename Return, typename... Params> +struct ParamExtractor<Return (Class::*)(Params...)> { + using ParamTuple = std::tuple<Params...>; +}; +template <typename Class, typename Return, typename... Params> +struct ParamExtractor<Return (Class::*)(Params...) const> { + using ParamTuple = std::tuple<Params...>; +}; + +} // namespace SafeInterface + +template <typename Interface> +class SafeBpInterface : public BpInterface<Interface> { +protected: + SafeBpInterface(const sp<IBinder>& impl, const char* logTag) + : BpInterface<Interface>(impl), mLogTag(logTag) {} + ~SafeBpInterface() override = default; + + // callRemote is used to invoke a synchronous procedure call over Binder + template <typename Method, typename TagType, typename... Args> + status_t callRemote(TagType tag, Args&&... args) const { + static_assert(sizeof(TagType) <= sizeof(uint32_t), "Tag must fit inside uint32_t"); + + // Verify that the arguments are compatible with the parameters + using ParamTuple = typename SafeInterface::ParamExtractor<Method>::ParamTuple; + static_assert(ArgsMatchParams<std::tuple<Args...>, ParamTuple>::value, + "Invalid argument type"); + + // Write the input arguments to the data Parcel + Parcel data; + data.writeInterfaceToken(this->getInterfaceDescriptor()); + + status_t error = writeInputs(&data, std::forward<Args>(args)...); + if (CC_UNLIKELY(error != NO_ERROR)) { + // A message will have been logged by writeInputs + return error; + } + + // Send the data Parcel to the remote and retrieve the reply parcel + Parcel reply; + error = this->remote()->transact(static_cast<uint32_t>(tag), data, &reply); + if (CC_UNLIKELY(error != NO_ERROR)) { + ALOG(LOG_ERROR, mLogTag, "Failed to transact (%d)", error); +#if SI_DUMP_CALLSTACKS + CallStack callStack(mLogTag); +#endif + return error; + } + + // Read the outputs from the reply Parcel into the output arguments + error = readOutputs(reply, std::forward<Args>(args)...); + if (CC_UNLIKELY(error != NO_ERROR)) { + // A message will have been logged by readOutputs + return error; + } + + // Retrieve the result code from the reply Parcel + status_t result = NO_ERROR; + error = reply.readInt32(&result); + if (CC_UNLIKELY(error != NO_ERROR)) { + ALOG(LOG_ERROR, mLogTag, "Failed to obtain result"); +#if SI_DUMP_CALLSTACKS + CallStack callStack(mLogTag); +#endif + return error; + } + return result; + } + + // callRemoteAsync is used to invoke an asynchronous procedure call over Binder + template <typename Method, typename TagType, typename... Args> + void callRemoteAsync(TagType tag, Args&&... args) const { + static_assert(sizeof(TagType) <= sizeof(uint32_t), "Tag must fit inside uint32_t"); + + // Verify that the arguments are compatible with the parameters + using ParamTuple = typename SafeInterface::ParamExtractor<Method>::ParamTuple; + static_assert(ArgsMatchParams<std::tuple<Args...>, ParamTuple>::value, + "Invalid argument type"); + + // Write the input arguments to the data Parcel + Parcel data; + data.writeInterfaceToken(this->getInterfaceDescriptor()); + status_t error = writeInputs(&data, std::forward<Args>(args)...); + if (CC_UNLIKELY(error != NO_ERROR)) { + // A message will have been logged by writeInputs + return; + } + + // There will be no data in the reply Parcel since the call is one-way + Parcel reply; + error = this->remote()->transact(static_cast<uint32_t>(tag), data, &reply, + IBinder::FLAG_ONEWAY); + if (CC_UNLIKELY(error != NO_ERROR)) { + ALOG(LOG_ERROR, mLogTag, "Failed to transact (%d)", error); +#if SI_DUMP_CALLSTACKS + CallStack callStack(mLogTag); +#endif + } + } + +private: + const char* const mLogTag; + + // This struct provides information on whether the decayed types of the elements at Index in the + // tuple types T and U (that is, the types after stripping cv-qualifiers, removing references, + // and a few other less common operations) are the same + template <size_t Index, typename T, typename U> + struct DecayedElementsMatch { + private: + using FirstT = typename std::tuple_element<Index, T>::type; + using DecayedT = typename std::decay<FirstT>::type; + using FirstU = typename std::tuple_element<Index, U>::type; + using DecayedU = typename std::decay<FirstU>::type; + + public: + static constexpr bool value = std::is_same<DecayedT, DecayedU>::value; + }; + + // When comparing whether the argument types match the parameter types, we first decay them (see + // DecayedElementsMatch) to avoid falsely flagging, say, T&& against T even though they are + // equivalent enough for our purposes + template <typename T, typename U> + struct ArgsMatchParams {}; + template <typename... Args, typename... Params> + struct ArgsMatchParams<std::tuple<Args...>, std::tuple<Params...>> { + static_assert(sizeof...(Args) <= sizeof...(Params), "Too many arguments"); + static_assert(sizeof...(Args) >= sizeof...(Params), "Not enough arguments"); + + private: + template <size_t Index> + static constexpr typename std::enable_if<(Index < sizeof...(Args)), bool>::type + elementsMatch() { + if (!DecayedElementsMatch<Index, std::tuple<Args...>, std::tuple<Params...>>::value) { + return false; + } + return elementsMatch<Index + 1>(); + } + template <size_t Index> + static constexpr typename std::enable_if<(Index >= sizeof...(Args)), bool>::type + elementsMatch() { + return true; + } + + public: + static constexpr bool value = elementsMatch<0>(); + }; + + // Since we assume that pointer arguments are outputs, we can use this template struct to + // determine whether or not a given argument is fundamentally a pointer type and thus an output + template <typename T> + struct IsPointerIfDecayed { + private: + using Decayed = typename std::decay<T>::type; + + public: + static constexpr bool value = std::is_pointer<Decayed>::value; + }; + + template <typename T> + typename std::enable_if<!IsPointerIfDecayed<T>::value, status_t>::type writeIfInput( + Parcel* data, T&& t) const { + return SafeInterface::ParcelHandler{mLogTag}.write(data, std::forward<T>(t)); + } + template <typename T> + typename std::enable_if<IsPointerIfDecayed<T>::value, status_t>::type writeIfInput( + Parcel* /*data*/, T&& /*t*/) const { + return NO_ERROR; + } + + // This method iterates through all of the arguments, writing them to the data Parcel if they + // are an input (i.e., if they are not a pointer type) + template <typename T, typename... Remaining> + status_t writeInputs(Parcel* data, T&& t, Remaining&&... remaining) const { + status_t error = writeIfInput(data, std::forward<T>(t)); + if (CC_UNLIKELY(error != NO_ERROR)) { + // A message will have been logged by writeIfInput + return error; + } + return writeInputs(data, std::forward<Remaining>(remaining)...); + } + static status_t writeInputs(Parcel* /*data*/) { return NO_ERROR; } + + template <typename T> + typename std::enable_if<IsPointerIfDecayed<T>::value, status_t>::type readIfOutput( + const Parcel& reply, T&& t) const { + return SafeInterface::ParcelHandler{mLogTag}.read(reply, std::forward<T>(t)); + } + template <typename T> + static typename std::enable_if<!IsPointerIfDecayed<T>::value, status_t>::type readIfOutput( + const Parcel& /*reply*/, T&& /*t*/) { + return NO_ERROR; + } + + // Similar to writeInputs except that it reads output arguments from the reply Parcel + template <typename T, typename... Remaining> + status_t readOutputs(const Parcel& reply, T&& t, Remaining&&... remaining) const { + status_t error = readIfOutput(reply, std::forward<T>(t)); + if (CC_UNLIKELY(error != NO_ERROR)) { + // A message will have been logged by readIfOutput + return error; + } + return readOutputs(reply, std::forward<Remaining>(remaining)...); + } + static status_t readOutputs(const Parcel& /*data*/) { return NO_ERROR; } +}; + +template <typename Interface> +class SafeBnInterface : public BnInterface<Interface> { +public: + explicit SafeBnInterface(const char* logTag) : mLogTag(logTag) {} + +protected: + template <typename Method> + status_t callLocal(const Parcel& data, Parcel* reply, Method method) { + CHECK_INTERFACE(this, data, reply); + + // Since we need to both pass inputs into the call as well as retrieve outputs, we create a + // "raw" tuple, where the inputs are interleaved with actual, non-pointer versions of the + // outputs. When we ultimately call into the method, we will pass the addresses of the + // output arguments instead of their tuple members directly, but the storage will live in + // the tuple. + using ParamTuple = typename SafeInterface::ParamExtractor<Method>::ParamTuple; + typename RawConverter<std::tuple<>, ParamTuple>::type rawArgs{}; + + // Read the inputs from the data Parcel into the argument tuple + status_t error = InputReader<ParamTuple>{mLogTag}.readInputs(data, &rawArgs); + if (CC_UNLIKELY(error != NO_ERROR)) { + // A message will have been logged by read + return error; + } + + // Call the local method + status_t result = MethodCaller<ParamTuple>::call(this, method, &rawArgs); + + // Extract the outputs from the argument tuple and write them into the reply Parcel + error = OutputWriter<ParamTuple>{mLogTag}.writeOutputs(reply, &rawArgs); + if (CC_UNLIKELY(error != NO_ERROR)) { + // A message will have been logged by write + return error; + } + + // Return the result code in the reply Parcel + error = reply->writeInt32(result); + if (CC_UNLIKELY(error != NO_ERROR)) { + ALOG(LOG_ERROR, mLogTag, "Failed to write result"); +#if SI_DUMP_CALLSTACKS + CallStack callStack(mLogTag); +#endif + return error; + } + return NO_ERROR; + } + + template <typename Method> + status_t callLocalAsync(const Parcel& data, Parcel* /*reply*/, Method method) { + // reply is not actually used by CHECK_INTERFACE + CHECK_INTERFACE(this, data, reply); + + // Since we need to both pass inputs into the call as well as retrieve outputs, we create a + // "raw" tuple, where the inputs are interleaved with actual, non-pointer versions of the + // outputs. When we ultimately call into the method, we will pass the addresses of the + // output arguments instead of their tuple members directly, but the storage will live in + // the tuple. + using ParamTuple = typename SafeInterface::ParamExtractor<Method>::ParamTuple; + typename RawConverter<std::tuple<>, ParamTuple>::type rawArgs{}; + + // Read the inputs from the data Parcel into the argument tuple + status_t error = InputReader<ParamTuple>{mLogTag}.readInputs(data, &rawArgs); + if (CC_UNLIKELY(error != NO_ERROR)) { + // A message will have been logged by read + return error; + } + + // Call the local method + MethodCaller<ParamTuple>::callVoid(this, method, &rawArgs); + + // After calling, there is nothing more to do since asynchronous calls do not return a value + // to the caller + return NO_ERROR; + } + +private: + const char* const mLogTag; + + // RemoveFirst strips the first element from a tuple. + // For example, given T = std::tuple<A, B, C>, RemoveFirst<T>::type = std::tuple<B, C> + template <typename T, typename... Args> + struct RemoveFirst; + template <typename T, typename... Args> + struct RemoveFirst<std::tuple<T, Args...>> { + using type = std::tuple<Args...>; + }; + + // RawConverter strips a tuple down to its fundamental types, discarding both pointers and + // references. This allows us to allocate storage for both input (non-pointer) arguments and + // output (pointer) arguments in one tuple. + // For example, given T = std::tuple<const A&, B*>, RawConverter<T>::type = std::tuple<A, B> + template <typename Unconverted, typename... Converted> + struct RawConverter; + template <typename Unconverted, typename... Converted> + struct RawConverter<std::tuple<Converted...>, Unconverted> { + private: + using ElementType = typename std::tuple_element<0, Unconverted>::type; + using Decayed = typename std::decay<ElementType>::type; + using WithoutPointer = typename std::remove_pointer<Decayed>::type; + + public: + using type = typename RawConverter<std::tuple<Converted..., WithoutPointer>, + typename RemoveFirst<Unconverted>::type>::type; + }; + template <typename... Converted> + struct RawConverter<std::tuple<Converted...>, std::tuple<>> { + using type = std::tuple<Converted...>; + }; + + // This provides a simple way to determine whether the indexed element of Args... is a pointer + template <size_t I, typename... Args> + struct ElementIsPointer { + private: + using ElementType = typename std::tuple_element<I, std::tuple<Args...>>::type; + + public: + static constexpr bool value = std::is_pointer<ElementType>::value; + }; + + // This class iterates over the parameter types, and if a given parameter is an input + // (i.e., is not a pointer), reads the corresponding argument tuple element from the data Parcel + template <typename... Params> + class InputReader; + template <typename... Params> + class InputReader<std::tuple<Params...>> { + public: + explicit InputReader(const char* logTag) : mLogTag(logTag) {} + + // Note that in this case (as opposed to in SafeBpInterface), we iterate using an explicit + // index (starting with 0 here) instead of using recursion and stripping the first element. + // This is because in SafeBpInterface we aren't actually operating on a real tuple, but are + // instead just using a tuple as a convenient container for variadic types, whereas here we + // can't modify the argument tuple without causing unnecessary copies or moves of the data + // contained therein. + template <typename RawTuple> + status_t readInputs(const Parcel& data, RawTuple* args) { + return dispatchArg<0>(data, args); + } + + private: + const char* const mLogTag; + + template <std::size_t I, typename RawTuple> + typename std::enable_if<!ElementIsPointer<I, Params...>::value, status_t>::type readIfInput( + const Parcel& data, RawTuple* args) { + return SafeInterface::ParcelHandler{mLogTag}.read(data, &std::get<I>(*args)); + } + template <std::size_t I, typename RawTuple> + typename std::enable_if<ElementIsPointer<I, Params...>::value, status_t>::type readIfInput( + const Parcel& /*data*/, RawTuple* /*args*/) { + return NO_ERROR; + } + + // Recursively iterate through the arguments + template <std::size_t I, typename RawTuple> + typename std::enable_if<(I < sizeof...(Params)), status_t>::type dispatchArg( + const Parcel& data, RawTuple* args) { + status_t error = readIfInput<I>(data, args); + if (CC_UNLIKELY(error != NO_ERROR)) { + // A message will have been logged in read + return error; + } + return dispatchArg<I + 1>(data, args); + } + template <std::size_t I, typename RawTuple> + typename std::enable_if<(I >= sizeof...(Params)), status_t>::type dispatchArg( + const Parcel& /*data*/, RawTuple* /*args*/) { + return NO_ERROR; + } + }; + + // getForCall uses the types of the parameters to determine whether a given element of the + // argument tuple is an input, which should be passed directly into the call, or an output, for + // which its address should be passed into the call + template <size_t I, typename RawTuple, typename... Params> + static typename std::enable_if< + ElementIsPointer<I, Params...>::value, + typename std::tuple_element<I, std::tuple<Params...>>::type>::type + getForCall(RawTuple* args) { + return &std::get<I>(*args); + } + template <size_t I, typename RawTuple, typename... Params> + static typename std::enable_if< + !ElementIsPointer<I, Params...>::value, + typename std::tuple_element<I, std::tuple<Params...>>::type>::type& + getForCall(RawTuple* args) { + return std::get<I>(*args); + } + + // This template class uses std::index_sequence and parameter pack expansion to call the given + // method using the elements of the argument tuple (after those arguments are passed through + // getForCall to get addresses instead of values for output arguments) + template <typename... Params> + struct MethodCaller; + template <typename... Params> + struct MethodCaller<std::tuple<Params...>> { + public: + // The calls through these to the helper methods are necessary to generate the + // std::index_sequences used to unpack the argument tuple into the method call + template <typename Class, typename MemberFunction, typename RawTuple> + static status_t call(Class* instance, MemberFunction function, RawTuple* args) { + return callHelper(instance, function, args, std::index_sequence_for<Params...>{}); + } + template <typename Class, typename MemberFunction, typename RawTuple> + static void callVoid(Class* instance, MemberFunction function, RawTuple* args) { + callVoidHelper(instance, function, args, std::index_sequence_for<Params...>{}); + } + + private: + template <typename Class, typename MemberFunction, typename RawTuple, std::size_t... I> + static status_t callHelper(Class* instance, MemberFunction function, RawTuple* args, + std::index_sequence<I...> /*unused*/) { + return (instance->*function)(getForCall<I, RawTuple, Params...>(args)...); + } + template <typename Class, typename MemberFunction, typename RawTuple, std::size_t... I> + static void callVoidHelper(Class* instance, MemberFunction function, RawTuple* args, + std::index_sequence<I...> /*unused*/) { + (instance->*function)(getForCall<I, RawTuple, Params...>(args)...); + } + }; + + // This class iterates over the parameter types, and if a given parameter is an output + // (i.e., is a pointer), writes the corresponding argument tuple element into the reply Parcel + template <typename... Params> + struct OutputWriter; + template <typename... Params> + struct OutputWriter<std::tuple<Params...>> { + public: + explicit OutputWriter(const char* logTag) : mLogTag(logTag) {} + + // See the note on InputReader::readInputs for why this differs from the arguably simpler + // RemoveFirst approach in SafeBpInterface + template <typename RawTuple> + status_t writeOutputs(Parcel* reply, RawTuple* args) { + return dispatchArg<0>(reply, args); + } + + private: + const char* const mLogTag; + + template <std::size_t I, typename RawTuple> + typename std::enable_if<ElementIsPointer<I, Params...>::value, status_t>::type + writeIfOutput(Parcel* reply, RawTuple* args) { + return SafeInterface::ParcelHandler{mLogTag}.write(reply, std::get<I>(*args)); + } + template <std::size_t I, typename RawTuple> + typename std::enable_if<!ElementIsPointer<I, Params...>::value, status_t>::type + writeIfOutput(Parcel* /*reply*/, RawTuple* /*args*/) { + return NO_ERROR; + } + + // Recursively iterate through the arguments + template <std::size_t I, typename RawTuple> + typename std::enable_if<(I < sizeof...(Params)), status_t>::type dispatchArg( + Parcel* reply, RawTuple* args) { + status_t error = writeIfOutput<I>(reply, args); + if (CC_UNLIKELY(error != NO_ERROR)) { + // A message will have been logged in read + return error; + } + return dispatchArg<I + 1>(reply, args); + } + template <std::size_t I, typename RawTuple> + typename std::enable_if<(I >= sizeof...(Params)), status_t>::type dispatchArg( + Parcel* /*reply*/, RawTuple* /*args*/) { + return NO_ERROR; + } + }; +}; + +} // namespace android diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp index 327ecad0cd..1ee4b6f741 100644 --- a/libs/binder/tests/Android.bp +++ b/libs/binder/tests/Android.bp @@ -80,3 +80,27 @@ cc_test { "libbase", ], } + +cc_test { + name: "binderSafeInterfaceTest", + srcs: ["binderSafeInterfaceTest.cpp"], + + cppflags: [ + "-Werror", + "-Weverything", + "-Wno-c++98-compat", + "-Wno-c++98-compat-pedantic", + "-Wno-global-constructors", + "-Wno-padded", + "-Wno-weak-vtables", + ], + + cpp_std: "experimental", + gnu_extensions: false, + + shared_libs: [ + "libbinder", + "liblog", + "libutils", + ], +} diff --git a/libs/binder/tests/binderSafeInterfaceTest.cpp b/libs/binder/tests/binderSafeInterfaceTest.cpp new file mode 100644 index 0000000000..ac2f4d5a59 --- /dev/null +++ b/libs/binder/tests/binderSafeInterfaceTest.cpp @@ -0,0 +1,514 @@ +/* + * Copyright 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <binder/SafeInterface.h> + +#include <binder/IInterface.h> +#include <binder/IPCThreadState.h> +#include <binder/IServiceManager.h> +#include <binder/Parcel.h> +#include <binder/Parcelable.h> +#include <binder/ProcessState.h> + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#include <gtest/gtest.h> +#pragma clang diagnostic pop + +#include <optional> + +using namespace std::chrono_literals; // NOLINT - google-build-using-namespace + +namespace android { +namespace tests { + +// This class serves two purposes: +// 1) It ensures that the implementation doesn't require copying or moving the data (for +// efficiency purposes) +// 2) It tests that Parcelables can be passed correctly +class NoCopyNoMove : public Parcelable { +public: + NoCopyNoMove() = default; + explicit NoCopyNoMove(int32_t value) : mValue(value) {} + ~NoCopyNoMove() override = default; + + // Not copyable + NoCopyNoMove(const NoCopyNoMove&) = delete; + NoCopyNoMove& operator=(const NoCopyNoMove&) = delete; + + // Not movable + NoCopyNoMove(NoCopyNoMove&&) = delete; + NoCopyNoMove& operator=(NoCopyNoMove&&) = delete; + + // Parcelable interface + status_t writeToParcel(Parcel* parcel) const override { return parcel->writeInt32(mValue); } + status_t readFromParcel(const Parcel* parcel) override { return parcel->readInt32(&mValue); } + + int32_t getValue() const { return mValue; } + void setValue(int32_t value) { mValue = value; } + +private: + int32_t mValue = 0; + uint8_t mPadding[4] = {}; // Avoids a warning from -Wpadded +}; + +struct TestLightFlattenable : LightFlattenablePod<TestLightFlattenable> { + TestLightFlattenable() = default; + explicit TestLightFlattenable(int32_t v) : value(v) {} + int32_t value = 0; +}; + +class ExitOnDeath : public IBinder::DeathRecipient { +public: + ~ExitOnDeath() override = default; + + void binderDied(const wp<IBinder>& /*who*/) override { + ALOG(LOG_INFO, "ExitOnDeath", "Exiting"); + exit(0); + } +}; + +// This callback class is used to test both one-way transactions and that sp<IInterface> can be +// passed correctly +class ICallback : public IInterface { +public: + DECLARE_META_INTERFACE(Callback) + + enum class Tag : uint32_t { + OnCallback = IBinder::FIRST_CALL_TRANSACTION, + Last, + }; + + virtual void onCallback(int32_t aPlusOne) = 0; +}; + +class BpCallback : public SafeBpInterface<ICallback> { +public: + explicit BpCallback(const sp<IBinder>& impl) : SafeBpInterface<ICallback>(impl, getLogTag()) {} + + void onCallback(int32_t aPlusOne) override { + ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__); + return callRemoteAsync<decltype(&ICallback::onCallback)>(Tag::OnCallback, aPlusOne); + } + +private: + static constexpr const char* getLogTag() { return "BpCallback"; } +}; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wexit-time-destructors" +IMPLEMENT_META_INTERFACE(Callback, "android.gfx.tests.ICallback"); +#pragma clang diagnostic pop + +class BnCallback : public SafeBnInterface<ICallback> { +public: + BnCallback() : SafeBnInterface("BnCallback") {} + + status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, + uint32_t /*flags*/) override { + EXPECT_GE(code, IBinder::FIRST_CALL_TRANSACTION); + EXPECT_LT(code, static_cast<uint32_t>(ICallback::Tag::Last)); + ICallback::Tag tag = static_cast<ICallback::Tag>(code); + switch (tag) { + case ICallback::Tag::OnCallback: { + return callLocalAsync(data, reply, &ICallback::onCallback); + } + case ICallback::Tag::Last: + // Should not be possible because of the asserts at the beginning of the method + [&]() { FAIL(); }(); + return UNKNOWN_ERROR; + } + } +}; + +class ISafeInterfaceTest : public IInterface { +public: + DECLARE_META_INTERFACE(SafeInterfaceTest) + + enum class Tag : uint32_t { + SetDeathToken = IBinder::FIRST_CALL_TRANSACTION, + ReturnsNoMemory, + LogicalNot, + IncrementLightFlattenable, + IncrementNoCopyNoMove, + ToUpper, + CallMeBack, + IncrementInt32, + IncrementUint32, + IncrementTwo, + Last, + }; + + // This is primarily so that the remote service dies when the test does, but it also serves to + // test the handling of sp<IBinder> and non-const methods + virtual status_t setDeathToken(const sp<IBinder>& token) = 0; + + // This is the most basic test since it doesn't require parceling any arguments + virtual status_t returnsNoMemory() const = 0; + + // These are ordered according to their corresponding methods in SafeInterface::ParcelHandler + virtual status_t logicalNot(bool a, bool* notA) const = 0; + virtual status_t increment(const TestLightFlattenable& a, + TestLightFlattenable* aPlusOne) const = 0; + virtual status_t increment(const NoCopyNoMove& a, NoCopyNoMove* aPlusOne) const = 0; + virtual status_t toUpper(const String8& str, String8* upperStr) const = 0; + // As mentioned above, sp<IBinder> is already tested by setDeathToken + virtual void callMeBack(const sp<ICallback>& callback, int32_t a) const = 0; + virtual status_t increment(int32_t a, int32_t* aPlusOne) const = 0; + virtual status_t increment(uint32_t a, uint32_t* aPlusOne) const = 0; + + // This tests that input/output parameter interleaving works correctly + virtual status_t increment(int32_t a, int32_t* aPlusOne, int32_t b, + int32_t* bPlusOne) const = 0; +}; + +class BpSafeInterfaceTest : public SafeBpInterface<ISafeInterfaceTest> { +public: + explicit BpSafeInterfaceTest(const sp<IBinder>& impl) + : SafeBpInterface<ISafeInterfaceTest>(impl, getLogTag()) {} + + status_t setDeathToken(const sp<IBinder>& token) override { + ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__); + return callRemote<decltype(&ISafeInterfaceTest::setDeathToken)>(Tag::SetDeathToken, token); + } + status_t returnsNoMemory() const override { + ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__); + return callRemote<decltype(&ISafeInterfaceTest::returnsNoMemory)>(Tag::ReturnsNoMemory); + } + status_t logicalNot(bool a, bool* notA) const override { + ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__); + return callRemote<decltype(&ISafeInterfaceTest::logicalNot)>(Tag::LogicalNot, a, notA); + } + status_t increment(const TestLightFlattenable& a, + TestLightFlattenable* aPlusOne) const override { + using Signature = status_t (ISafeInterfaceTest::*)(const TestLightFlattenable&, + TestLightFlattenable*) const; + ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__); + return callRemote<Signature>(Tag::IncrementLightFlattenable, a, aPlusOne); + } + status_t increment(const NoCopyNoMove& a, NoCopyNoMove* aPlusOne) const override { + ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__); + using Signature = status_t (ISafeInterfaceTest::*)(const NoCopyNoMove& a, + NoCopyNoMove* aPlusOne) const; + return callRemote<Signature>(Tag::IncrementNoCopyNoMove, a, aPlusOne); + } + status_t toUpper(const String8& str, String8* upperStr) const override { + ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__); + return callRemote<decltype(&ISafeInterfaceTest::toUpper)>(Tag::ToUpper, str, upperStr); + } + void callMeBack(const sp<ICallback>& callback, int32_t a) const override { + ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__); + return callRemoteAsync<decltype(&ISafeInterfaceTest::callMeBack)>(Tag::CallMeBack, callback, + a); + } + status_t increment(int32_t a, int32_t* aPlusOne) const override { + ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__); + using Signature = status_t (ISafeInterfaceTest::*)(int32_t, int32_t*) const; + return callRemote<Signature>(Tag::IncrementInt32, a, aPlusOne); + } + status_t increment(uint32_t a, uint32_t* aPlusOne) const override { + ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__); + using Signature = status_t (ISafeInterfaceTest::*)(uint32_t, uint32_t*) const; + return callRemote<Signature>(Tag::IncrementUint32, a, aPlusOne); + } + status_t increment(int32_t a, int32_t* aPlusOne, int32_t b, int32_t* bPlusOne) const override { + ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__); + using Signature = + status_t (ISafeInterfaceTest::*)(int32_t, int32_t*, int32_t, int32_t*) const; + return callRemote<Signature>(Tag::IncrementTwo, a, aPlusOne, b, bPlusOne); + } + +private: + static constexpr const char* getLogTag() { return "BpSafeInterfaceTest"; } +}; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wexit-time-destructors" +IMPLEMENT_META_INTERFACE(SafeInterfaceTest, "android.gfx.tests.ISafeInterfaceTest"); + +static sp<IBinder::DeathRecipient> getDeathRecipient() { + static sp<IBinder::DeathRecipient> recipient = new ExitOnDeath; + return recipient; +} +#pragma clang diagnostic pop + +class BnSafeInterfaceTest : public SafeBnInterface<ISafeInterfaceTest> { +public: + BnSafeInterfaceTest() : SafeBnInterface(getLogTag()) {} + + status_t setDeathToken(const sp<IBinder>& token) override { + ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__); + token->linkToDeath(getDeathRecipient()); + return NO_ERROR; + } + status_t returnsNoMemory() const override { + ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__); + return NO_MEMORY; + } + status_t logicalNot(bool a, bool* notA) const override { + ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__); + *notA = !a; + return NO_ERROR; + } + status_t increment(const TestLightFlattenable& a, + TestLightFlattenable* aPlusOne) const override { + ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__); + aPlusOne->value = a.value + 1; + return NO_ERROR; + } + status_t increment(const NoCopyNoMove& a, NoCopyNoMove* aPlusOne) const override { + ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__); + aPlusOne->setValue(a.getValue() + 1); + return NO_ERROR; + } + status_t toUpper(const String8& str, String8* upperStr) const override { + ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__); + *upperStr = str; + upperStr->toUpper(); + return NO_ERROR; + } + void callMeBack(const sp<ICallback>& callback, int32_t a) const override { + ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__); + callback->onCallback(a + 1); + } + status_t increment(int32_t a, int32_t* aPlusOne) const override { + ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__); + *aPlusOne = a + 1; + return NO_ERROR; + } + status_t increment(uint32_t a, uint32_t* aPlusOne) const override { + ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__); + *aPlusOne = a + 1; + return NO_ERROR; + } + status_t increment(int32_t a, int32_t* aPlusOne, int32_t b, int32_t* bPlusOne) const override { + ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__); + *aPlusOne = a + 1; + *bPlusOne = b + 1; + return NO_ERROR; + } + + // BnInterface + status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, + uint32_t /*flags*/) override { + EXPECT_GE(code, IBinder::FIRST_CALL_TRANSACTION); + EXPECT_LT(code, static_cast<uint32_t>(Tag::Last)); + ISafeInterfaceTest::Tag tag = static_cast<ISafeInterfaceTest::Tag>(code); + switch (tag) { + case ISafeInterfaceTest::Tag::SetDeathToken: { + return callLocal(data, reply, &ISafeInterfaceTest::setDeathToken); + } + case ISafeInterfaceTest::Tag::ReturnsNoMemory: { + return callLocal(data, reply, &ISafeInterfaceTest::returnsNoMemory); + } + case ISafeInterfaceTest::Tag::LogicalNot: { + return callLocal(data, reply, &ISafeInterfaceTest::logicalNot); + } + case ISafeInterfaceTest::Tag::IncrementLightFlattenable: { + using Signature = + status_t (ISafeInterfaceTest::*)(const TestLightFlattenable& a, + TestLightFlattenable* aPlusOne) const; + return callLocal<Signature>(data, reply, &ISafeInterfaceTest::increment); + } + case ISafeInterfaceTest::Tag::IncrementNoCopyNoMove: { + using Signature = status_t (ISafeInterfaceTest::*)(const NoCopyNoMove& a, + NoCopyNoMove* aPlusOne) const; + return callLocal<Signature>(data, reply, &ISafeInterfaceTest::increment); + } + case ISafeInterfaceTest::Tag::ToUpper: { + return callLocal(data, reply, &ISafeInterfaceTest::toUpper); + } + case ISafeInterfaceTest::Tag::CallMeBack: { + return callLocalAsync(data, reply, &ISafeInterfaceTest::callMeBack); + } + case ISafeInterfaceTest::Tag::IncrementInt32: { + using Signature = status_t (ISafeInterfaceTest::*)(int32_t, int32_t*) const; + return callLocal<Signature>(data, reply, &ISafeInterfaceTest::increment); + } + case ISafeInterfaceTest::Tag::IncrementUint32: { + using Signature = status_t (ISafeInterfaceTest::*)(uint32_t, uint32_t*) const; + return callLocal<Signature>(data, reply, &ISafeInterfaceTest::increment); + } + case ISafeInterfaceTest::Tag::IncrementTwo: { + using Signature = status_t (ISafeInterfaceTest::*)(int32_t, int32_t*, int32_t, + int32_t*) const; + return callLocal<Signature>(data, reply, &ISafeInterfaceTest::increment); + } + case ISafeInterfaceTest::Tag::Last: + // Should not be possible because of the asserts at the beginning of the method + [&]() { FAIL(); }(); + return UNKNOWN_ERROR; + } + } + +private: + static constexpr const char* getLogTag() { return "BnSafeInterfaceTest"; } +}; + +class SafeInterfaceTest : public ::testing::Test { +public: + SafeInterfaceTest() : mSafeInterfaceTest(getRemoteService()) { + ProcessState::self()->startThreadPool(); + } + ~SafeInterfaceTest() override = default; + +protected: + sp<ISafeInterfaceTest> mSafeInterfaceTest; + +private: + static constexpr const char* getLogTag() { return "SafeInterfaceTest"; } + + sp<ISafeInterfaceTest> getRemoteService() { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wexit-time-destructors" + static std::mutex sMutex; + static sp<ISafeInterfaceTest> sService; + static sp<IBinder> sDeathToken = new BBinder; +#pragma clang diagnostic pop + + std::unique_lock<decltype(sMutex)> lock; + if (sService == nullptr) { + ALOG(LOG_INFO, getLogTag(), "Forking remote process"); + pid_t forkPid = fork(); + EXPECT_NE(forkPid, -1); + + const String16 serviceName("SafeInterfaceTest"); + + if (forkPid == 0) { + ALOG(LOG_INFO, getLogTag(), "Remote process checking in"); + sp<ISafeInterfaceTest> nativeService = new BnSafeInterfaceTest; + defaultServiceManager()->addService(serviceName, + IInterface::asBinder(nativeService)); + ProcessState::self()->startThreadPool(); + IPCThreadState::self()->joinThreadPool(); + // We shouldn't get to this point + [&]() { FAIL(); }(); + } + + sp<IBinder> binder = defaultServiceManager()->getService(serviceName); + sService = interface_cast<ISafeInterfaceTest>(binder); + EXPECT_TRUE(sService != nullptr); + + sService->setDeathToken(sDeathToken); + } + + return sService; + } +}; + +TEST_F(SafeInterfaceTest, TestReturnsNoMemory) { + status_t result = mSafeInterfaceTest->returnsNoMemory(); + ASSERT_EQ(NO_MEMORY, result); +} + +TEST_F(SafeInterfaceTest, TestLogicalNot) { + const bool a = true; + bool notA = true; + status_t result = mSafeInterfaceTest->logicalNot(a, ¬A); + ASSERT_EQ(NO_ERROR, result); + ASSERT_EQ(!a, notA); + // Test both since we don't want to accidentally catch a default false somewhere + const bool b = false; + bool notB = false; + result = mSafeInterfaceTest->logicalNot(b, ¬B); + ASSERT_EQ(NO_ERROR, result); + ASSERT_EQ(!b, notB); +} + +TEST_F(SafeInterfaceTest, TestIncrementLightFlattenable) { + const TestLightFlattenable a{1}; + TestLightFlattenable aPlusOne{0}; + status_t result = mSafeInterfaceTest->increment(a, &aPlusOne); + ASSERT_EQ(NO_ERROR, result); + ASSERT_EQ(a.value + 1, aPlusOne.value); +} + +TEST_F(SafeInterfaceTest, TestIncrementNoCopyNoMove) { + const NoCopyNoMove a{1}; + NoCopyNoMove aPlusOne{0}; + status_t result = mSafeInterfaceTest->increment(a, &aPlusOne); + ASSERT_EQ(NO_ERROR, result); + ASSERT_EQ(a.getValue() + 1, aPlusOne.getValue()); +} + +TEST_F(SafeInterfaceTest, TestToUpper) { + const String8 str{"Hello, world!"}; + String8 upperStr; + status_t result = mSafeInterfaceTest->toUpper(str, &upperStr); + ASSERT_EQ(NO_ERROR, result); + ASSERT_TRUE(upperStr == String8{"HELLO, WORLD!"}); +} + +TEST_F(SafeInterfaceTest, TestCallMeBack) { + class CallbackReceiver : public BnCallback { + public: + void onCallback(int32_t aPlusOne) override { + ALOG(LOG_INFO, "CallbackReceiver", "%s", __PRETTY_FUNCTION__); + std::unique_lock<decltype(mMutex)> lock(mMutex); + mValue = aPlusOne; + mCondition.notify_one(); + } + + std::optional<int32_t> waitForCallback() { + std::unique_lock<decltype(mMutex)> lock(mMutex); + bool success = + mCondition.wait_for(lock, 100ms, [&]() { return static_cast<bool>(mValue); }); + return success ? mValue : std::nullopt; + } + + private: + std::mutex mMutex; + std::condition_variable mCondition; + std::optional<int32_t> mValue; + }; + + sp<CallbackReceiver> receiver = new CallbackReceiver; + const int32_t a = 1; + mSafeInterfaceTest->callMeBack(receiver, a); + auto result = receiver->waitForCallback(); + ASSERT_TRUE(result); + ASSERT_EQ(a + 1, *result); +} + +TEST_F(SafeInterfaceTest, TestIncrementInt32) { + const int32_t a = 1; + int32_t aPlusOne = 0; + status_t result = mSafeInterfaceTest->increment(a, &aPlusOne); + ASSERT_EQ(NO_ERROR, result); + ASSERT_EQ(a + 1, aPlusOne); +} + +TEST_F(SafeInterfaceTest, TestIncrementUint32) { + const uint32_t a = 1; + uint32_t aPlusOne = 0; + status_t result = mSafeInterfaceTest->increment(a, &aPlusOne); + ASSERT_EQ(NO_ERROR, result); + ASSERT_EQ(a + 1, aPlusOne); +} + +TEST_F(SafeInterfaceTest, TestIncrementTwo) { + const int32_t a = 1; + int32_t aPlusOne = 0; + const int32_t b = 2; + int32_t bPlusOne = 0; + status_t result = mSafeInterfaceTest->increment(1, &aPlusOne, 2, &bPlusOne); + ASSERT_EQ(NO_ERROR, result); + ASSERT_EQ(a + 1, aPlusOne); + ASSERT_EQ(b + 1, bPlusOne); +} + +} // namespace tests +} // namespace android |