Commit message: None
CVE: CVE-2016-3861
CWE: CWE-119
Vulnerability description: LibUtils in Android 4.x before 4.4.4, 5.0.x before 5.0.2, 5.1.x before 5.1.1, 6.x before 2016-09-01, and 7.0 before 2016-09-01 mishandles conversions between Unicode character encodings with different encoding widths, which allows remote attackers to execute arbitrary code or cause a denial of service (heap-based buffer overflow) via a crafted file, aka internal bug 29250543.

Project: Android

void acquire_object(const sp<ProcessState>& proc,
 const flat_binder_object& obj, const void* who, size_t* outAshmemSize)
{
 switch (obj.type) {
 case BINDER_TYPE_BINDER:
 if (obj.binder) {
                LOG_REFS("Parcel %p acquiring reference on local %p", who, obj.cookie);
 reinterpret_cast<IBinder*>(obj.cookie)->incStrong(who);
 }
 return;
 case BINDER_TYPE_WEAK_BINDER:
 if (obj.binder)
 reinterpret_cast<RefBase::weakref_type*>(obj.binder)->incWeak(who);
 return;
 case BINDER_TYPE_HANDLE: {
 const sp<IBinder> b = proc->getStrongProxyForHandle(obj.handle);
 if (b != NULL) {
                LOG_REFS("Parcel %p acquiring reference on remote %p", who, b.get());
                b->incStrong(who);
 }
 return;
 }
 case BINDER_TYPE_WEAK_HANDLE: {
 const wp<IBinder> b = proc->getWeakProxyForHandle(obj.handle);
 if (b != NULL) b.get_refs()->incWeak(who);
 return;
 }
 case BINDER_TYPE_FD: {
 if ((obj.cookie != 0) && (outAshmemSize != NULL)) {
 struct stat st;
 int ret = fstat(obj.handle, &st);
 if (!ret && S_ISCHR(st.st_mode) && (st.st_rdev == ashmem_rdev())) {
 int size = ashmem_get_size_region(obj.handle);
 if (size > 0) {
 *outAshmemSize += size;
 }
 }
 }
 return;
 }
 }

    ALOGD("Invalid object type 0x%08x", obj.type);
}

Project: Android

static dev_t ashmem_rdev()
{
 static dev_t __ashmem_rdev;
 static pthread_mutex_t __ashmem_rdev_lock = PTHREAD_MUTEX_INITIALIZER;

    pthread_mutex_lock(&__ashmem_rdev_lock);

 dev_t rdev = __ashmem_rdev;
 if (!rdev) {
 int fd = TEMP_FAILURE_RETRY(open("/dev/ashmem", O_RDONLY));
 if (fd >= 0) {
 struct stat st;

 int ret = TEMP_FAILURE_RETRY(fstat(fd, &st));
            close(fd);
 if ((ret >= 0) && S_ISCHR(st.st_mode)) {
                rdev = __ashmem_rdev = st.st_rdev;
 }
 }
 }

    pthread_mutex_unlock(&__ashmem_rdev_lock);

 return rdev;
}

Project: Android

size_t Parcel::dataAvail() const
{
 size_t result = dataSize() - dataPosition();
 if (result > INT32_MAX) {
        abort();
 }
 return result;
}

Project: Android

void Parcel::freeDataNoInit()
{
 if (mOwner) {
        LOG_ALLOC("Parcel %p: freeing other owner data", this);
        mOwner(this, mData, mDataSize, mObjects, mObjectsSize, mOwnerCookie);
 } else {
        LOG_ALLOC("Parcel %p: freeing allocated data", this);
        releaseObjects();
 if (mData) {
            LOG_ALLOC("Parcel %p: freeing with %zu capacity", this, mDataCapacity);
            pthread_mutex_lock(&gParcelGlobalAllocSizeLock);
 if (mDataCapacity <= gParcelGlobalAllocSize) {
              gParcelGlobalAllocSize = gParcelGlobalAllocSize - mDataCapacity;
 } else {
              gParcelGlobalAllocSize = 0;
 }
 if (gParcelGlobalAllocCount > 0) {
              gParcelGlobalAllocCount--;
 }
            pthread_mutex_unlock(&gParcelGlobalAllocSizeLock);
            free(mData);
 }
 if (mObjects) free(mObjects);
 }
}

