diff options
author | Sven Gothel <[email protected]> | 2022-07-07 14:39:40 +0200 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2022-07-07 14:39:40 +0200 |
commit | 2ebef56ba148c88c6cb1d864b4773df574bb4168 (patch) | |
tree | b843f8f84bee0211ff4d4c1430873a7f36e72139 | |
parent | 014c5f1b0f49f5be2595ef74511a5e09c157f704 (diff) |
Migrate `void zeroByteBuffer(final ByteBuffer buf)` from Cipherpack's CPUtils -> MemUtil, incl. unit test
-rw-r--r-- | java_jni/jni/CMakeLists.txt | 1 | ||||
-rw-r--r-- | java_jni/jni/jau/jau_io_MemUtil.cxx | 155 | ||||
-rw-r--r-- | java_jni/org/jau/io/MemUtil.java | 48 | ||||
-rw-r--r-- | test/java/jau/test/io/TestMemBuffers00.java | 142 |
4 files changed, 346 insertions, 0 deletions
diff --git a/java_jni/jni/CMakeLists.txt b/java_jni/jni/CMakeLists.txt index 77236ca..97bd124 100644 --- a/java_jni/jni/CMakeLists.txt +++ b/java_jni/jni/CMakeLists.txt @@ -25,6 +25,7 @@ set (jaulib_jni_JNI_SRCS ${PROJECT_SOURCE_DIR}/java_jni/jni/jau/JVM_JNI8.cxx ${PROJECT_SOURCE_DIR}/java_jni/jni/jau/MachineDataInfoRuntime.cxx ${PROJECT_SOURCE_DIR}/java_jni/jni/jau/jau_fs_FileUtil.cxx + ${PROJECT_SOURCE_DIR}/java_jni/jni/jau/jau_io_MemUtil.cxx ${PROJECT_SOURCE_DIR}/java_jni/jni/jau/jau_io_UriTk.cxx ${PROJECT_SOURCE_DIR}/java_jni/jni/jau/jau_sys_Clock.cxx ${PROJECT_SOURCE_DIR}/java_jni/jni/jau/ByteInStream_File.cxx diff --git a/java_jni/jni/jau/jau_io_MemUtil.cxx b/java_jni/jni/jau/jau_io_MemUtil.cxx new file mode 100644 index 0000000..9472167 --- /dev/null +++ b/java_jni/jni/jau/jau_io_MemUtil.cxx @@ -0,0 +1,155 @@ +/* + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2022 Gothel Software e.K. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "org_jau_io_MemUtil.h" + +#include <cstdint> +#include <cinttypes> + +#include <time.h> + +#include <jau/environment.hpp> + +#include "jau/jni/helper_jni.hpp" + +void Java_org_jau_io_MemUtil_zeroByteBuffer(JNIEnv *env, jclass clazz, jobject jbuf) { + (void)clazz; + if( nullptr != jbuf ) { + void* address = env->GetDirectBufferAddress(jbuf); + jlong capacity = env->GetDirectBufferCapacity(jbuf); + if( nullptr != address && 0 < capacity ) { + ::explicit_bzero(address, capacity); + } + } +} + +#if 0 + +jboolean Java_org_jau_io_MemUtil_zeroString(JNIEnv *env, jclass clazz, jstring jstr) { + (void)clazz; + if( nullptr != jstr ) { + size_t len = env->GetStringLength(jstr); + if( 0 == len ) { + return JNI_TRUE; + } + jboolean is_copy = JNI_FALSE; + const jchar* str_u16 = env->GetStringCritical(jstr, &is_copy); + DBG_PRINT("zeroString.0: jstr %p, copy %d", str_u16, is_copy); + if( nullptr != str_u16 ) { + ::explicit_bzero((void*)str_u16, len * sizeof(jchar)); + env->ReleaseStringCritical(jstr, str_u16); + } else { + ERR_PRINT("GetStringCritical() return null"); + } + if( nullptr == str_u16 || JNI_TRUE == is_copy ) { + // try harder .. + jclass string_clazz = jau::jni::search_class(env, "java/lang/String"); + jfieldID f_value = jau::jni::search_field(env, string_clazz, "value", "[B", false /* is_static */); + jbyteArray jstr_value = (jbyteArray)env->GetObjectField(jstr, f_value); + jau::jni::java_exception_check_and_throw(env, E_FILE_LINE); + + if( nullptr == jstr_value ) { + ERR_PRINT("GetObjectField(value) is null"); + return JNI_FALSE; + } + const size_t jstr_value_size = env->GetArrayLength(jstr_value); + if( 0 == jstr_value_size ) { + ERR_PRINT("GetArrayLength(address byte array) is null"); + return JNI_FALSE; + } + jau::jni::JNICriticalArray<uint8_t, jbyteArray> criticalArray(env); // RAII - release + uint8_t * ptr = criticalArray.get(jstr_value, criticalArray.Mode::UPDATE_AND_RELEASE); + DBG_PRINT("zeroString.1: value: %p, len %zu, is_copy %d", ptr, jstr_value_size, criticalArray.getIsCopy()); + if( NULL == ptr ) { + ERR_PRINT("GetPrimitiveArrayCritical(address byte array) is null"); + return JNI_FALSE; + } + ::explicit_bzero((void*)ptr, jstr_value_size); + } + } + return JNI_TRUE; +} + +#include <locale> +#include <codecvt> + +// utility wrapper to adapt locale-bound facets for wstring/wbuffer convert +template<class Facet> +struct deletable_facet : Facet +{ + template<class ...Args> + deletable_facet(Args&& ...args) : Facet(std::forward<Args>(args)...) {} + ~deletable_facet() {} +}; + +jobject Java_org_jau_io_MemUtil_toByteBufferImpl(JNIEnv *env, jclass clazz, jstring jstr) { + (void)clazz; + if( nullptr == jstr ) { + return nullptr; + } + size_t jstr_len = env->GetStringLength(jstr); + if( 0 == jstr_len ) { + return nullptr; + } + jboolean isCopy = JNI_TRUE; + const jchar* jstr_u16 = env->GetStringCritical(jstr, &isCopy); + if( nullptr == jstr_u16 ) { + return nullptr; + } + std::u16string str_u16(jstr_u16, jstr_u16+jstr_len); + if( JNI_TRUE == isCopy ) { + ::explicit_bzero((void*)jstr_u16, jstr_len * sizeof(jchar)); + } + env->ReleaseStringCritical(jstr, jstr_u16); + + // UTF-16/char16_t to UTF-8 + std::wstring_convert<deletable_facet<std::codecvt<char16_t, char, std::mbstate_t>>, char16_t> conv16; + // std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> conv16; + std::string u8_conv = conv16.to_bytes(str_u16); + ::explicit_bzero((void*)str_u16.data(), str_u16.size() * sizeof(char16_t)); + const size_t u8_size = u8_conv.size(); + + jmethodID buffer_new = jau::jni::search_method(env, clazz, "newDirectByteBuffer", "(I)Ljava/nio/ByteBuffer;", true); + jobject jdest = env->CallStaticObjectMethod(clazz, buffer_new, (jint)u8_size); + jau::jni::java_exception_check_and_throw(env, E_FILE_LINE); + if( nullptr == jdest ) { + ERR_PRINT("Couldn't allocated ByteBuffer w/ capacity %zu", u8_size); + return nullptr; + } + char* jdest_address = (char*)env->GetDirectBufferAddress(jdest); + size_t jdest_capacity = (size_t)env->GetDirectBufferCapacity(jdest); + if( nullptr == jdest_address || 0 == jdest_capacity ) { + ERR_PRINT("ByteBuffer w/ capacity %zu has zero address (%p) or length (%zu)", u8_size, jdest_address, jdest_capacity); + return nullptr; + } + if( u8_size > jdest_capacity ) { + ERR_PRINT("ByteBuffer w/ capacity %zu < required size %zu)", jdest_capacity, u8_size); + return nullptr; + } + ::memcpy(jdest_address, u8_conv.data(), u8_size); + ::explicit_bzero((void*)u8_conv.data(), u8_size); + return jdest; +} + +#endif diff --git a/java_jni/org/jau/io/MemUtil.java b/java_jni/org/jau/io/MemUtil.java new file mode 100644 index 0000000..ea3cbb8 --- /dev/null +++ b/java_jni/org/jau/io/MemUtil.java @@ -0,0 +1,48 @@ +package org.jau.io; + +import java.nio.ByteBuffer; + +public class MemUtil { + /** + * Zeros all bytes of given direct NIO byte buffer. + */ + public static native void zeroByteBuffer(final ByteBuffer buf); + + /** + * Zeros all underlying bytes of given Java string. + * + * Implementation either uses the JNI GetStringCritical() with is_copy == false or the String backing array `value`. + * + * @return true if successful, i.e. the underlying bytes could have been zeroed, otherwise returns false w/o zero'ed content. + public static native boolean zeroString(final String str); + */ + + /** + * Converts the Java string to a native direct ByteBuffer as UTF-8 + * allowing better control over its memory region to {@link #zeroByteBuffer(ByteBuffer) zero} it after usage. + * + * @param str the string to convert + * @param zerostr pass true to zero the bytes of the given Java string afterwards (recommended!) + * @return direct ByteBuffer instance suitable for Cipherpack. + public static ByteBuffer to_ByteBuffer(final String str, final boolean zerostr) { + final ByteBuffer res = toByteBufferImpl(str); + res.limit(res.capacity()); + res.position(0); + if( zerostr ) { + zeroString(str); + } + return res; + } + private static native ByteBuffer toByteBufferImpl(final String str); + + public static String to_String(final ByteBuffer bb, final Charset cs) { + final int p0 = bb.position(); + bb.position(0); + final byte[] bb_bytes = new byte[bb.limit()]; + bb.get(bb_bytes); + bb.position(p0); + return new String(bb_bytes, cs); + } + */ + +} diff --git a/test/java/jau/test/io/TestMemBuffers00.java b/test/java/jau/test/io/TestMemBuffers00.java new file mode 100644 index 0000000..780f347 --- /dev/null +++ b/test/java/jau/test/io/TestMemBuffers00.java @@ -0,0 +1,142 @@ +/** + * Author: Sven Gothel <[email protected]> + * Copyright (c) 2022 Gothel Software e.K. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package jau.test.io; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import org.jau.io.Buffers; +import org.jau.io.MemUtil; +import org.junit.Assert; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +import jau.pkg.PlatformRuntime; +import jau.test.junit.util.JunitTracer; + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class TestMemBuffers00 extends JunitTracer { + static final boolean DEBUG = false; + + @Test(timeout = 10000) + public final void test01_ByteBuffer() { + PlatformRuntime.checkInitialized(); + final int len = 10; + + final ByteBuffer b1 = Buffers.newDirectByteBuffer(len * 2); + Assert.assertEquals(len * 2, b1.capacity()); + Assert.assertEquals(len * 2, b1.limit()); + Assert.assertEquals(0, b1.position()); + Assert.assertEquals(len * 2, b1.remaining()); + Assert.assertEquals(ByteOrder.nativeOrder(), b1.order()); + + for(int i=0; i<len; ++i) { + b1.put((byte)0xaa); + } + Assert.assertEquals(len, b1.position()); + b1.flip(); + Assert.assertEquals(len, b1.limit()); + Assert.assertEquals(0, b1.position()); + Assert.assertEquals(len, b1.remaining()); + + Assert.assertEquals((byte)0xaa, b1.get(0)); + Assert.assertEquals((byte)0xaa, b1.get(len-1)); + MemUtil.zeroByteBuffer(b1); + Assert.assertEquals((byte)0x00, b1.get(0)); + Assert.assertEquals((byte)0x00, b1.get(len-1)); + } + +/** + @Test(timeout = 10000) + public final void test02_String_ByteBuffer() { + PlatformRuntime.checkInitialized(); + final ByteBuffer b0; + { + final String s1 = "0123456789"; // len = 10 + b0 = CPUtils.to_ByteBuffer(s1, false); + final String s2 = new String(s1.getBytes()); // true copy before deletion + final boolean r2 = CPUtils.zeroString(s2); + // Assert.assertTrue( r1 ); + CPUtils.fprintf_td(System.err, "test01_enc_dec_file_ok.1a: s1: '%s', len %d\n", s1, s1.length()); + CPUtils.fprintf_td(System.err, "test01_enc_dec_file_ok.1a: s2: '%s', len %d, zeroString result %b\n", s2, s2.length(), r2); + + final boolean r1 = CPUtils.zeroString(s1); + CPUtils.fprintf_td(System.err, "test01_enc_dec_file_ok.1b: s1: '%s', len %d, zeroString result %b\n", s1, s1.length(), r1); + } + { + // Ooops, the internalized string at compile time got zeroed out + final String s1 = "0123456789"; // len = 10 + CPUtils.fprintf_td(System.err, "test01_enc_dec_file_ok.1c: s1: '%s', len %d\n", s1, s1.length()); + } + { + // That works, using dynamic storage + final String s1 = CPUtils.to_String(b0, StandardCharsets.UTF_8); + CPUtils.fprintf_td(System.err, "test01_enc_dec_file_ok.1d: s1: '%s', len %d\n", s1, s1.length()); + } + { + final String s1 = CPUtils.to_String(b0, StandardCharsets.UTF_8); + CPUtils.fprintf_td(System.err, "test01_enc_dec_file_ok.2a: s1: '%s', len %d\n", s1, s1.length()); + + final ByteBuffer b1 = CPUtils.newDirectByteBuffer(s1.length() * 2); + Assert.assertEquals(s1.length() * 2, b1.capacity()); + Assert.assertEquals(s1.length() * 2, b1.limit()); + Assert.assertEquals(0, b1.position()); + Assert.assertEquals(s1.length() * 2, b1.remaining()); + + b1.put(s1.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals(s1.length(), b1.position()); + b1.flip(); + Assert.assertEquals(s1.length(), b1.limit()); + Assert.assertEquals(0, b1.position()); + Assert.assertEquals(s1.length(), b1.remaining()); + { + final String b1_str = CPUtils.to_String(b1, StandardCharsets.UTF_8); + CPUtils.fprintf_td(System.err, "test01_enc_dec_file_ok.2b: b1: '%s', len %d/%d\n", b1_str, b1_str.length(), b1.limit()); + } + Assert.assertEquals(s1.length(), b1.limit()); + Assert.assertEquals(0, b1.position()); + Assert.assertEquals(s1.length(), b1.remaining()); + + final String s2 = new String(s1.getBytes()); // true copy + final ByteBuffer b2 = CPUtils.to_ByteBuffer(s2, true); + Assert.assertEquals(s1.length(), b2.capacity()); + Assert.assertEquals(s1.length(), b2.limit()); + Assert.assertEquals(0, b2.position()); + Assert.assertEquals(s1.length(), b2.remaining()); + { + final byte[] b2_bytes = new byte[b2.remaining()]; + b2.get(b2_bytes); + b2.rewind(); // yada yada yada (relative get, absolute @ JDK13 + final String b2_str = new String(b2_bytes, StandardCharsets.UTF_8); + CPUtils.fprintf_td(System.err, "test01_enc_dec_file_ok.2c: b2: '%s', len %d/%d\n", b2_str, b2_str.length(), b2.limit()); + } + } + } +*/ + public static void main(final String args[]) { + org.junit.runner.JUnitCore.main(TestMemBuffers00.class.getName()); + } +} |