diff --git a/compiler/src/backend/llvm/builders/builtins/security_builtins_ops.cpp b/compiler/src/backend/llvm/builders/builtins/security_builtins_ops.cpp index 810cf264d..79714fc0a 100644 --- a/compiler/src/backend/llvm/builders/builtins/security_builtins_ops.cpp +++ b/compiler/src/backend/llvm/builders/builtins/security_builtins_ops.cpp @@ -422,32 +422,31 @@ namespace Sad { namespace LLVM { llvm::Value *SecurityBuiltinsCodeGen::emitBuiltinSecuritySecureRandom(std::shared_ptr inst) { - // Use BCryptGenRandom on Windows for cryptographic randomness - // Signature: NTSTATUS BCryptGenRandom(BCRYPT_ALG_HANDLE, PUCHAR, ULONG, ULONG) - // We use flag BCRYPT_USE_SYSTEM_PREFERRED_RNG = 2 with NULL handle + // (AR) عشوائي_آمن(الحد_الأدنى، الحد_الأقصى) — يستدعي + // sad_security_secure_random(i64, i64) -> i64 من وقت التشغيل + // المضمَّن (sad_embedded_runtime.c)، بنفس نمط بقيّة عائلة الأمن + // المستضافة (هاش/شفّر/فك_تشفير). هذا يستبدل نداءً سابقًا مباشرًا + // لِـBCryptGenRandom في LLVM IR كان: (أ) يفشل الربط دومًا — لم + // يُربط bcrypt.lib في أيّ من مسارات الربط الثلاثة، و(ب) يتجاهل + // معاملَي الحد الأدنى/الأقصى تمامًا (يُرجع عددًا كاملًا شبه غير + // محدود بدل مدى مطلوب). الآن: منطق التوليد (وحماية Windows/POSIX + // عبر مصدر عشوائيّة النظام) في مكان واحد قابل للاختبار. + // (EN) Calls sad_security_secure_random(i64,i64)->i64 in the embedded + // runtime, matching the rest of the hosted security family. This + // replaces a previous direct BCryptGenRandom call in LLVM IR that + // (a) never linked (bcrypt.lib was linked nowhere) and (b) ignored + // the min/max arguments entirely. + if (!inst || inst->operands.size() < 2) + return nullptr; + llvm::Value *minVal = cg_.resolveOperand(inst->operands[0]); + llvm::Value *maxVal = cg_.resolveOperand(inst->operands[1]); + if (!minVal || !maxVal) + return nullptr; auto i64Ty = llvm::Type::getInt64Ty(*cg_.context_); - auto i32Ty = llvm::Type::getInt32Ty(*cg_.context_); - auto i8Ty = llvm::Type::getInt8Ty(*cg_.context_); - auto ptrTy = llvm::PointerType::getUnqual(*cg_.context_); - - // Allocate 8 bytes on the stack for the random value - llvm::Value *buf = cg_.builder_->CreateAlloca(i64Ty, nullptr, "rng.buf"); - - // Call BCryptGenRandom(NULL, buf, 8, BCRYPT_USE_SYSTEM_PREFERRED_RNG=2) - auto *bcrType = llvm::FunctionType::get(i32Ty, {ptrTy, ptrTy, i32Ty, i32Ty}, false); - auto bcrFunc = cg_.module_->getOrInsertFunction("BCryptGenRandom", bcrType); - cg_.builder_->CreateCall(bcrFunc, { - llvm::ConstantPointerNull::get(llvm::cast(ptrTy)), - buf, - llvm::ConstantInt::get(i32Ty, 8), - llvm::ConstantInt::get(i32Ty, 2) // BCRYPT_USE_SYSTEM_PREFERRED_RNG - }); - - // Load the random value - llvm::Value *result = cg_.builder_->CreateLoad(i64Ty, buf, "rng.val"); - // Make it positive by masking off sign bit - result = cg_.builder_->CreateAnd(result, llvm::ConstantInt::get(i64Ty, 0x7FFFFFFFFFFFFFFF), "rng.pos"); - if (inst && inst->result.has_value()) + llvm::FunctionType *ft = llvm::FunctionType::get(i64Ty, {i64Ty, i64Ty}, false); + llvm::FunctionCallee fn = cg_.module_->getOrInsertFunction("sad_security_secure_random", ft); + llvm::Value *result = cg_.builder_->CreateCall(fn, {minVal, maxVal}, "secure_random.ret"); + if (inst->result.has_value()) cg_.context_info_.namedValues[inst->result->name] = result; return result; } diff --git a/language-truth/builtins/assertions.yaml b/language-truth/builtins/assertions.yaml index d58e8ef0b..68450ba10 100644 --- a/language-truth/builtins/assertions.yaml +++ b/language-truth/builtins/assertions.yaml @@ -260,9 +260,29 @@ functions: compiler_strategy: RUNTIME_CALL since: 0.1.0 status: stable - description_ar: عشوائي آمن - description_en: Secure random - params: [] + description_ar: >- + يُرجع عددًا صحيحًا عشوائيًّا ضمن المدى [الحدّ_الأدنى، الحدّ_الأقصى] + (كلاهما شامل)، مصدره CSPRNG حقيقيّ لنظام التشغيل — BCryptGenRandom على + Windows، /dev/urandom على Linux/macOS — لا مولِّد أرقام شبه عشوائيّ عاديّ + (rand()/srand()). مطابق بين المفسّر والمترجم منذ توحيد جولة توسيع مكتبة + التشفير (المرحلة ٠). مناسب لتوليد nonces/رموز جلسة/مفاتيح مؤقّتة؛ ليس + توليدًا لمفاتيح تشفير طويلة الأمد بحدّ ذاته (استعمل اشتقاق مفتاح مخصَّص + لذلك عند توفّره). + description_en: >- + Returns a random integer in [min, max] (inclusive) sourced from a real OS + CSPRNG — BCryptGenRandom on Windows, /dev/urandom on Linux/macOS — not a + plain PRNG (rand()/srand()). Identical behavior on interpreter and + compiler since the crypto-library-expansion RFC's phase 0. Suitable for + nonces/session tokens/short-lived keys; not a long-term key-generation + primitive by itself. + params: + - الحدّ_الأدنى — أصغر قيمة ممكنة للناتج (شاملة). + - الحدّ_الأقصى — أكبر قيمة ممكنة للناتج (شاملة). + examples: + - code: | + استورد تأكيدات + اطبع_سطر(عشوائي_آمن(1، 100) >= 1) + runnable: true lookup_table: name_only - cpp_id: BASE64_ENCODE canonical: ترميز_64 diff --git a/shared/builtins/generated/builtin_registry_generated.h b/shared/builtins/generated/builtin_registry_generated.h index 56bbcb3ee..48b881d7f 100644 --- a/shared/builtins/generated/builtin_registry_generated.h +++ b/shared/builtins/generated/builtin_registry_generated.h @@ -329,7 +329,7 @@ namespace Sad inline constexpr std::string_view DECRYPT = "فك_تشفير"; // (AR) تنظيف مدخلات inline constexpr std::string_view SANITIZE = "نظف"; - // (AR) عشوائي آمن + // (AR) يُرجع عددًا صحيحًا عشوائيًّا ضمن المدى [الحدّ_الأدنى، الحدّ_الأقصى] (كلاهما شامل)، مصدره CSPRNG حقيقيّ لنظام التشغيل — BCryptGenRandom على Windows، /dev/urandom على Linux/macOS — لا مولِّد أرقام شبه عشوائيّ عاديّ (rand()/srand()). مطابق بين المفسّر والمترجم منذ توحيد جولة توسيع مكتبة التشفير (المرحلة ٠). مناسب لتوليد nonces/رموز جلسة/مفاتيح مؤقّتة؛ ليس توليدًا لمفاتيح تشفير طويلة الأمد بحدّ ذاته (استعمل اشتقاق مفتاح مخصَّص لذلك عند توفّره). inline constexpr std::string_view SECURE_RANDOM = "عشوائي_آمن"; // (AR) ترميز Base64 inline constexpr std::string_view BASE64_ENCODE = "ترميز_64"; @@ -3070,7 +3070,7 @@ namespace Sad {Names::Assertions::ENCRYPT, "Assertions", "MODULE_FUNCTION", "ASSERTIONS", true, "تشفير نصّ بمفتاح عبر تدفق SHA-256-CTR (SHA-256(مفتاح‖nonce‖عدّاد) كتيار مفاتيح XOR مع النصّ). يُولَّد nonce عشوائيّ 8 بايت لكل استدعاء ويُضمَّن في مقدّمة الناتج (8 بايت + النصّ المشفَّر)، والكلّ مُرمَّز ست عشريًّا. التنفيذ مطابق حرفيًّا بين المفسّر والمترجم (tools/compiler/runtime/sad_embedded_runtime.c، sad_security_encrypt) — ما يُشفَّر بأحد المحرِّكين يُفكّ بالآخر (تبادليّة كاملة). طول المفتاح غير محدود. ⚠️ للاستخدام الخفيف فقط — ليس بديلًا عن معايير معتمَدة كـAES-GCM لحماية بيانات حسّاسة حقًّا (لا مصادقة/AEAD، ولا nonce مُولَّد بمولّد عشوائيّ مُعتمَد تشفيريًّا).", "نص — النصّ الأصليّ المراد تشفيره، مفتاح — مفتاح التشفير (أيّ طول)", ""}, {Names::Assertions::DECRYPT, "Assertions", "MODULE_FUNCTION", "ASSERTIONS", true, "فكّ تشفير ناتج «شفر» — يقرأ nonce الـ8 بايت من مقدّمة السلسلة الست عشرية، ثمّ يعيد توليد تيار مفاتيح SHA-256-CTR بنفس المفتاح لاستعادة النصّ الأصليّ. ⚠️ مسار الخطأ عند إدخال ست عشريّ غير صالح (طول فرديّ/أقصر من 16 حرفًا/رموز غير ست عشرية) **متباعد حاليًّا بين المحرِّكين**: المفسّر يرمي استثناءً قابلًا للالتقاط بـ`حاول`/`امسك`؛ المترجم يطبع رسالة على stderr ويُعيد المدخل كما هو دون رمي استثناء لغويّ — فجوة توحيد معروفة غير مُغلَقة بعد (راجع RISK.md لقسم 09_المكتبة_القياسية/04_تشفير). المسار السعيد (فكّ تشفير ناتج صحيح من «شفر») مطابق حرفيًّا بين المحرِّكين.", "نص_مشفر — الناتج الست عشريّ من «شفر»، مفتاح — نفس المفتاح المُستخدَم في التشفير", ""}, {Names::Assertions::SANITIZE, "Assertions", "MODULE_FUNCTION", "ASSERTIONS", true, "تنظيف مدخلات", "", ""}, - {Names::Assertions::SECURE_RANDOM, "Assertions", "MODULE_FUNCTION", "ASSERTIONS", true, "عشوائي آمن", "", ""}, + {Names::Assertions::SECURE_RANDOM, "Assertions", "MODULE_FUNCTION", "ASSERTIONS", true, "يُرجع عددًا صحيحًا عشوائيًّا ضمن المدى [الحدّ_الأدنى، الحدّ_الأقصى] (كلاهما شامل)، مصدره CSPRNG حقيقيّ لنظام التشغيل — BCryptGenRandom على Windows، /dev/urandom على Linux/macOS — لا مولِّد أرقام شبه عشوائيّ عاديّ (rand()/srand()). مطابق بين المفسّر والمترجم منذ توحيد جولة توسيع مكتبة التشفير (المرحلة ٠). مناسب لتوليد nonces/رموز جلسة/مفاتيح مؤقّتة؛ ليس توليدًا لمفاتيح تشفير طويلة الأمد بحدّ ذاته (استعمل اشتقاق مفتاح مخصَّص لذلك عند توفّره).", "الحدّ_الأدنى — أصغر قيمة ممكنة للناتج (شاملة).، الحدّ_الأقصى — أكبر قيمة ممكنة للناتج (شاملة).", ""}, {Names::Assertions::BASE64_ENCODE, "Assertions", "MODULE_FUNCTION", "ASSERTIONS", true, "ترميز Base64", "", ""}, // ─── Maps (90) ─── {Names::Maps::JSON_PARSE, "Maps", "MODULE_FUNCTION", "MAPS", true, "تحليل JSON", "", ""}, diff --git "a/tests/behavior/sections/09_\330\247\331\204\331\205\331\203\330\252\330\250\330\251_\330\247\331\204\331\202\331\212\330\247\330\263\331\212\330\251/04_\330\252\330\264\331\201\331\212\330\261/154_stdlib_security_secure_random.\330\265" "b/tests/behavior/sections/09_\330\247\331\204\331\205\331\203\330\252\330\250\330\251_\330\247\331\204\331\202\331\212\330\247\330\263\331\212\330\251/04_\330\252\330\264\331\201\331\212\330\261/154_stdlib_security_secure_random.\330\265" new file mode 100644 index 000000000..bccd33234 --- /dev/null +++ "b/tests/behavior/sections/09_\330\247\331\204\331\205\331\203\330\252\330\250\330\251_\330\247\331\204\331\202\331\212\330\247\330\263\331\212\330\251/04_\330\252\330\264\331\201\331\212\330\261/154_stdlib_security_secure_random.\330\265" @@ -0,0 +1,42 @@ +# @description عشوائي_آمن — التزام بالمدى [الأدنى،الأقصى] + عدم تكرار حتميّ؛ شعاع تراجع (regression) لثغرتين حقيقيّتين في مسار المترجم أصلحتهما هذه الجولة (توسيع مكتبة التشفير، المرحلة ٠) +# @expected: 5\nصحيح\nصحيح +# @priority P1 + +# ============================================================ +# قبل هذه الجولة، مسار المترجم (sadc) لِـعشوائي_آمن كان معطوبًا بعلّتين: +# ١) يستدعي BCryptGenRandom مباشرة في LLVM IR دون ربط bcrypt.lib في أيّ +# من مسارات الربط الثلاثة — فشل الربط (undefined symbol) دومًا على +# Windows؛ أيّ برنامج .ص مُصرَّف يستدعي عشوائي_آمن كان يفشل البناء. +# ٢) حتى لو رُبط، كان يتجاهل معاملَي الحدّ الأدنى/الأقصى بالكامل ويُرجع +# عددًا كاملًا شبه غير محدود بدل قيمة ضمن المدى المطلوب. +# الإصلاح: عشوائي_آمن يستدعي الآن sad_security_secure_random(i64,i64)->i64 +# من وقت التشغيل المضمَّن (نفس نمط هاش/شفّر/فك_تشفير)، الذي يستعمل مصدر +# عشوائيّة نظام التشغيل الحقيقيّ (BCryptGenRandom عبر تحميل ديناميكيّ على +# Windows، /dev/urandom على POSIX) بدل srand(time(NULL))+rand() القابل +# للتنبّؤ القديم. +# ============================================================ +استورد تأكيدات + +# نطاق نقطة واحدة [5،5] — يجب أن يكون الناتج 5 دائمًا (يتحقّق من احترام +# المعاملين لا تجاهلهما). +اطبع_سطر(عشوائي_آمن(5، 5)) + +# نطاق واسع [1،100] عبر 50 استدعاء — يتحقّق أنّ كل قيمة ضمن المدى فعلًا. +متغير ك = 0 +متغير ضمن_النطاق = صحيح +بينما (ك < 50) + متغير ع = عشوائي_آمن(1، 100) + إذا (ع < 1 أو ع > 100) + ضمن_النطاق = خطأ + نهاية + ك = ك + 1 +نهاية +اطبع_سطر(ضمن_النطاق) + +# عدم تكرار حتميّ: نطاق واسع جدًّا [1،1000000000] — احتمال تطابق قيمتين +# ضئيل جدًّا (~1 في المليار) إن كان المصدر عشوائيًّا فعلًا؛ لو عاد الباغ +# القديم (بذرة الوقت بالثانية) لكانت القيمتان متطابقتين حتمًا عند تشغيل +# الاستدعاءين ضمن نفس الثانية. +متغير أ = عشوائي_آمن(1، 1000000000) +متغير ب = عشوائي_آمن(1، 1000000000) +اطبع_سطر(أ != ب) diff --git "a/tests/behavior/sections/09_\330\247\331\204\331\205\331\203\330\252\330\250\330\251_\330\247\331\204\331\202\331\212\330\247\330\263\331\212\330\251/COVERAGE.md" "b/tests/behavior/sections/09_\330\247\331\204\331\205\331\203\330\252\330\250\330\251_\330\247\331\204\331\202\331\212\330\247\330\263\331\212\330\251/COVERAGE.md" index ac746deb9..c52f7de94 100644 --- "a/tests/behavior/sections/09_\330\247\331\204\331\205\331\203\330\252\330\250\330\251_\330\247\331\204\331\202\331\212\330\247\330\263\331\212\330\251/COVERAGE.md" +++ "b/tests/behavior/sections/09_\330\247\331\204\331\205\331\203\330\252\330\250\330\251_\330\247\331\204\331\202\331\212\330\247\330\263\331\212\330\251/COVERAGE.md" @@ -1,4 +1,4 @@ -# COVERAGE — 09_المكتبة_القياسية (129 ملفاً) +# COVERAGE — 09_المكتبة_القياسية (130 ملفاً) > جدول تدقيق التغطية (GR-01): كل ميزة في هذا القسم ↔ اختبار يغطّيها. > ✅ مغطّى · ⚠️ جزئي · ❌ فجوة. حدِّثه ضمن DoD أي ميزة جديدة. @@ -11,7 +11,7 @@ | رياضيات (جذر/لوغاريتم/أس/π) | ✅ | `02_رياضيات/` (6) | — | | نصوص متقدمة | ✅ | `03_نصوص/` (8) | — | | شبكة (TCP/HTTP/websocket) | ⚠️ | `05_شبكة/` (90) | — | -| تشفير (هاش/شفّر/فك_تشفير — SHA-256/SHA-256-CTR) | ✅ | `04_تشفير/` (4) | — | +| تشفير (هاش/شفّر/فك_تشفير/عشوائي_آمن) | ✅ | `04_تشفير/` (5) | — | | أساسيات (ملفات/مدى/تأكد) | ❌ | — | — | | خرائط متقدمة/JSON/تاريخ/regex | ❌ | — | — | | مقابس | ❌ | — | — | @@ -31,3 +31,11 @@ متباعد فعليًّا بين المحرِّكين (استثناء قابل للالتقاط بالمفسّر مقابل رسالة stderr بالمترجم بلا استثناء لغويّ) وغير مُغلَق؛ راجع RISK.md. `_negative/` في هذا القسم فارغ حاليًّا في كل المستودع — لا سابقة عمل حيّة لتوليد اختبار سلبي منها. +- **`عشوائي_آمن` (المرحلة ٠ من RFC توسيع مكتبة التشفير):** فحص السوت كشف عِلَّتين حقيقيّتين + في مسار المترجم كانتا تسبقان هذه الجولة — (أ) نداء مباشر لِـ`BCryptGenRandom` في LLVM IR + دون ربط `bcrypt.lib` في أيّ من مسارات الربط الثلاثة، فيفشل الربط دومًا على Windows؛ + (ب) تجاهل تامّ لمعاملَي الحدّ الأدنى/الأقصى. أُصلحتا معًا: الآن يستدعي المترجم + `sad_security_secure_random` من وقت التشغيل المضمَّن (نفس نمط بقيّة عائلة الأمن)، ومصدر + العشوائيّة نفسه CSPRNG حقيقيّ (BCryptGenRandom/`/dev/urandom` حسب المنصّة) بدل + `srand(time(NULL))+rand()` القابل للتنبّؤ القديم. `154_stdlib_security_secure_random.ص` + شعاع تراجع لكلتا العلّتين (التزام المدى + عدم تكرار حتميّ عبر 10 جولات burn-in). diff --git a/tools/compiler/compiler_driver_android_linker.cpp b/tools/compiler/compiler_driver_android_linker.cpp index ccf1c1ffb..dbdd587af 100644 --- a/tools/compiler/compiler_driver_android_linker.cpp +++ b/tools/compiler/compiler_driver_android_linker.cpp @@ -340,12 +340,36 @@ long long sad_security_timestamp(void) { return (long long)time(NULL); } +/* أندرويد نظام تشغيل مضيف دائمًا (لا وضع حرّ) — /dev/urandom متاح دون أذونات + * خاصّة على كل إصدارات API، فلا حاجة لتحميل مكتبة NDK إضافيّة أو تمييز حرّ/مضيف + * كما في sad_embedded_runtime.c. */ +static int sad_crypto_os_random(unsigned char* buf, size_t len) { + FILE* f = fopen("/dev/urandom", "rb"); + size_t got; + if (!f) return 0; + got = fread(buf, 1, len, f); + fclose(f); + return got == len; +} + long long sad_security_secure_random(long long min_val, long long max_val) { - static int seeded = 0; - if (!seeded) { srand((unsigned int)time(NULL)); seeded = 1; } + unsigned long long range; if (min_val >= max_val) return min_val; - long long range = max_val - min_val + 1; - return min_val + (long long)(rand() % (int)range); + range = (unsigned long long)(max_val - min_val + 1); + { + unsigned char bytes[8]; + if (sad_crypto_os_random(bytes, sizeof(bytes))) { + unsigned long long r = 0; + int i; + for (i = 0; i < 8; ++i) r = (r << 8) | bytes[i]; + return min_val + (long long)(r % range); + } + } + { + static int seeded = 0; + if (!seeded) { srand((unsigned int)time(NULL)); seeded = 1; } + return min_val + (long long)(rand() % (int)range); + } } )"; diff --git a/tools/compiler/compiler_driver_build_utils.cpp b/tools/compiler/compiler_driver_build_utils.cpp index a4db34843..cccd81753 100644 --- a/tools/compiler/compiler_driver_build_utils.cpp +++ b/tools/compiler/compiler_driver_build_utils.cpp @@ -581,16 +581,32 @@ namespace sad bool is_clang = (c_compiler.find("clang") != std::string::npos || c_compiler.find("CLANG") != std::string::npos); + // (AR) SAD_FREESTANDING يُميَّز فقط عند --freestanding: يُبقي + // sad_embedded_runtime.c على مسار العشوائيّة القديم (srand/rand + // عبر libc فقط، بلا نداء نظام تشغيل) لأهداف الوضع الحرّ التي قد + // لا يوجد تحتها نظام تشغيل مضيف أصلًا (كنواة sad-os). الأهداف + // المستضافة (الافتراضي) تحصل على CSPRNG حقيقيّ — انظر + // sad_crypto_os_random في sad_embedded_runtime.c. + // (EN) SAD_FREESTANDING is defined only for --freestanding builds, so + // sad_embedded_runtime.c keeps the old libc-only rand() path on + // that target (may have no host OS at all, e.g. sad-os kernel). + // Hosted targets (default) get a real OS-backed CSPRNG. + const std::string freestanding_define = options_.freestanding ? "SAD_FREESTANDING" : ""; + if (is_msvc) { command = "\"" + c_compiler + "\" /c /O2 /TC /W0 /nologo"; command += options_.link_static ? " /MT" : " /MD"; + if (!freestanding_define.empty()) + command += " /D" + freestanding_define; command += " /Fo\"" + obj_output_path + "\""; command += " \"" + c_source_path + "\""; } else { command = "\"" + c_compiler + "\" -c -O2 -w"; + if (!freestanding_define.empty()) + command += " -D" + freestanding_define; #ifdef _WIN32 if (is_clang) { diff --git a/tools/compiler/runtime/sad_embedded_runtime.c b/tools/compiler/runtime/sad_embedded_runtime.c index 91cd5ce22..7a9409c37 100644 --- a/tools/compiler/runtime/sad_embedded_runtime.c +++ b/tools/compiler/runtime/sad_embedded_runtime.c @@ -606,21 +606,79 @@ static void sad_sha256_raw(const unsigned char *data, size_t len, unsigned char } } -/* (AR) مولّد 64 بت بسيط (غير آمن تشفيريًّا) لِـnonce تشفير-التيار — نفس مستوى - * الجودة المستخدَم أصلًا في sad_security_secure_random أعلاه. */ +/* (AR) مصدر عشوائيّة النظام (CSPRNG حقيقيّ) — Windows: BCryptGenRandom (تحميل + * ديناميكيّ عبر LoadLibraryA/GetProcAddress، فلا حاجة لربط bcrypt.lib في + * أيّ من مسارات الربط الثلاثة). POSIX: /dev/urandom. يُرجع 1 عند النجاح. + * غير مُستعمَل تحت SAD_FREESTANDING (انظر SAD_FREESTANDING أدناه) — الهدف + * الحرّ قد لا يوجد تحته نظام تشغيل مضيف أصلًا (كنواة sad-os)، فيبقى على + * مسار libc القديم عمدًا (فجوة معروفة، موثَّقة في RFC توسيع مكتبة + * التشفير — "أسئلة غير محسومة": مصدر عشوائيّة للوضع الحرّ البحت). + * (EN) Real OS-backed CSPRNG source. Not used under SAD_FREESTANDING — that + * target may have no host OS at all, so it intentionally keeps the old + * libc-only path (tracked gap, see the crypto-library-expansion RFC). */ +#ifndef SAD_FREESTANDING +#ifdef _WIN32 +static int sad_crypto_os_random(unsigned char *buf, size_t len) +{ + typedef long (WINAPI *BCryptGenRandomFn)(void *, unsigned char *, unsigned long, unsigned long); + static BCryptGenRandomFn fn = NULL; + static int tried = 0; + if (!tried) + { + HMODULE h; + tried = 1; + h = LoadLibraryA("bcrypt.dll"); + if (h) + fn = (BCryptGenRandomFn)(void *)GetProcAddress(h, "BCryptGenRandom"); + } + /* BCRYPT_USE_SYSTEM_PREFERRED_RNG = 0x00000002 */ + return (fn && fn(NULL, buf, (unsigned long)len, 0x00000002) == 0) ? 1 : 0; +} +#else +static int sad_crypto_os_random(unsigned char *buf, size_t len) +{ + FILE *f = fopen("/dev/urandom", "rb"); + size_t got; + if (!f) + return 0; + got = fread(buf, 1, len, f); + fclose(f); + return got == len; +} +#endif +#endif /* !SAD_FREESTANDING */ + +/* (AR) مولّد nonce 64 بت لتشفير-التيار — CSPRNG حقيقيّ على الأهداف المستضافة؛ + * يتراجع لمسار srand/rand القديم (غير آمن تشفيريًّا) فقط تحت + * SAD_FREESTANDING أو إن تعذّر الوصول لمصدر عشوائيّة النظام. */ static unsigned long long sad_crypto_random_u64(void) { - static int seeded = 0; - unsigned long long r = 0; - int i; - if (!seeded) +#ifndef SAD_FREESTANDING + { + unsigned char bytes[8]; + if (sad_crypto_os_random(bytes, sizeof(bytes))) + { + unsigned long long r = 0; + int i; + for (i = 0; i < 8; ++i) + r = (r << 8) | bytes[i]; + return r; + } + } +#endif { - srand((unsigned int)time(NULL)); - seeded = 1; + static int seeded = 0; + unsigned long long r = 0; + int i; + if (!seeded) + { + srand((unsigned int)time(NULL)); + seeded = 1; + } + for (i = 0; i < 4; ++i) + r = (r << 16) | ((unsigned long long)rand() & 0xFFFFu); + return r; } - for (i = 0; i < 4; ++i) - r = (r << 16) | ((unsigned long long)rand() & 0xFFFFu); - return r; } /* هاش / Hash — SHA-256 (يطابق FIPS 180-4 ومفسّر ص حرفيًّا) */ @@ -874,19 +932,37 @@ const char *sad_security_sanitize(const char *text) return result; } -/* عشوائي_آمن / Secure random in range */ +/* عشوائي_آمن / Secure random in range — CSPRNG حقيقيّ على الأهداف المستضافة + * (انظر sad_crypto_os_random أعلاه)؛ يتراجع لمسار srand/rand القديم فقط تحت + * SAD_FREESTANDING أو إن تعذّر الوصول لمصدر عشوائيّة النظام. */ long long sad_security_secure_random(long long min_val, long long max_val) { - static int seeded = 0; - if (!seeded) - { - srand((unsigned int)time(NULL)); - seeded = 1; - } + unsigned long long range; if (min_val >= max_val) return min_val; - long long range = max_val - min_val + 1; - return min_val + (long long)(rand() % (int)range); + range = (unsigned long long)(max_val - min_val + 1); +#ifndef SAD_FREESTANDING + { + unsigned char bytes[8]; + if (sad_crypto_os_random(bytes, sizeof(bytes))) + { + unsigned long long r = 0; + int i; + for (i = 0; i < 8; ++i) + r = (r << 8) | bytes[i]; + return min_val + (long long)(r % range); + } + } +#endif + { + static int seeded = 0; + if (!seeded) + { + srand((unsigned int)time(NULL)); + seeded = 1; + } + return min_val + (long long)(rand() % (int)range); + } } /* ترميز_64 / Base64 encode */