Project: Android

void Parcel::initState()
{
    LOG_ALLOC("Parcel %p: initState", this);
    mError = NO_ERROR;
    mData = 0;
    mDataSize = 0;
    mDataCapacity = 0;
    mDataPos = 0;
    ALOGV("initState Setting data size of %p to %zu", this, mDataSize);
    ALOGV("initState Setting data pos of %p to %zu", this, mDataPos);
    mObjects = NULL;
    mObjectsSize = 0;
    mObjectsCapacity = 0;
    mNextObjectHint = 0;
    mHasFds = false;
    mFdsKnown = true;
    mAllowFds = true;
    mOwner = NULL;
    mOpenAshmemSize = 0;

 if (gMaxFds == 0) {
 struct rlimit result;
 if (!getrlimit(RLIMIT_NOFILE, &result)) {
            gMaxFds = (size_t)result.rlim_cur;
 } else {
            ALOGW("Unable to getrlimit: %s", strerror(errno));
            gMaxFds = 1024;
 }
 }
}

Project: Android

status_t Parcel::read(FlattenableHelperInterface& val) const
{
 const size_t len = this->readInt32();
 const size_t fd_count = this->readInt32();

 if ((len > INT32_MAX) || (fd_count >= gMaxFds)) {
 return BAD_VALUE;
 }

 void const* const buf = this->readInplace(pad_size(len));
 if (buf == NULL)
 return BAD_VALUE;

 int* fds = NULL;
 if (fd_count) {
        fds = new (std::nothrow) int[fd_count];
 if (fds == nullptr) {
            ALOGE("read: failed to allocate requested %zu fds", fd_count);
 return BAD_VALUE;
 }
 }

 status_t err = NO_ERROR;
 for (size_t i=0 ; i<fd_count && err==NO_ERROR ; i++) {
        fds[i] = dup(this->readFileDescriptor());
 if (fds[i] < 0) {
            err = BAD_VALUE;
            ALOGE("dup() failed in Parcel::read, i is %zu, fds[i] is %d, fd_count is %zu, error: %s",
                i, fds[i], fd_count, strerror(errno));
 }
 }

 if (err == NO_ERROR) {
        err = val.unflatten(buf, len, fds, fd_count);
 }

 if (fd_count) {
 delete [] fds;
 }

 return err;
}

Project: Android

status_t Parcel::readBool(bool *pArg) const
{
 int32_t tmp;
 status_t ret = readInt32(&tmp);
 *pArg = (tmp != 0);
 return ret;
}

Project: Android

bool Parcel::readBool() const
{
 return readInt32() != 0;
}

Project: Android

status_t Parcel::readBoolVector(std::unique_ptr<std::vector<bool>>* val) const {
 const int32_t start = dataPosition();
 int32_t size;
 status_t status = readInt32(&size);
    val->reset();

 if (status != OK || size < 0) {
 return status;
 }

    setDataPosition(start);
    val->reset(new (std::nothrow) std::vector<bool>());

    status = readBoolVector(val->get());

 if (status != OK) {
        val->reset();
 }

 return status;
}

Project: Android

status_t Parcel::readByte(int8_t *pArg) const
{
 int32_t tmp;
 status_t ret = readInt32(&tmp);
 *pArg = int8_t(tmp);
 return ret;
}

Project: Android

int8_t Parcel::readByte() const
{
 return int8_t(readInt32());
}

Project: Android

status_t Parcel::readByteVector(std::vector<int8_t>* val) const {
 return readByteVectorInternal(this, val);
}

Project: Android

status_t Parcel::readByteVector(std::vector<uint8_t>* val) const {
 return readByteVectorInternal(this, val);
}

Project: Android

status_t Parcel::readByteVector(std::unique_ptr<std::vector<int8_t>>* val) const {
 return readByteVectorInternalPtr(this, val);
}

Project: Android

status_t Parcel::readByteVector(std::unique_ptr<std::vector<uint8_t>>* val) const {
 return readByteVectorInternalPtr(this, val);
}

Project: Android

status_t Parcel::readChar(char16_t *pArg) const
{
 int32_t tmp;
 status_t ret = readInt32(&tmp);
 *pArg = char16_t(tmp);
 return ret;
}

Project: Android

status_t Parcel::readCharVector(std::unique_ptr<std::vector<char16_t>>* val) const {
 return readNullableTypedVector(val, &Parcel::readChar);
}

Project: Android

status_t Parcel::readCharVector(std::vector<char16_t>* val) const {
 return readTypedVector(val, &Parcel::readChar);
}

Project: Android

status_t Parcel::readDoubleVector(std::unique_ptr<std::vector<double>>* val) const {
 return readNullableTypedVector(val, &Parcel::readDouble);
}

Project: Android

int Parcel::readFileDescriptor() const
{
 const flat_binder_object* flat = readObject(true);

 if (flat && flat->type == BINDER_TYPE_FD) {
 return flat->handle;
 }

 return BAD_TYPE;
}

Project: Android

status_t Parcel::readFloatVector(std::unique_ptr<std::vector<float>>* val) const {
 return readNullableTypedVector(val, &Parcel::readFloat);
}

Project: Android

status_t Parcel::readInt32Vector(std::unique_ptr<std::vector<int32_t>>* val) const {
 return readNullableTypedVector(val, &Parcel::readInt32);
}

Project: Android

status_t Parcel::readInt32Vector(std::vector<int32_t>* val) const {
 return readTypedVector(val, &Parcel::readInt32);
}

Project: Android

status_t Parcel::readInt64Vector(std::unique_ptr<std::vector<int64_t>>* val) const {
 return readNullableTypedVector(val, &Parcel::readInt64);
}

Project: Android

status_t Parcel::readInt64Vector(std::vector<int64_t>* val) const {
 return readTypedVector(val, &Parcel::readInt64);
}

Project: Android

native_handle* Parcel::readNativeHandle() const
{
 int numFds, numInts;
 status_t err;
    err = readInt32(&numFds);
 if (err != NO_ERROR) return 0;
    err = readInt32(&numInts);
 if (err != NO_ERROR) return 0;

    native_handle* h = native_handle_create(numFds, numInts);
 if (!h) {
 return 0;
 }

 for (int i=0 ; err==NO_ERROR && i<numFds ; i++) {
        h->data[i] = dup(readFileDescriptor());
 if (h->data[i] < 0) {
 for (int j = 0; j < i; j++) {
                close(h->data[j]);
 }
            native_handle_delete(h);
 return 0;
 }
 }
    err = read(h->data + numFds, sizeof(int)*numInts);
 if (err != NO_ERROR) {
        native_handle_close(h);
        native_handle_delete(h);
        h = 0;
 }
 return h;
}

Project: Android

status_t Parcel::readParcelable(Parcelable* parcelable) const {
 int32_t have_parcelable = 0;
 status_t status = readInt32(&have_parcelable);
 if (status != OK) {
 return status;
 }
 if (!have_parcelable) {
 return UNEXPECTED_NULL;
 }
 return parcelable->readFromParcel(this);
}

Project: Android

status_t Parcel::readString16(std::unique_ptr<String16>* pArg) const
{
 const int32_t start = dataPosition();
 int32_t size;
 status_t status = readInt32(&size);
    pArg->reset();

 if (status != OK || size < 0) {
 return status;
 }

    setDataPosition(start);
    pArg->reset(new (std::nothrow) String16());

    status = readString16(pArg->get());

 if (status != OK) {
        pArg->reset();
 }

 return status;
}

Project: Android

status_t Parcel::readString16(String16* pArg) const
{
 size_t len;
 const char16_t* str = readString16Inplace(&len);
 if (str) {
        pArg->setTo(str, len);
 return 0;
 } else {
 *pArg = String16();
 return UNEXPECTED_NULL;
 }
}

Project: Android

status_t Parcel::readString16Vector(
        std::unique_ptr<std::vector<std::unique_ptr<String16>>>* val) const {
 return readNullableTypedVector(val, &Parcel::readString16);
}

Project: Android

status_t Parcel::readString16Vector(std::vector<String16>* val) const {
 return readTypedVector(val, &Parcel::readString16);
}

Project: Android

status_t Parcel::readStrongBinder(sp<IBinder>* val) const
{
 return unflatten_binder(ProcessState::self(), *this, val);
}

Project: Android

sp<IBinder> Parcel::readStrongBinder() const
{
    sp<IBinder> val;
    readStrongBinder(&val);
 return val;
}

Project: Android

status_t Parcel::readStrongBinderVector(std::unique_ptr<std::vector<sp<IBinder>>>* val) const {
 return readNullableTypedVector(val, &Parcel::readStrongBinder);
}

Project: Android

status_t Parcel::readStrongBinderVector(std::vector<sp<IBinder>>* val) const {
 return readTypedVector(val, &Parcel::readStrongBinder);
}

Project: Android

status_t Parcel::readUniqueFileDescriptor(ScopedFd* val) const
{
 int got = readFileDescriptor();

 if (got == BAD_TYPE) {
 return BAD_TYPE;
 }

    val->reset(dup(got));

 if (val->get() < 0) {
 return BAD_VALUE;
 }

 return OK;
}

Project: Android

status_t Parcel::readUniqueFileDescriptorVector(std::vector<ScopedFd>* val) const {
 return readTypedVector(val, &Parcel::readUniqueFileDescriptor);
}

Project: Android

status_t Parcel::readUtf8FromUtf16(std::unique_ptr<std::string>* str) const {
 const int32_t start = dataPosition();
 int32_t size;
 status_t status = readInt32(&size);
    str->reset();

 if (status != OK || size < 0) {
 return status;
 }

    setDataPosition(start);
    str->reset(new (std::nothrow) std::string());
 return readUtf8FromUtf16(str->get());
}

Project: Android

status_t Parcel::readUtf8VectorFromUtf16Vector(
        std::unique_ptr<std::vector<std::unique_ptr<std::string>>>* val) const {
 return readNullableTypedVector(val, &Parcel::readUtf8FromUtf16);
}

Project: Android

status_t Parcel::readUtf8VectorFromUtf16Vector(std::vector<std::string>* val) const {
 return readTypedVector(val, &Parcel::readUtf8FromUtf16);
}

Project: Android

static void release_object(const sp<ProcessState>& proc,
 const flat_binder_object& obj, const void* who, size_t* outAshmemSize)
{
 switch (obj.type) {
 case BINDER_TYPE_BINDER:
 if (obj.binder) {
                LOG_REFS("Parcel %p releasing reference on local %p", who, obj.cookie);
 reinterpret_cast<IBinder*>(obj.cookie)->decStrong(who);
 }
 return;
 case BINDER_TYPE_WEAK_BINDER:
 if (obj.binder)
 reinterpret_cast<RefBase::weakref_type*>(obj.binder)->decWeak(who);
 return;
 case BINDER_TYPE_HANDLE: {
 const sp<IBinder> b = proc->getStrongProxyForHandle(obj.handle);
 if (b != NULL) {
                LOG_REFS("Parcel %p releasing reference on remote %p", who, b.get());
                b->decStrong(who);
 }
 return;
 }
 case BINDER_TYPE_WEAK_HANDLE: {
 const wp<IBinder> b = proc->getWeakProxyForHandle(obj.handle);
 if (b != NULL) b.get_refs()->decWeak(who);
 return;
 }
 case BINDER_TYPE_FD: {
 if (obj.cookie != 0) { // owned
 if (outAshmemSize != NULL) {
 struct stat st;
 int ret = fstat(obj.handle, &st);
 if (!ret && S_ISCHR(st.st_mode) && (st.st_rdev == ashmem_rdev())) {
 int size = ashmem_get_size_region(obj.handle);
 if (size > 0) {
 *outAshmemSize -= size;
 }
 }
 }

                close(obj.handle);
 }
 return;
 }
 }

    ALOGE("Invalid object type 0x%08x", obj.type);
}

Project: Android

status_t Parcel::restartWrite(size_t desired)
{
 if (desired > INT32_MAX) {
 return BAD_VALUE;
 }

 if (mOwner) {
        freeData();
 return continueWrite(desired);
 }

 uint8_t* data = (uint8_t*)realloc(mData, desired);
 if (!data && desired > mDataCapacity) {
        mError = NO_MEMORY;
 return NO_MEMORY;
 }

    releaseObjects();

 if (data) {
        LOG_ALLOC("Parcel %p: restart from %zu to %zu capacity", this, mDataCapacity, desired);
        pthread_mutex_lock(&gParcelGlobalAllocSizeLock);
        gParcelGlobalAllocSize += desired;
        gParcelGlobalAllocSize -= mDataCapacity;
 if (!mData) {
            gParcelGlobalAllocCount++;
 }
        pthread_mutex_unlock(&gParcelGlobalAllocSizeLock);
        mData = data;
        mDataCapacity = desired;
 }

    mDataSize = mDataPos = 0;
    ALOGV("restartWrite Setting data size of %p to %zu", this, mDataSize);
    ALOGV("restartWrite Setting data pos of %p to %zu", this, mDataPos);

    free(mObjects);
    mObjects = NULL;
    mObjectsSize = mObjectsCapacity = 0;
    mNextObjectHint = 0;
    mHasFds = false;
    mFdsKnown = true;
    mAllowFds = true;

 return NO_ERROR;
}

Project: Android

status_t Parcel::write(const FlattenableHelperInterface& val)
{
 status_t err;

 const size_t len = val.getFlattenedSize();
 const size_t fd_count = val.getFdCount();

 if ((len > INT32_MAX) || (fd_count >= gMaxFds)) {
 return BAD_VALUE;
 }

    err = this->writeInt32(len);
 if (err) return err;

    err = this->writeInt32(fd_count);
 if (err) return err;

 void* const buf = this->writeInplace(pad_size(len));
 if (buf == NULL)
 return BAD_VALUE;

 int* fds = NULL;
 if (fd_count) {
        fds = new (std::nothrow) int[fd_count];
 if (fds == nullptr) {
            ALOGE("write: failed to allocate requested %zu fds", fd_count);
 return BAD_VALUE;
 }
 }

    err = val.flatten(buf, len, fds, fd_count);
 for (size_t i=0 ; i<fd_count && err==NO_ERROR ; i++) {
        err = this->writeDupFileDescriptor( fds[i] );
 }

 if (fd_count) {
 delete [] fds;
 }

 return err;
}

Project: Android

status_t Parcel::writeBool(bool val)
{
 return writeInt32(int32_t(val));
}

Project: Android

status_t Parcel::writeBoolVector(const std::vector<bool>& val)
{
 return writeTypedVector(val, &Parcel::writeBool);
}

Project: Android

status_t Parcel::writeBoolVector(const std::unique_ptr<std::vector<bool>>& val)
{
 return writeNullableTypedVector(val, &Parcel::writeBool);
}

Project: Android

status_t Parcel::writeByteVector(const std::vector<uint8_t>& val) {
 return writeByteVectorInternal(this, val);
}

Project: Android

status_t Parcel::writeByteVector(const std::unique_ptr<std::vector<uint8_t>>& val)
{
 return writeByteVectorInternalPtr(this, val);
}

Project: Android

status_t writeByteVectorInternal(Parcel* parcel, const std::vector<T>& val)
{
 status_t status;
 if (val.size() > std::numeric_limits<int32_t>::max()) {
        status = BAD_VALUE;
 return status;
 }

    status = parcel->writeInt32(val.size());
 if (status != OK) {
 return status;
 }

 void* data = parcel->writeInplace(val.size());
 if (!data) {
        status = BAD_VALUE;
 return status;
 }

    memcpy(data, val.data(), val.size());
 return status;
}

Project: Android

status_t writeByteVectorInternalPtr(Parcel* parcel,
 const std::unique_ptr<std::vector<T>>& val)
{
 if (!val) {
 return parcel->writeInt32(-1);
 }

 return writeByteVectorInternal(parcel, *val);
}

Project: Android

status_t Parcel::writeChar(char16_t val)
{
 return writeInt32(int32_t(val));
}

Project: Android

status_t Parcel::writeCharVector(const std::vector<char16_t>& val)
{
 return writeTypedVector(val, &Parcel::writeChar);
}

Project: Android

status_t Parcel::writeCharVector(const std::unique_ptr<std::vector<char16_t>>& val)
{
 return writeNullableTypedVector(val, &Parcel::writeChar);
}

Project: Android

status_t Parcel::writeDoubleVector(const std::vector<double>& val)
{
 return writeTypedVector(val, &Parcel::writeDouble);
}

Project: Android

status_t Parcel::writeDoubleVector(const std::unique_ptr<std::vector<double>>& val)
{
 return writeNullableTypedVector(val, &Parcel::writeDouble);
}

Project: Android

status_t Parcel::writeDupFileDescriptor(int fd)
{
 int dupFd = dup(fd);
 if (dupFd < 0) {
 return -errno;
 }
 status_t err = writeFileDescriptor(dupFd, true /*takeOwnership*/);
 if (err != OK) {
        close(dupFd);
 }
 return err;
}

Project: Android

status_t Parcel::writeFloatVector(const std::vector<float>& val)
{
 return writeTypedVector(val, &Parcel::writeFloat);
}

Project: Android

status_t Parcel::writeFloatVector(const std::unique_ptr<std::vector<float>>& val)
{
 return writeNullableTypedVector(val, &Parcel::writeFloat);
}

Project: Android

status_t Parcel::writeInt32Vector(const std::vector<int32_t>& val)
{
 return writeTypedVector(val, &Parcel::writeInt32);
}

Project: Android

status_t Parcel::writeInt32Vector(const std::unique_ptr<std::vector<int32_t>>& val)
{
 return writeNullableTypedVector(val, &Parcel::writeInt32);
}

Project: Android

status_t Parcel::writeInt64Vector(const std::unique_ptr<std::vector<int64_t>>& val)
{
 return writeNullableTypedVector(val, &Parcel::writeInt64);
}

Project: Android

status_t Parcel::writeNoException()
{
    binder::Status status;
 return status.writeToParcel(this);
}

Project: Android

status_t Parcel::writeParcelable(const Parcelable& parcelable) {
 status_t status = writeInt32(1); // parcelable is not null.
 if (status != OK) {
 return status;
 }
 return parcelable.writeToParcel(this);
}

Project: Android

status_t Parcel::writeRawNullableParcelable(const Parcelable* parcelable) {
 if (!parcelable) {
 return writeInt32(0);
 }

 return writeParcelable(*parcelable);
}

Project: Android

status_t Parcel::writeString16(const std::unique_ptr<String16>& str)
{
 if (!str) {
 return writeInt32(-1);
 }

 return writeString16(*str);
}

Project: Android

status_t Parcel::writeString16Vector(const std::vector<String16>& val)
{
 return writeTypedVector(val, &Parcel::writeString16);
}

Project: Android

status_t Parcel::writeStrongBinderVector(const std::vector<sp<IBinder>>& val)
{
 return writeTypedVector(val, &Parcel::writeStrongBinder);
}

Project: Android

status_t Parcel::writeStrongBinderVector(const std::unique_ptr<std::vector<sp<IBinder>>>& val)
{
 return writeNullableTypedVector(val, &Parcel::writeStrongBinder);
}

Project: Android

status_t Parcel::writeUniqueFileDescriptor(const ScopedFd& fd) {
 return writeDupFileDescriptor(fd.get());
}

Project: Android

status_t Parcel::writeUniqueFileDescriptorVector(const std::vector<ScopedFd>& val) {
 return writeTypedVector(val, &Parcel::writeUniqueFileDescriptor);
}

Project: Android

status_t Parcel::writeUniqueFileDescriptorVector(const std::unique_ptr<std::vector<ScopedFd>>& val) {
 return writeNullableTypedVector(val, &Parcel::writeUniqueFileDescriptor);
}

Project: Android

status_t Parcel::writeUtf8AsUtf16(const std::string& str) {
 const uint8_t* strData = (uint8_t*)str.data();
 const size_t strLen= str.length();
 const ssize_t utf16Len = utf8_to_utf16_length(strData, strLen);
 if (utf16Len < 0 || utf16Len> std::numeric_limits<int32_t>::max()) {
 return BAD_VALUE;
 }

 status_t err = writeInt32(utf16Len);
 if (err) {
 return err;
 }

 void* dst = writeInplace((utf16Len + 1) * sizeof(char16_t));
 if (!dst) {
 return NO_MEMORY;
 }

    utf8_to_utf16(strData, strLen, (char16_t*)dst);

 return NO_ERROR;
}

Project: Android

status_t Parcel::writeUtf8AsUtf16(const std::unique_ptr<std::string>& str) {
 if (!str) {
 return writeInt32(-1);
 }
 return writeUtf8AsUtf16(*str);
}

Project: Android

status_t Parcel::writeUtf8VectorAsUtf16Vector(
 const std::unique_ptr<std::vector<std::unique_ptr<std::string>>>& val) {
 return writeNullableTypedVector(val, &Parcel::writeUtf8AsUtf16);
}