From a1ae7744ea7c708a366688cfebe435388d7ce479 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 1 Jul 2026 09:07:11 -0400 Subject: [PATCH 001/105] chore(lua): add prep packet --- joern-cli/frontends/lua2cpg/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 joern-cli/frontends/lua2cpg/README.md diff --git a/joern-cli/frontends/lua2cpg/README.md b/joern-cli/frontends/lua2cpg/README.md new file mode 100644 index 000000000000..11fc182b3282 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/README.md @@ -0,0 +1,10 @@ +# lua2cpg Preparation + +This directory exists only on the preparation branch. + +It is a scaffold for future upstream work and does not claim: + +- final maintainer-approved frontend identity +- final schema or traversal API shape +- QueryDB readiness +- upstream-ready Lua support From 879e8d61a271b6eb19a8d0fe108ef579439b5a68 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 1 Jul 2026 09:10:24 -0400 Subject: [PATCH 002/105] chore(lua): add frontend scaffold --- joern-cli/frontends/lua2cpg/build.sbt | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 joern-cli/frontends/lua2cpg/build.sbt diff --git a/joern-cli/frontends/lua2cpg/build.sbt b/joern-cli/frontends/lua2cpg/build.sbt new file mode 100644 index 000000000000..ebd1da295671 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/build.sbt @@ -0,0 +1,12 @@ +name := "lua2cpg" + +dependsOn( + Projects.x2cpg % "compile->compile;test->test", + Projects.linterRules % ScalafixConfig +) + +libraryDependencies ++= Seq( + "org.scalatest" %% "scalatest" % Versions.scalatest % Test +) + +enablePlugins(JavaAppPackaging) From bab0adc00cf28075e86780d4a3c85876f735d0f8 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 1 Jul 2026 09:16:55 -0400 Subject: [PATCH 003/105] chore(lua): wire frontend into build graph --- build.sbt | 2 ++ joern-cli/build.sbt | 2 ++ project/Projects.scala | 1 + 3 files changed, 5 insertions(+) diff --git a/build.sbt b/build.sbt index 715eeb74c1ab..7b79adc0692c 100644 --- a/build.sbt +++ b/build.sbt @@ -25,6 +25,7 @@ lazy val swiftsrc2cpg = Projects.swiftsrc2cpg lazy val csharpsrc2cpg = Projects.csharpsrc2cpg lazy val abap2cpg = Projects.abap2cpg lazy val rust2cpg = Projects.rust2cpg +lazy val lua2cpg = Projects.lua2cpg lazy val linterRules = Projects.linterRules lazy val root = project @@ -51,6 +52,7 @@ lazy val root = project csharpsrc2cpg, abap2cpg, rust2cpg, + lua2cpg, linterRules ) .dependsOn(linterRules % ScalafixConfig) diff --git a/joern-cli/build.sbt b/joern-cli/build.sbt index a897ebf5655c..2624bed7b762 100644 --- a/joern-cli/build.sbt +++ b/joern-cli/build.sbt @@ -48,6 +48,7 @@ lazy val rubysrc2cpg = project.in(file("frontends/rubysrc2cpg")) lazy val gosrc2cpg = project.in(file("frontends/gosrc2cpg")) lazy val csharpsrc2cpg = project.in(file("frontends/csharpsrc2cpg")) lazy val rust2cpg = project.in(file("frontends/rust2cpg")) +lazy val lua2cpg = project.in(file("frontends/lua2cpg")) Universal / mappings ++= frontendMappings("kotlin2cpg", (kotlin2cpg / stage).value) Universal / mappings ++= frontendMappings("abap2cpg", (abap2cpg / stage).value) @@ -63,6 +64,7 @@ Universal / mappings ++= frontendMappings("rubysrc2cpg", (rubysrc2cpg / stage).v Universal / mappings ++= frontendMappings("gosrc2cpg", (gosrc2cpg / stage).value) Universal / mappings ++= frontendMappings("csharpsrc2cpg", (csharpsrc2cpg / stage).value) Universal / mappings ++= frontendMappings("rust2cpg", (rust2cpg / stage).value) +Universal / mappings ++= frontendMappings("lua2cpg", (lua2cpg / stage).value) lazy val cpgVersionFile = taskKey[File]("persist cpg version in file (e.g. for schema-extender)") cpgVersionFile := { diff --git a/project/Projects.scala b/project/Projects.scala index 8d98ce0ac763..dfdf4e18bb5e 100644 --- a/project/Projects.scala +++ b/project/Projects.scala @@ -26,6 +26,7 @@ object Projects { lazy val csharpsrc2cpg = project.in(frontendsRoot / "csharpsrc2cpg") lazy val abap2cpg = project.in(frontendsRoot / "abap2cpg") lazy val rust2cpg = project.in(frontendsRoot / "rust2cpg") + lazy val lua2cpg = project.in(frontendsRoot / "lua2cpg") lazy val linterRules = project.in(file("linter-rules")) From 1016e33ddc77b6b6c1636ed99ddfc583453bb7fc Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 8 Jul 2026 02:04:26 -0400 Subject: [PATCH 004/105] feat(lua2cpg): add runnable Lua entry smoke --- joern-cli/frontends/lua2cpg/README.md | 19 ++++++++++++ joern-cli/frontends/lua2cpg/build.sbt | 2 +- .../main/scala/io/joern/lua2cpg/Lua2Cpg.scala | 21 ++++++++++++++ .../main/scala/io/joern/lua2cpg/Main.scala | 21 ++++++++++++++ .../lua2cpg/passes/LuaFileInventoryPass.scala | 29 +++++++++++++++++++ 5 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/Lua2Cpg.scala create mode 100644 joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/Main.scala create mode 100644 joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/passes/LuaFileInventoryPass.scala diff --git a/joern-cli/frontends/lua2cpg/README.md b/joern-cli/frontends/lua2cpg/README.md index 11fc182b3282..ee34ebd7e143 100644 --- a/joern-cli/frontends/lua2cpg/README.md +++ b/joern-cli/frontends/lua2cpg/README.md @@ -8,3 +8,22 @@ It is a scaffold for future upstream work and does not claim: - final schema or traversal API shape - QueryDB readiness - upstream-ready Lua support + +## E1 Local Smoke + +```bash +sbt 'lua2cpg/testOnly io.joern.lua2cpg.DLinkLuCIEntrySmokeTest' +sbt 'lua2cpg/test' +sbt 'lua2cpg/stage' +``` + +Expected result: + +- `DLinkLuCIEntrySmokeTest` observes `META_DATA.language == "LUA"`. +- `DLinkLuCIEntrySmokeTest` observes `cgi.lua`, `uci.lua`, and `version.lua` + as CPG `FILE` nodes. +- `lua2cpg/stage` exits `0` and produces a staged `lua2cpg` command. + +This E1 smoke proves only the runnable Lua frontend entry and file inventory. +It does not claim Lua parsing, bytecode decode, AST semantics, dataflow, +QueryDB, sanitizer, or report construction. diff --git a/joern-cli/frontends/lua2cpg/build.sbt b/joern-cli/frontends/lua2cpg/build.sbt index ebd1da295671..1c390146efdc 100644 --- a/joern-cli/frontends/lua2cpg/build.sbt +++ b/joern-cli/frontends/lua2cpg/build.sbt @@ -9,4 +9,4 @@ libraryDependencies ++= Seq( "org.scalatest" %% "scalatest" % Versions.scalatest % Test ) -enablePlugins(JavaAppPackaging) +enablePlugins(JavaAppPackaging, LauncherJarPlugin) diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/Lua2Cpg.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/Lua2Cpg.scala new file mode 100644 index 000000000000..0aaca3d7fc5c --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/Lua2Cpg.scala @@ -0,0 +1,21 @@ +package io.joern.lua2cpg + +import io.joern.lua2cpg.passes.LuaFileInventoryPass +import io.joern.x2cpg.X2Cpg.withNewEmptyCpg +import io.joern.x2cpg.X2CpgFrontend +import io.joern.x2cpg.passes.frontend.MetaDataPass +import io.shiftleft.codepropertygraph.generated.Cpg + +import scala.util.Try + +class Lua2Cpg extends X2CpgFrontend { + override type ConfigType = Config + override val defaultConfig: Config = Config() + + override def createCpg(config: Config): Try[Cpg] = { + withNewEmptyCpg(config.outputPath, config) { (cpg, config) => + new MetaDataPass(cpg, "LUA", config.inputPath).createAndApply() + new LuaFileInventoryPass(cpg, config).createAndApply() + } + } +} diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/Main.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/Main.scala new file mode 100644 index 000000000000..e7fd342ff0c7 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/Main.scala @@ -0,0 +1,21 @@ +package io.joern.lua2cpg + +import io.joern.lua2cpg.Frontend.cmdLineParser +import io.joern.x2cpg.{X2CpgConfig, X2CpgMain} +import scopt.OParser + +final case class Config(override val genericConfig: X2CpgConfig.GenericConfig = X2CpgConfig.GenericConfig()) + extends X2CpgConfig[Config] { + override def withGenericConfig(value: X2CpgConfig.GenericConfig): Config = + copy(genericConfig = value) +} + +private object Frontend { + val cmdLineParser: OParser[Unit, Config] = { + val builder = OParser.builder[Config] + import builder.* + OParser.sequence(programName("lua2cpg")) + } +} + +object Main extends X2CpgMain(new Lua2Cpg(), cmdLineParser) diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/passes/LuaFileInventoryPass.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/passes/LuaFileInventoryPass.scala new file mode 100644 index 000000000000..c3f46774d7c7 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/passes/LuaFileInventoryPass.scala @@ -0,0 +1,29 @@ +package io.joern.lua2cpg.passes + +import io.joern.lua2cpg.Config +import io.joern.x2cpg.SourceFiles +import io.shiftleft.codepropertygraph.generated.Cpg +import io.shiftleft.codepropertygraph.generated.nodes.NewFile +import io.shiftleft.passes.CpgPass +import io.shiftleft.semanticcpg.utils.FileUtil.* + +import java.nio.file.Paths + +class LuaFileInventoryPass(cpg: Cpg, config: Config) extends CpgPass(cpg) { + + override def run(diffGraph: DiffGraphBuilder): Unit = { + val inputRoot = Paths.get(config.inputPath).absolutePathAsString + val luaFiles = SourceFiles.determine( + inputPath = inputRoot, + sourceFileExtensions = Set(".lua"), + ignoredDefaultRegex = Some(config.defaultIgnoredFilesRegex), + ignoredFilesRegex = Some(config.ignoredFilesRegex), + ignoredFilesPath = Some(config.ignoredFiles) + )() + + luaFiles.zipWithIndex.foreach { case (file, index) => + val relativeName = SourceFiles.toRelativePath(file, inputRoot) + diffGraph.addNode(NewFile().name(relativeName).order(index + 1)) + } + } +} From c4950936f393eb9c70745631083685e49c825af8 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 8 Jul 2026 03:15:43 -0400 Subject: [PATCH 005/105] Add Lua bytecode decoder model --- .../lua2cpg/bytecode/LuaBytecodeDecoder.scala | 322 ++++++++++++++++++ .../lua2cpg/bytecode/LuaBytecodeModel.scala | 131 +++++++ 2 files changed, 453 insertions(+) create mode 100644 joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaBytecodeDecoder.scala create mode 100644 joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaBytecodeModel.scala diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaBytecodeDecoder.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaBytecodeDecoder.scala new file mode 100644 index 000000000000..c0a3500735b4 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaBytecodeDecoder.scala @@ -0,0 +1,322 @@ +package io.joern.lua2cpg.bytecode + +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.nio.charset.{CharacterCodingException, StandardCharsets} + +object LuaBytecodeDecoder { + private val LuaMagic: Array[Byte] = Array(0x1b.toByte, 0x4c.toByte, 0x75.toByte, 0x61.toByte) + private val Lua51Version: Int = 0x51 + private val DefaultInputKind: String = "lua-bytecode" + private val SuccessDiagnosticKind = "accepted" + private val SeverityInfo = "info" + private val SeverityError = "error" + private val NumberModeFloating = "floating" + private val NumberModeIntegral = "integral" + + def decode(path: String, bytes: Array[Byte]): LuaBytecodeDecodeResult = { + val reader = new Reader(bytes) + reader.decode(path) + } + + private final class Reader(bytes: Array[Byte]) { + private var index: Int = 0 + private var version: Int = 0 + private var format: Int = 0 + private var endianFlag: Int = 1 + private var byteOrder: ByteOrder = ByteOrder.LITTLE_ENDIAN + private var intSize: Int = 4 + private var sizeTSize: Int = 8 + private var instructionSize: Int = 4 + private var luaNumberSize: Int = 8 + private var integralFlag: Int = 0 + private var currentProfileId: Option[String] = None + + def decode(path: String): LuaBytecodeDecodeResult = { + if (bytes.length < LuaMagic.length || !bytes.take(LuaMagic.length).sameElements(LuaMagic)) { + return rejected(path, "not-lua-bytecode", "input does not start with Lua bytecode magic") + } + + index = LuaMagic.length + try { + decodeHeader() + val root = decodePrototype("root", None, Vector.empty) + accepted(path, root) + } catch { + case error: DecodeFailure => rejected(path, error.kind, error.message) + } + } + + private def decodeHeader(): Unit = { + version = readByte() + format = readByte() + endianFlag = readByte() + intSize = readByte() + sizeTSize = readByte() + instructionSize = readByte() + luaNumberSize = readByte() + integralFlag = readByte() + + if (version != Lua51Version) { + reject("unsupported-bytecode-version", f"expected Lua bytecode version 0x51, got 0x$version%02x") + } + validateImplementedHeader() + byteOrder = endianFlag match { + case 0 => ByteOrder.BIG_ENDIAN + case 1 => ByteOrder.LITTLE_ENDIAN + } + currentProfileId = Some(profileId) + } + + private def validateImplementedHeader(): Unit = { + if (format != 0) { + reject("unsupported-bytecode-profile", s"unsupported Lua 5.1 bytecode format $format") + } + if (!Set(0, 1).contains(endianFlag)) { + reject("unsupported-bytecode-profile", s"unsupported Lua 5.1 endianness flag $endianFlag") + } + if (intSize <= 0) { + reject("unsupported-bytecode-profile", s"unsupported Lua 5.1 int size $intSize") + } + if (sizeTSize <= 0) { + reject("unsupported-bytecode-profile", s"unsupported Lua 5.1 size_t size $sizeTSize") + } + if (instructionSize != 4) { + reject("unsupported-bytecode-profile", s"unsupported Lua 5.1 instruction size $instructionSize") + } + if (luaNumberSize != 8) { + reject("unsupported-bytecode-profile", s"unsupported Lua 5.1 lua_Number size $luaNumberSize") + } + if (integralFlag != 0) { + reject("unsupported-bytecode-profile", s"unsupported Lua 5.1 number mode integral_flag=$integralFlag") + } + } + + private def decodePrototype( + prototypeId: String, + parentPrototypeId: Option[String], + ordinalPath: Vector[Int] + ): LuaPrototype = { + val sourceName = readString() + val firstLine = readUInt() + val lastLine = readUInt() + val upvalueCount = readByte() + val numParams = readByte() + val isVararg = (readByte() & 0x02) != 0 + val maxStack = readByte() + val instructions = readVector(readUInt(), pc => decodeInstruction(pc)) + val constants = readVector(readUInt(), constantIndex => decodeConstant(constantIndex)) + val nested = readVector(readUInt(), childOrdinal => { + val childId = s"$prototypeId.$childOrdinal" + decodePrototype(childId, Some(prototypeId), ordinalPath :+ childOrdinal) + }) + val lineNumbers = readVector(readUInt(), _ => readUInt()) + val locals = readVector(readUInt(), _ => LuaLocal(readString(), readUInt(), readUInt())) + val upvalueNames = readVector(readUInt(), _ => readString()) + + LuaPrototype( + prototypeId = prototypeId, + parentPrototypeId = parentPrototypeId, + ordinalPath = ordinalPath, + sourceName = sourceName, + firstLine = firstLine, + lastLine = lastLine, + upvalueCount = upvalueCount, + numParams = numParams, + isVararg = isVararg, + maxStack = maxStack, + instructions = instructions, + constants = constants, + nested = nested, + lineNumbers = lineNumbers, + locals = locals, + upvalueNames = upvalueNames + ) + } + + private def decodeInstruction(pc: Int): LuaInstruction = { + val raw = readUInt32() + val opcodeCode = bits(raw, 0, 6).toInt + val opcode = LuaOpcode.fromCode(opcodeCode).getOrElse { + reject("malformed-constant", s"invalid opcode $opcodeCode at pc $pc") + } + val a = bits(raw, 6, 8).toInt + opcode.mode match { + case LuaInstructionMode.Abc => + LuaInstruction( + pc = pc, + opcode = opcode, + mode = opcode.mode, + a = a, + b = bits(raw, 23, 9).toInt, + c = Some(bits(raw, 14, 9).toInt) + ) + case LuaInstructionMode.ABx => + LuaInstruction(pc = pc, opcode = opcode, mode = opcode.mode, a = a, b = bits(raw, 14, 18).toInt, c = None) + case LuaInstructionMode.AsBx => + LuaInstruction( + pc = pc, + opcode = opcode, + mode = opcode.mode, + a = a, + b = bits(raw, 14, 18).toInt - 131071, + c = None + ) + } + } + + private def decodeConstant(constantIndex: Int): LuaConstant = { + readByte() match { + case 0 => LuaConstant(constantIndex, "nil", LuaConstantValue.NilValue) + case 1 => LuaConstant(constantIndex, "boolean", LuaConstantValue.BooleanValue(readByte() != 0)) + case 3 => LuaConstant(constantIndex, "number", LuaConstantValue.NumberValue(readDouble())) + case 4 => LuaConstant(constantIndex, "string", LuaConstantValue.StringValue(readString())) + case other => + reject("malformed-constant", s"unsupported constant tag $other at index $constantIndex") + } + } + + private def readVector[A](count: Long, decodeElement: Int => A): Vector[A] = { + if (count > Int.MaxValue) { + reject("unsupported-bytecode-profile", s"unsupported Lua 5.1 element count $count") + } + Vector.tabulate(count.toInt)(decodeElement) + } + + private def readByte(): Int = { + ensureAvailable(1) + val value = bytes(index) & 0xff + index += 1 + value + } + + private def readUInt32(): Long = { + ensureAvailable(4) + val value = ByteBuffer.wrap(bytes, index, 4).order(byteOrder).getInt.toLong & 0xffffffffL + index += 4 + value + } + + private def readUInt(): Long = readUnsignedInteger(intSize) + + private def readSizeT(): Long = readUnsignedInteger(sizeTSize) + + private def readUnsignedInteger(size: Int): Long = { + ensureAvailable(size) + val raw = bytes.slice(index, index + size) + index += size + val orderedBytes = if (byteOrder == ByteOrder.LITTLE_ENDIAN) raw.reverse else raw + val value = BigInt(1, orderedBytes) + if (value > BigInt(Long.MaxValue)) { + reject("unsupported-bytecode-profile", s"unsupported Lua 5.1 unsigned integer value $value") + } + value.longValue + } + + private def readDouble(): Double = { + ensureAvailable(luaNumberSize) + val value = ByteBuffer.wrap(bytes, index, luaNumberSize).order(byteOrder).getDouble + index += luaNumberSize + value + } + + private def readString(): String = { + val size = readSizeT() + if (size == 0) { + return "" + } + if (size > Int.MaxValue) { + reject("malformed-constant", s"unsupported Lua bytecode string size $size") + } + ensureAvailable(size.toInt) + val bytesStart = index + index += size.toInt + if (bytes(bytesStart + size.toInt - 1) != 0) { + reject("malformed-constant", "unterminated Lua bytecode string") + } + val decoder = StandardCharsets.UTF_8.newDecoder() + try { + decoder.decode(ByteBuffer.wrap(bytes, bytesStart, size.toInt - 1)).toString + } catch { + case error: CharacterCodingException => + reject("malformed-constant", s"invalid UTF-8 Lua bytecode string: ${error.getMessage}") + } + } + + private def ensureAvailable(size: Int): Unit = { + if (size < 0 || index > bytes.length - size) { + reject("truncated-bytecode", "unexpected end of bytecode stream") + } + } + + private def accepted(path: String, root: LuaPrototype): LuaBytecodeDecodeResult = { + val profile = buildProfile() + LuaBytecodeDecodeResult( + artifact = LuaBytecodeArtifact( + path = path, + inputKind = DefaultInputKind, + profileId = Some(profile.profileId), + accepted = true, + diagnostic = LuaDiagnostic( + kind = SuccessDiagnosticKind, + message = "Lua 5.1 bytecode accepted", + severity = SeverityInfo, + successFactsAllowed = true + ) + ), + profile = Some(profile), + root = Some(root) + ) + } + + private def rejected(path: String, kind: String, message: String): LuaBytecodeDecodeResult = { + LuaBytecodeDecodeResult( + artifact = LuaBytecodeArtifact( + path = path, + inputKind = DefaultInputKind, + profileId = currentProfileId, + accepted = false, + diagnostic = LuaDiagnostic( + kind = kind, + message = message, + severity = SeverityError, + successFactsAllowed = false + ) + ), + profile = currentProfileId.map(_ => buildProfile()), + root = None + ) + } + + private def buildProfile(): LuaBytecodeProfile = { + LuaBytecodeProfile( + luaVersion = "5.1", + bytecodeVersion = f"0x$version%02x", + format = format, + endianness = if (byteOrder == ByteOrder.BIG_ENDIAN) "big-endian" else "little-endian", + intSize = intSize, + sizeTSize = sizeTSize, + instructionSize = instructionSize, + luaNumberSize = luaNumberSize, + numberMode = if (integralFlag == 0) NumberModeFloating else NumberModeIntegral, + profileId = profileId + ) + } + + private def profileId: String = { + val endian = if (byteOrder == ByteOrder.BIG_ENDIAN) "big" else "little" + val numberMode = if (integralFlag == 0) "float" else "integral" + s"lua51-$endian-int$intSize-size_t$sizeTSize-instruction$instructionSize-number$luaNumberSize-$numberMode" + } + + private def bits(number: Long, position: Int, size: Int): Long = { + (number >> position) & ((1L << size) - 1L) + } + + private def reject(kind: String, message: String): Nothing = { + throw DecodeFailure(kind, message) + } + } + + private final case class DecodeFailure(kind: String, message: String) extends RuntimeException(message) +} diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaBytecodeModel.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaBytecodeModel.scala new file mode 100644 index 000000000000..bf5f84772e37 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaBytecodeModel.scala @@ -0,0 +1,131 @@ +package io.joern.lua2cpg.bytecode + +final case class LuaBytecodeArtifact( + path: String, + inputKind: String, + profileId: Option[String], + accepted: Boolean, + diagnostic: LuaDiagnostic +) + +final case class LuaBytecodeProfile( + luaVersion: String, + bytecodeVersion: String, + format: Int, + endianness: String, + intSize: Int, + sizeTSize: Int, + instructionSize: Int, + luaNumberSize: Int, + numberMode: String, + profileId: String +) + +final case class LuaPrototype( + prototypeId: String, + parentPrototypeId: Option[String], + ordinalPath: Vector[Int], + sourceName: String, + firstLine: Long, + lastLine: Long, + upvalueCount: Int, + numParams: Int, + isVararg: Boolean, + maxStack: Int, + instructions: Vector[LuaInstruction], + constants: Vector[LuaConstant], + nested: Vector[LuaPrototype], + lineNumbers: Vector[Long], + locals: Vector[LuaLocal], + upvalueNames: Vector[String] +) + +final case class LuaInstruction( + pc: Int, + opcode: LuaOpcode, + mode: LuaInstructionMode, + a: Int, + b: Int, + c: Option[Int] +) + +final case class LuaConstant(index: Int, luaType: String, value: LuaConstantValue) + +final case class LuaDiagnostic( + kind: String, + message: String, + severity: String, + successFactsAllowed: Boolean +) + +final case class LuaLocal(name: String, startPc: Long, endPc: Long) + +final case class LuaBytecodeDecodeResult( + artifact: LuaBytecodeArtifact, + profile: Option[LuaBytecodeProfile], + root: Option[LuaPrototype] +) + +enum LuaInstructionMode(val encodedName: String) { + case Abc extends LuaInstructionMode("ABC") + case ABx extends LuaInstructionMode("ABx") + case AsBx extends LuaInstructionMode("AsBx") + + override def toString: String = encodedName +} + +enum LuaOpcode(val code: Int, val mode: LuaInstructionMode, val mnemonic: String) { + case Move extends LuaOpcode(0, LuaInstructionMode.Abc, "MOVE") + case LoadK extends LuaOpcode(1, LuaInstructionMode.ABx, "LOADK") + case LoadBool extends LuaOpcode(2, LuaInstructionMode.Abc, "LOADBOOL") + case LoadNil extends LuaOpcode(3, LuaInstructionMode.Abc, "LOADNIL") + case GetUpval extends LuaOpcode(4, LuaInstructionMode.Abc, "GETUPVAL") + case GetGlobal extends LuaOpcode(5, LuaInstructionMode.ABx, "GETGLOBAL") + case GetTable extends LuaOpcode(6, LuaInstructionMode.Abc, "GETTABLE") + case SetGlobal extends LuaOpcode(7, LuaInstructionMode.ABx, "SETGLOBAL") + case SetUpval extends LuaOpcode(8, LuaInstructionMode.Abc, "SETUPVAL") + case SetTable extends LuaOpcode(9, LuaInstructionMode.Abc, "SETTABLE") + case NewTable extends LuaOpcode(10, LuaInstructionMode.Abc, "NEWTABLE") + case Self extends LuaOpcode(11, LuaInstructionMode.Abc, "SELF") + case Add extends LuaOpcode(12, LuaInstructionMode.Abc, "ADD") + case Sub extends LuaOpcode(13, LuaInstructionMode.Abc, "SUB") + case Mul extends LuaOpcode(14, LuaInstructionMode.Abc, "MUL") + case Div extends LuaOpcode(15, LuaInstructionMode.Abc, "DIV") + case Mod extends LuaOpcode(16, LuaInstructionMode.Abc, "MOD") + case Pow extends LuaOpcode(17, LuaInstructionMode.Abc, "POW") + case Unm extends LuaOpcode(18, LuaInstructionMode.Abc, "UNM") + case Not extends LuaOpcode(19, LuaInstructionMode.Abc, "NOT") + case Len extends LuaOpcode(20, LuaInstructionMode.Abc, "LEN") + case Concat extends LuaOpcode(21, LuaInstructionMode.Abc, "CONCAT") + case Jmp extends LuaOpcode(22, LuaInstructionMode.AsBx, "JMP") + case Eq extends LuaOpcode(23, LuaInstructionMode.Abc, "EQ") + case Lt extends LuaOpcode(24, LuaInstructionMode.Abc, "LT") + case Le extends LuaOpcode(25, LuaInstructionMode.Abc, "LE") + case Test extends LuaOpcode(26, LuaInstructionMode.Abc, "TEST") + case TestSet extends LuaOpcode(27, LuaInstructionMode.Abc, "TESTSET") + case Call extends LuaOpcode(28, LuaInstructionMode.Abc, "CALL") + case TailCall extends LuaOpcode(29, LuaInstructionMode.Abc, "TAILCALL") + case Return extends LuaOpcode(30, LuaInstructionMode.Abc, "RETURN") + case ForLoop extends LuaOpcode(31, LuaInstructionMode.AsBx, "FORLOOP") + case ForPrep extends LuaOpcode(32, LuaInstructionMode.AsBx, "FORPREP") + case TForLoop extends LuaOpcode(33, LuaInstructionMode.Abc, "TFORLOOP") + case SetList extends LuaOpcode(34, LuaInstructionMode.Abc, "SETLIST") + case Close extends LuaOpcode(35, LuaInstructionMode.Abc, "CLOSE") + case Closure extends LuaOpcode(36, LuaInstructionMode.ABx, "CLOSURE") + case Vararg extends LuaOpcode(37, LuaInstructionMode.Abc, "VARARG") + + override def toString: String = mnemonic +} + +object LuaOpcode { + private val byCode: Map[Int, LuaOpcode] = LuaOpcode.values.map(opcode => opcode.code -> opcode).toMap + + def fromCode(code: Int): Option[LuaOpcode] = byCode.get(code) +} + +enum LuaConstantValue { + case NilValue + case BooleanValue(value: Boolean) + case NumberValue(value: Double) + case StringValue(value: String) +} From 6a356a9162cb61d0fa41886ac77bc5b446bc3285 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 8 Jul 2026 03:23:15 -0400 Subject: [PATCH 006/105] Decode Lua byte strings deterministically --- .../joern/lua2cpg/bytecode/LuaBytecodeDecoder.scala | 13 ++++--------- .../joern/lua2cpg/bytecode/LuaBytecodeModel.scala | 11 +++++++---- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaBytecodeDecoder.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaBytecodeDecoder.scala index c0a3500735b4..8bcfab4338f3 100644 --- a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaBytecodeDecoder.scala +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaBytecodeDecoder.scala @@ -2,7 +2,7 @@ package io.joern.lua2cpg.bytecode import java.nio.ByteBuffer import java.nio.ByteOrder -import java.nio.charset.{CharacterCodingException, StandardCharsets} +import java.nio.charset.StandardCharsets object LuaBytecodeDecoder { private val LuaMagic: Array[Byte] = Array(0x1b.toByte, 0x4c.toByte, 0x75.toByte, 0x61.toByte) @@ -13,6 +13,7 @@ object LuaBytecodeDecoder { private val SeverityError = "error" private val NumberModeFloating = "floating" private val NumberModeIntegral = "integral" + private val LuaByteStringCharset = StandardCharsets.ISO_8859_1 def decode(path: String, bytes: Array[Byte]): LuaBytecodeDecodeResult = { val reader = new Reader(bytes) @@ -220,7 +221,7 @@ object LuaBytecodeDecoder { value } - private def readString(): String = { + private def readString(): LuaByteStringText = { val size = readSizeT() if (size == 0) { return "" @@ -234,13 +235,7 @@ object LuaBytecodeDecoder { if (bytes(bytesStart + size.toInt - 1) != 0) { reject("malformed-constant", "unterminated Lua bytecode string") } - val decoder = StandardCharsets.UTF_8.newDecoder() - try { - decoder.decode(ByteBuffer.wrap(bytes, bytesStart, size.toInt - 1)).toString - } catch { - case error: CharacterCodingException => - reject("malformed-constant", s"invalid UTF-8 Lua bytecode string: ${error.getMessage}") - } + new String(bytes, bytesStart, size.toInt - 1, LuaByteStringCharset) } private def ensureAvailable(size: Int): Unit = { diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaBytecodeModel.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaBytecodeModel.scala index bf5f84772e37..380898a993ab 100644 --- a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaBytecodeModel.scala +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaBytecodeModel.scala @@ -1,5 +1,8 @@ package io.joern.lua2cpg.bytecode +/** Lua 5.1 byte strings decoded with ISO-8859-1, preserving each byte as the same numeric code point. */ +type LuaByteStringText = String + final case class LuaBytecodeArtifact( path: String, inputKind: String, @@ -25,7 +28,7 @@ final case class LuaPrototype( prototypeId: String, parentPrototypeId: Option[String], ordinalPath: Vector[Int], - sourceName: String, + sourceName: LuaByteStringText, firstLine: Long, lastLine: Long, upvalueCount: Int, @@ -37,7 +40,7 @@ final case class LuaPrototype( nested: Vector[LuaPrototype], lineNumbers: Vector[Long], locals: Vector[LuaLocal], - upvalueNames: Vector[String] + upvalueNames: Vector[LuaByteStringText] ) final case class LuaInstruction( @@ -58,7 +61,7 @@ final case class LuaDiagnostic( successFactsAllowed: Boolean ) -final case class LuaLocal(name: String, startPc: Long, endPc: Long) +final case class LuaLocal(name: LuaByteStringText, startPc: Long, endPc: Long) final case class LuaBytecodeDecodeResult( artifact: LuaBytecodeArtifact, @@ -127,5 +130,5 @@ enum LuaConstantValue { case NilValue case BooleanValue(value: Boolean) case NumberValue(value: Double) - case StringValue(value: String) + case StringValue(value: LuaByteStringText) } From e1b753d88327b4955a2a2851f085e7f60726c987 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 8 Jul 2026 03:30:53 -0400 Subject: [PATCH 007/105] Add Lua bytecode decoder unit tests --- .../bc-constants-call/input.luac | Bin 0 -> 348 bytes .../malformed-constant.luac | Bin 0 -> 398 bytes .../not-lua-bytecode.bin | 1 + .../bc-malformed-diagnostic/truncated.luac | Bin 0 -> 8 bytes .../unsupported-profile.luac | Bin 0 -> 398 bytes .../unsupported-version.luac | Bin 0 -> 398 bytes .../bc-prototype-params/input.luac | Bin 0 -> 340 bytes .../bc-stripped-metadata/input.luac | Bin 0 -> 154 bytes .../bytecode/LuaBytecodeDecoderTest.scala | 84 ++++++++++++++++++ 9 files changed, 85 insertions(+) create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/bytecode-model/bc-constants-call/input.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/bytecode-model/bc-malformed-diagnostic/malformed-constant.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/bytecode-model/bc-malformed-diagnostic/not-lua-bytecode.bin create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/bytecode-model/bc-malformed-diagnostic/truncated.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/bytecode-model/bc-malformed-diagnostic/unsupported-profile.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/bytecode-model/bc-malformed-diagnostic/unsupported-version.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/bytecode-model/bc-prototype-params/input.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/bytecode-model/bc-stripped-metadata/input.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/bytecode/LuaBytecodeDecoderTest.scala diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/bytecode-model/bc-constants-call/input.luac b/joern-cli/frontends/lua2cpg/src/test/resources/bytecode-model/bc-constants-call/input.luac new file mode 100644 index 0000000000000000000000000000000000000000..829baa29a56c7c303619ca9176d92322153bac74 GIT binary patch literal 348 zcmaJ*OAdlC5S=0-x_6~v^T($rwmfT3-|5^v^3^eoOR5?%R{*O_^J^Vyxe zJd=piIHjB>lulb`i_3LzzSp*>Rj#_$I@vm#D``w&JKrmPG+usM5Tazm?}1q{? nTn5LIiKVMQoPBH|t(q>3U2`#CYhIhv literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/bytecode-model/bc-malformed-diagnostic/malformed-constant.luac b/joern-cli/frontends/lua2cpg/src/test/resources/bytecode-model/bc-malformed-diagnostic/malformed-constant.luac new file mode 100644 index 0000000000000000000000000000000000000000..6d7172f904f009ff70725abd0e127bb3529f7cd5 GIT binary patch literal 398 zcmYjNOAdlC6nsTc@CqiZ+#>M?9Kel7s8C}gprNf{$w54f2k|hzX(4fv_nXf9q`Q-o zXA*H5r*x!%$|_?`dDWwJgEHl%F09nOEjr!nPPWBU+FNNJZm89vb#iV)h?123o~@~f zCXVlEq7O1b;#-l(J5G^`f+#>M?9Kel7s8C}gprNf{$w54f2k|hzX=&ml?>C+INw+5_ z&qAbmn$wX2Dyxh&X-@xS>{u*2%dIA<1&~d$y(` znmE3vi9W~#iEl+>=UG{&Te3leC8&YXdoTbtV>(pp*)|;iExt2N;aB`XwBQK9=>G0EfW%hdC0nN``f+#>M?9Ke+~s8C}gprNf{$w54f2k|hzX=&ml?>C+INw+5_ z&m_`3&FM%1l~u-?@~TJc24%`iU0A7mTXeeDootJzw71ec+)%4S>*U;qkYqXgJzG-| zO&s6TL?2{=#J3``^DHa*Ekbaz2t|U3ddC4;NKlC4veat0Ro E0g?|UM*si- literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/bytecode-model/bc-prototype-params/input.luac b/joern-cli/frontends/lua2cpg/src/test/resources/bytecode-model/bc-prototype-params/input.luac new file mode 100644 index 0000000000000000000000000000000000000000..8bce7bc47c7e50a7cb24a62d3d9b8d1380584c06 GIT binary patch literal 340 zcmZ`!OA5j;6nv>sdk4Xl+nOu%0B$^hLRunJ)YK*c-PJ?5^(^&fzG)1u9GEx5%ll=q z33^LH<}xQm5v4cIJ9TJoK6J*ZsxI2jdh744DO%m>vs2E7t~Tpa(BCFVCg&Vi%#!(F zzS-=k7klM3KD2tfO@R}wKdMtBLAk({#h%z@)`An_s+n3RBb3{0G`M=vxjiZ>#g fG8Ow+mooL(ff@k+fS>&#F2rWwXq(G;K8k@4OeP>l literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/bytecode-model/bc-stripped-metadata/input.luac b/joern-cli/frontends/lua2cpg/src/test/resources/bytecode-model/bc-stripped-metadata/input.luac new file mode 100644 index 0000000000000000000000000000000000000000..7cd65439088b9a61b4c11a8e56d3e529ea526ff0 GIT binary patch literal 154 zcmb34DNPJyU}WK7;b6c7Of2j`K}H}>0pbQAJOHHU0)^%QX*nPtB*(%DBtRfHwInem eu_O^J3K3#pU constant.luaType -> constant.value) + constants.contains("string" -> StringValue("alpha")).shouldBe(true) + constants.contains("number" -> NumberValue(7.0)).shouldBe(true) + } + + "accept stripped metadata bytecode while preserving structural bytecode facts" in { + val result = decodeResource("bytecode-model/bc-stripped-metadata/input.luac") + val root = acceptedRoot(result) + + root.prototypeId.shouldBe("root") + root.nested.map(_.prototypeId).contains("root.0").shouldBe(true) + root.constants + .map(constant => constant.luaType -> constant.value) + .contains("string" -> StringValue("metadata")) + .shouldBe(true) + root.locals.isEmpty.shouldBe(true) + root.upvalueNames.isEmpty.shouldBe(true) + } + + "return diagnostics without accepted prototype models for malformed inputs" in { + val cases = Seq( + "not-lua-bytecode.bin" -> "not-lua-bytecode", + "truncated.luac" -> "truncated-bytecode", + "unsupported-version.luac" -> "unsupported-bytecode-version", + "unsupported-profile.luac" -> "unsupported-bytecode-profile", + "malformed-constant.luac" -> "malformed-constant" + ) + + cases.foreach { case (fileName, expectedKind) => + val result = decodeResource(s"bytecode-model/bc-malformed-diagnostic/$fileName") + + result.artifact.accepted.shouldBe(false) + result.artifact.diagnostic.kind.shouldBe(expectedKind) + result.artifact.diagnostic.severity.shouldBe("error") + result.artifact.diagnostic.successFactsAllowed.shouldBe(false) + result.root.shouldBe(None) + } + } + } + + private def acceptedRoot(result: LuaBytecodeDecodeResult): LuaPrototype = { + result.artifact.accepted.shouldBe(true) + result.artifact.diagnostic.kind.shouldBe("accepted") + result.artifact.diagnostic.successFactsAllowed.shouldBe(true) + result.profile.isDefined.shouldBe(true) + result.root.get + } + + private def decodeResource(path: String): LuaBytecodeDecodeResult = + LuaBytecodeDecoder.decode(path, readResourceBytes(path)) + + private def readResourceBytes(path: String): Array[Byte] = { + val stream = Option(getClass.getClassLoader.getResourceAsStream(path)).getOrElse { + fail(s"Missing lua2cpg bytecode test resource: $path") + } + try stream.readAllBytes() + finally stream.close() + } +} From 9812be562d061562621a7b0d15661d27b94ad6cd Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 8 Jul 2026 03:40:14 -0400 Subject: [PATCH 008/105] Emit Lua bytecode model into CPG --- .../main/scala/io/joern/lua2cpg/Lua2Cpg.scala | 3 +- .../lua2cpg/passes/LuaBytecodeModelPass.scala | 482 ++++++++++++++++++ .../lua2cpg/BytecodeModelSmokeTest.scala | 74 +++ 3 files changed, 558 insertions(+), 1 deletion(-) create mode 100644 joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/passes/LuaBytecodeModelPass.scala create mode 100644 joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/BytecodeModelSmokeTest.scala diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/Lua2Cpg.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/Lua2Cpg.scala index 0aaca3d7fc5c..a93f2d19b623 100644 --- a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/Lua2Cpg.scala +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/Lua2Cpg.scala @@ -1,6 +1,6 @@ package io.joern.lua2cpg -import io.joern.lua2cpg.passes.LuaFileInventoryPass +import io.joern.lua2cpg.passes.{LuaBytecodeModelPass, LuaFileInventoryPass} import io.joern.x2cpg.X2Cpg.withNewEmptyCpg import io.joern.x2cpg.X2CpgFrontend import io.joern.x2cpg.passes.frontend.MetaDataPass @@ -16,6 +16,7 @@ class Lua2Cpg extends X2CpgFrontend { withNewEmptyCpg(config.outputPath, config) { (cpg, config) => new MetaDataPass(cpg, "LUA", config.inputPath).createAndApply() new LuaFileInventoryPass(cpg, config).createAndApply() + new LuaBytecodeModelPass(cpg, config).createAndApply() } } } diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/passes/LuaBytecodeModelPass.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/passes/LuaBytecodeModelPass.scala new file mode 100644 index 000000000000..7a8ca03f33db --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/passes/LuaBytecodeModelPass.scala @@ -0,0 +1,482 @@ +package io.joern.lua2cpg.passes + +import io.joern.lua2cpg.Config +import io.joern.lua2cpg.bytecode.* +import io.joern.x2cpg.{Ast, Defines, SourceFiles, ValidationMode} +import io.shiftleft.codepropertygraph.generated.nodes.* +import io.shiftleft.codepropertygraph.generated.{Cpg, DispatchTypes, EdgeTypes, EvaluationStrategies, NodeTypes} +import io.shiftleft.passes.CpgPass +import io.shiftleft.semanticcpg.utils.FileUtil.* + +import java.nio.file.{Files, Paths} +import scala.collection.mutable + +class LuaBytecodeModelPass( + cpg: Cpg, + config: Config, + decodedInputs: Option[Vector[LuaBytecodeModelPass.DecodedBytecode]] = None +) extends CpgPass(cpg) { + + private given ValidationMode = ValidationMode.Disabled + + private final case class PrototypeAst(ast: Ast, reachingDefEdges: Vector[(NewIdentifier, NewIdentifier, String)]) + override def run(diffGraph: DiffGraphBuilder): Unit = { + val decoded = decodedInputs.getOrElse(LuaBytecodeModelPass.decodeInputs(config)) + val programSemantics = LuaProgramSemantics.normalize(decoded.map(item => item.relativeName -> item.result)) + + decoded.foreach { decodedItem => + val relativeName = decodedItem.relativeName + val result = decodedItem.result + + diffGraph.addNode(NewFile().name(relativeName).order(decodedItem.order)) + addArtifact(result, relativeName, diffGraph) + if (canEmitSuccessFacts(result)) { + result.root.foreach(root => addPrototypeTree(root, relativeName, programSemantics, diffGraph)) + } + addDiagnostic(result, relativeName, diffGraph) + if (isStrippedMetadata(result)) { + addMetadataUnavailableDiagnostic(relativeName, diffGraph) + } + } + } + + private def canEmitSuccessFacts(result: LuaBytecodeDecodeResult): Boolean = + result.artifact.accepted && result.artifact.diagnostic.successFactsAllowed + + private def addArtifact(result: LuaBytecodeDecodeResult, relativeName: String, diffGraph: DiffGraphBuilder): Unit = { + diffGraph.addNode( + NewTypeDecl() + .name("lua-bytecode-artifact") + .fullName(s"lua:$relativeName") + .code(artifactCode(result)) + .filename(relativeName) + .isExternal(false) + .astParentType(NodeTypes.FILE) + .astParentFullName(relativeName) + ) + } + + private def addPrototypeTree( + prototype: LuaPrototype, + relativeName: String, + programSemantics: LuaProgramSemantics, + diffGraph: DiffGraphBuilder + ): Unit = { + val prototypeTypeDecl = NewTypeDecl() + .name("lua-bytecode-prototype") + .fullName(prototypeTypeFullName(relativeName, prototype)) + .code(prototypeCode(prototype)) + .filename(relativeName) + .isExternal(false) + .astParentType(NodeTypes.FILE) + .astParentFullName(relativeName) + + diffGraph.addNode(prototypeTypeDecl) + val methodAst = prototypeMethodAst(prototype, relativeName, programSemantics) + Ast.storeInDiffGraph(methodAst.ast, diffGraph) + methodAst.reachingDefEdges.foreach { case (source, sink, variable) => + diffGraph.addEdge(source, sink, EdgeTypes.REACHING_DEF, variable) + } + prototype.nested.foreach(child => addPrototypeTree(child, relativeName, programSemantics, diffGraph)) + } + + private def prototypeMethodAst( + prototype: LuaPrototype, + relativeName: String, + programSemantics: LuaProgramSemantics + ): PrototypeAst = { + val fullName = prototypeMethodFullName(relativeName, prototype) + val semantics = LuaInstructionSemantics.normalizePrototype(prototype) + val method = NewMethod() + .name(prototype.prototypeId) + .code(prototypeCode(prototype)) + .fullName(fullName) + .filename(relativeName) + .signature(prototypeSignature(prototype)) + .isExternal(false) + .astParentType(NodeTypes.TYPE_DECL) + .astParentFullName(prototypeTypeFullName(relativeName, prototype)) + + val parameters = (0 until prototype.numParams).map { index => + Ast( + NewMethodParameterIn() + .name(s"r$index") + .code(s"${prototype.prototypeId}:r$index") + .index(index + 1) + .order(index + 1) + .isVariadic(false) + .evaluationStrategy(EvaluationStrategies.BY_VALUE) + .typeFullName(Defines.Any) + ) + } + val semanticNodes = semanticValueNodes(prototype, semantics) ++ + semanticCallNodes(prototype, semantics) ++ + programSemanticCallNodes(prototype, relativeName, programSemantics) + val block = Ast(NewBlock().code(prototype.prototypeId).typeFullName(Defines.Any)) + .withChildren(prototype.constants.map(constant => Ast(literalNode(prototype, constant)))) + .withChildren(prototype.instructions.map(instruction => Ast(instructionNode(prototype, instruction)))) + .withChildren(semanticNodes.map(Ast(_))) + val methodReturn = Ast( + NewMethodReturn() + .code("RET") + .order(prototype.numParams + 2) + .evaluationStrategy(EvaluationStrategies.BY_VALUE) + .typeFullName(Defines.Any) + ) + + val ast = Ast(method) + .withChildren(parameters) + .withChild(block) + .withChild(methodReturn) + PrototypeAst(ast, reachingDefEdges(ast, semantics)) + } + + private def literalNode(prototype: LuaPrototype, constant: LuaConstant): NewLiteral = { + val text = constantValueText(constant.value) + NewLiteral() + .code(text) + .typeFullName(s"lua.${constant.luaType}") + .order(constant.index + 1) + .argumentIndex(constant.index + 1) + .lineNumber((constant.index + 1).toInt) + .columnNumber(0) + } + + private def instructionNode(prototype: LuaPrototype, instruction: LuaInstruction): NewCall = + NewCall() + .name(s"lua.bytecode.${instruction.opcode.mnemonic}") + .code(s"${prototype.prototypeId}@pc${instruction.pc}:${instruction.opcode.mnemonic}") + .methodFullName(s"lua.bytecode.${instruction.opcode.mnemonic}") + .dispatchType(DispatchTypes.STATIC_DISPATCH) + .typeFullName(Defines.Any) + .order(instruction.pc + 1) + .argumentIndex(instruction.pc + 1) + .lineNumber(instruction.pc + 1) + .columnNumber(0) + + private def semanticValueNodes(prototype: LuaPrototype, semantics: LuaPrototypeSemantics): Vector[NewIdentifier] = + semantics.registerEvents + .filter(_.prototypeId == prototype.prototypeId) + .map(_.valueRef) + .distinct + .sorted + .zipWithIndex + .map { case (ref, index) => + NewIdentifier() + .name(ref) + .code(ref) + .typeFullName(Defines.Any) + .order(10_000 + index) + .argumentIndex(10_000 + index) + .lineNumber(10_000 + index) + .columnNumber(0) + } + + private def semanticCallNodes(prototype: LuaPrototype, semantics: LuaPrototypeSemantics): Vector[NewCall] = { + val candidateNodes = semantics.callTargetCandidates + .filter(_.callsiteId.startsWith(s"${prototype.prototypeId}@pc")) + .sortBy(candidate => (candidate.callsiteId, candidate.targetRef)) + .zipWithIndex + .map { case (candidate, index) => + semanticCallNode( + name = "lua.calltarget.candidate", + code = s"${candidate.callsiteId} -> ${candidate.targetRef}", + order = 20_000 + index + ) + } + val unresolvedNodes = semantics.unresolvedCalls + .filter(_.callsiteId.startsWith(s"${prototype.prototypeId}@pc")) + .sortBy(_.callsiteId) + .zipWithIndex + .map { case (call, index) => + semanticCallNode( + name = "lua.calltarget.unresolved", + code = s"${call.callsiteId} unresolved=${call.unresolvedReason}", + order = 30_000 + index + ) + } + val boundaryNodes = semantics.negativeExpectations + .filter(row => + row.sourceRef.startsWith(s"${prototype.prototypeId}@") || row.sourceRef.startsWith(s"${prototype.prototypeId}.") + ) + .sortBy(_.negativeId) + .zipWithIndex + .map { case (row, index) => + semanticCallNode(name = "lua.semantic.boundary", code = row.negativeId, order = 40_000 + index) + } + candidateNodes ++ unresolvedNodes ++ boundaryNodes + } + + private def programSemanticCallNodes( + prototype: LuaPrototype, + relativeName: String, + semantics: LuaProgramSemantics + ): Vector[NewCall] = { + val prefix = s"$relativeName:${prototype.prototypeId}@pc" + val rootMethod = prototype.prototypeId == "root" + val rootMarkers = if (rootMethod) { + val moduleResolutions = semantics.moduleResolutions + .filter(_.fromModulePath == relativeName) + .sortBy(_.requireCallsiteId) + .zipWithIndex + .map { case (resolution, index) => + val target = resolution.targetModulePath.getOrElse(resolution.unresolvedReason.getOrElse("unresolved")) + semanticCallNode( + name = "lua.module.resolution", + code = + s"${resolution.fromModulePath} require ${resolution.requireString} -> ${resolution.resolutionStatus}:$target", + order = 50_000 + index + ) + } + val returnTables = semantics.moduleReturnTables + .filter(_.modulePath == relativeName) + .sortBy(row => (row.fieldName, row.targetPrototypeId)) + .zipWithIndex + .map { case (row, index) => + semanticCallNode( + name = "lua.module.return_table", + code = s"${row.modulePath}::${row.fieldName} -> ${row.targetPrototypeId}", + order = 51_000 + index + ) + } + val boundaries = semantics.boundaries + .filter(_.boundaryId.startsWith(relativeName)) + .sortBy(_.boundaryId) + .zipWithIndex + .map { case (boundary, index) => + semanticCallNode( + name = "lua.e4.boundary", + code = s"${boundary.boundaryId} reason=${boundary.reason}", + order = 52_000 + index + ) + } + moduleResolutions ++ returnTables ++ boundaries + } else Vector.empty + + val fieldCalls = semantics.moduleFieldCallTargets + .filter(row => s"${row.fromModulePath}:${row.callsiteId}".startsWith(prefix)) + .sortBy(_.callsiteId) + .zipWithIndex + .map { case (row, index) => + semanticCallNode( + name = "lua.module.field_call_target", + code = s"${row.fromModulePath}:${row.callsiteId} -> ${row.targetModulePath}::${row.targetPrototypeId}", + order = 53_000 + index + ) + } + val argFlows = semantics.interproceduralArgFlows + .filter(row => + row.callsiteId.startsWith(s"${prototype.prototypeId}@pc") && row.fromArgumentRef.startsWith(relativeName) + ) + .sortBy(row => (row.callsiteId, row.fromArgumentRef)) + .zipWithIndex + .map { case (row, index) => + semanticCallNode( + name = "lua.interproc.arg_flow", + code = s"${row.fromArgumentRef} -> ${row.targetModulePath}::${row.toParameterRef}", + order = 54_000 + index + ) + } + val returnFlows = semantics.interproceduralReturnFlows + .filter(row => + row.callsiteId.startsWith(s"${prototype.prototypeId}@pc") && row.callerResultRef.startsWith(relativeName) + ) + .sortBy(row => (row.callsiteId, row.callerResultRef)) + .zipWithIndex + .map { case (row, index) => + semanticCallNode( + name = "lua.interproc.return_flow", + code = s"${row.targetModulePath}::${row.calleeReturnRef} -> ${row.callerResultRef}", + order = 55_000 + index + ) + } + val crossTargets = semantics.crossBoundaryCallTargets + .filter(row => + row.fromModulePath == relativeName && s"${row.fromModulePath}:${row.callsiteId}".startsWith(prefix) + ) + .sortBy(row => (row.callsiteId, row.targetModulePath, row.targetPrototypeId)) + .zipWithIndex + .map { case (row, index) => + semanticCallNode( + name = "lua.calltarget.cross_boundary", + code = s"$relativeName:${row.callsiteId} -> ${row.targetModulePath}::${row.targetPrototypeId}", + order = 56_000 + index + ) + } + val taintPaths = semantics.taintPaths + .filter(row => row.sourceRef.startsWith(s"$relativeName:${prototype.prototypeId}@")) + .sortBy(row => (row.sourceRef, row.sinkRef)) + .zipWithIndex + .map { case (row, index) => + semanticCallNode( + name = "lua.taint.path", + code = s"${row.sourceRef} -> ${row.sinkRef} via ${row.pathSteps.mkString(";")}", + order = 57_000 + index + ) + } + + rootMarkers ++ fieldCalls ++ argFlows ++ returnFlows ++ crossTargets ++ taintPaths + } + + private def semanticCallNode(name: String, code: String, order: Int): NewCall = + NewCall() + .name(name) + .code(code) + .methodFullName(name) + .dispatchType(DispatchTypes.STATIC_DISPATCH) + .typeFullName(Defines.Any) + .order(order) + .argumentIndex(order) + .lineNumber(order) + .columnNumber(0) + + private def reachingDefEdges( + ast: Ast, + semantics: LuaPrototypeSemantics + ): Vector[(NewIdentifier, NewIdentifier, String)] = { + val nodesByCode = ast.nodes.collect { case node: NewIdentifier => node.code -> node }.toMap + val edges = mutable.LinkedHashSet.empty[(NewIdentifier, NewIdentifier, String)] + semantics.localFlows.foreach { flow => + addEdge(nodesByCode, edges, flow.sourceRef, flow.sinkRef, flow.sourceRef) + } + semantics.tableFieldFlows.foreach { flow => + addEdge(nodesByCode, edges, flow.writeRef, flow.readRef, s"table:${flow.tableRef}:${flow.keyRef}") + } + semantics.globalFlows.foreach { flow => + addEdge(nodesByCode, edges, flow.valueRef, flow.readRef, s"global:${flow.globalName}") + } + semantics.upvalueFlows.foreach { flow => + addEdge(nodesByCode, edges, flow.captureRef, flow.writeRef, s"upvalue:${flow.upvalueId}") + } + edges.toVector + } + + private def addEdge( + nodesByCode: Map[String, NewIdentifier], + edges: mutable.LinkedHashSet[(NewIdentifier, NewIdentifier, String)], + sourceRef: String, + sinkRef: String, + variable: String + ): Unit = + for { + source <- nodesByCode.get(sourceRef) + sink <- nodesByCode.get(sinkRef) + } edges += ((source, sink, variable)) + + private def addDiagnostic( + result: LuaBytecodeDecodeResult, + relativeName: String, + diffGraph: DiffGraphBuilder + ): Unit = { + val diagnostic = result.artifact.diagnostic + diffGraph.addNode( + NewTypeDecl() + .name("lua-bytecode-diagnostic") + .fullName(s"lua:$relativeName:diagnostic:${diagnostic.kind}") + .code(diagnosticCode(result, diagnostic)) + .filename(relativeName) + .isExternal(false) + .astParentType(NodeTypes.FILE) + .astParentFullName(relativeName) + ) + } + + private def addMetadataUnavailableDiagnostic(relativeName: String, diffGraph: DiffGraphBuilder): Unit = { + diffGraph.addNode( + NewTypeDecl() + .name("lua-bytecode-diagnostic") + .fullName(s"lua:$relativeName:diagnostic:metadata-unavailable") + .code("kind=metadata-unavailable severity=info success_facts_allowed=true") + .filename(relativeName) + .isExternal(false) + .astParentType(NodeTypes.FILE) + .astParentFullName(relativeName) + ) + } + + private def isStrippedMetadata(result: LuaBytecodeDecodeResult): Boolean = { + result.artifact.accepted && result.root.exists { root => + allPrototypes(root).exists(prototype => + prototype.sourceName.isEmpty && + prototype.lineNumbers.isEmpty && + prototype.locals.isEmpty && + prototype.upvalueNames.isEmpty + ) + } + } + + private def allPrototypes(prototype: LuaPrototype): Vector[LuaPrototype] = + prototype +: prototype.nested.flatMap(allPrototypes) + + private def artifactCode(result: LuaBytecodeDecodeResult): String = { + val artifact = result.artifact + Seq( + s"input_kind=${artifact.inputKind}", + s"accepted=${artifact.accepted}", + s"diagnostic=${artifact.diagnostic.kind}", + artifact.profileId.map(profile => s"profile=$profile").getOrElse("profile=unavailable") + ).mkString(" ") + } + + private def prototypeCode(prototype: LuaPrototype): String = { + Seq( + s"prototype=${prototype.prototypeId}", + s"parent=${prototype.parentPrototypeId.getOrElse("none")}", + s"params=${prototype.numParams}", + s"vararg=${prototype.isVararg}", + s"max_stack=${prototype.maxStack}", + s"upvalues=${prototype.upvalueCount}" + ).mkString(" ") + } + + private def prototypeSignature(prototype: LuaPrototype): String = + s"(${Vector.fill(prototype.numParams)(Defines.Any).mkString(",")}):${Defines.Any}" + + private def diagnosticCode(result: LuaBytecodeDecodeResult, diagnostic: LuaDiagnostic): String = + Seq( + s"kind=${diagnostic.kind}", + s"severity=${diagnostic.severity}", + s"success_facts_allowed=${diagnostic.successFactsAllowed}", + s"accepted=${result.artifact.accepted}", + s"message=${diagnostic.message}" + ).mkString(" ") + + private def constantValueText(value: LuaConstantValue): String = value match { + case LuaConstantValue.NilValue => "nil" + case LuaConstantValue.BooleanValue(value) => value.toString + case LuaConstantValue.NumberValue(value) => numericText(value) + case LuaConstantValue.StringValue(value) => value + } + + private def numericText(value: Double): String = + if (value.isWhole) value.toLong.toString else value.toString + + private def prototypeTypeFullName(relativeName: String, prototype: LuaPrototype): String = + s"${prototypeMethodFullName(relativeName, prototype)}:prototype" + + private def prototypeMethodFullName(relativeName: String, prototype: LuaPrototype): String = + s"lua:$relativeName:${prototype.prototypeId}" +} + +object LuaBytecodeModelPass { + final case class DecodedBytecode(relativeName: String, result: LuaBytecodeDecodeResult, order: Int) + + def decodeInputs(config: Config): Vector[DecodedBytecode] = { + val inputRoot = Paths.get(config.inputPath).absolutePathAsString + val bytecodeFiles = SourceFiles.determine( + inputPath = inputRoot, + sourceFileExtensions = Set(".luac"), + ignoredDefaultRegex = Some(config.defaultIgnoredFilesRegex), + ignoredFilesRegex = Some(config.ignoredFilesRegex), + ignoredFilesPath = Some(config.ignoredFiles) + )() + + bytecodeFiles.zipWithIndex.map { case (file, index) => + val relativeName = SourceFiles.toRelativePath(file, inputRoot) + val bytes = Files.readAllBytes(Paths.get(file)) + val result = LuaBytecodeDecoder.decode(relativeName, bytes) + DecodedBytecode(relativeName, result, index + 1) + }.toVector + } + +} diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/BytecodeModelSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/BytecodeModelSmokeTest.scala new file mode 100644 index 000000000000..f0939788eeac --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/BytecodeModelSmokeTest.scala @@ -0,0 +1,74 @@ +package io.joern.lua2cpg + +import io.shiftleft.codepropertygraph.cpgloading.CpgLoader +import io.shiftleft.semanticcpg.language.* +import io.shiftleft.semanticcpg.language.types.structure.FileTraversal +import io.shiftleft.semanticcpg.utils.FileUtil +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec + +import java.nio.file.Paths + +class BytecodeModelSmokeTest extends AnyWordSpec with Matchers { + + "Lua2Cpg" should { + "emit bytecode model nodes that survive CPG reopen" in { + val resourceRoot = Paths.get(getClass.getClassLoader.getResource("bytecode-model").toURI) + + FileUtil.usingTemporaryDirectory("lua2cpg-bytecode-model-smoke") { tmpDir => + val outputPath = tmpDir.resolve("bytecode-model.cpg.bin").toString + val cpg = new Lua2Cpg() + .createCpg(Config().withInputPath(resourceRoot.toString).withOutputPath(outputPath)) + .get + cpg.close() + + val reopened = CpgLoader.load(outputPath) + try { + reopened.file.nameNot(FileTraversal.UNKNOWN).name.sorted.l should contain allOf ( + "bc-prototype-params/input.luac", + "bc-constants-call/input.luac", + "bc-stripped-metadata/input.luac" + ) + + val methodFullNames = reopened.method.fullName.sorted.l + methodFullNames should contain allOf ( + "lua:bc-prototype-params/input.luac:root", + "lua:bc-prototype-params/input.luac:root.0", + "lua:bc-constants-call/input.luac:root", + "lua:bc-constants-call/input.luac:root.0", + "lua:bc-stripped-metadata/input.luac:root", + "lua:bc-stripped-metadata/input.luac:root.0" + ) + + reopened.method + .fullNameExact("lua:bc-prototype-params/input.luac:root.0") + .parameter + .indexGt(0) + .index + .sorted + .l shouldBe List(1, 2) + + val literalCodes = reopened.literal.code.l + literalCodes should contain allOf ("alpha", "7") + + val diagnostics = reopened.typeDecl + .name("lua-bytecode-diagnostic") + .fullName + .l + diagnostics should contain allOf ( + "lua:bc-malformed-diagnostic/not-lua-bytecode.bin:diagnostic:not-lua-bytecode", + "lua:bc-malformed-diagnostic/truncated.luac:diagnostic:truncated-bytecode", + "lua:bc-malformed-diagnostic/unsupported-version.luac:diagnostic:unsupported-bytecode-version", + "lua:bc-malformed-diagnostic/unsupported-profile.luac:diagnostic:unsupported-bytecode-profile", + "lua:bc-malformed-diagnostic/malformed-constant.luac:diagnostic:malformed-constant", + "lua:bc-stripped-metadata/input.luac:diagnostic:metadata-unavailable" + ) + + methodFullNames.filter(_.startsWith("lua:bc-malformed-diagnostic/")) shouldBe Nil + } finally { + reopened.close() + } + } + } + } +} From c281df4adb8bfc0933d5f8da2f083e98398f730c Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 8 Jul 2026 03:49:44 -0400 Subject: [PATCH 009/105] Gate Lua bytecode success fact emission --- .../lua2cpg/BytecodeModelSmokeTest.scala | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/BytecodeModelSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/BytecodeModelSmokeTest.scala index f0939788eeac..2d2d45b2bf7d 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/BytecodeModelSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/BytecodeModelSmokeTest.scala @@ -13,7 +13,7 @@ class BytecodeModelSmokeTest extends AnyWordSpec with Matchers { "Lua2Cpg" should { "emit bytecode model nodes that survive CPG reopen" in { - val resourceRoot = Paths.get(getClass.getClassLoader.getResource("bytecode-model").toURI) + val resourceRoot = Paths.get(getClass.getClassLoader.getResource("bytecode-model").toURI).getParent FileUtil.usingTemporaryDirectory("lua2cpg-bytecode-model-smoke") { tmpDir => val outputPath = tmpDir.resolve("bytecode-model.cpg.bin").toString @@ -25,23 +25,28 @@ class BytecodeModelSmokeTest extends AnyWordSpec with Matchers { val reopened = CpgLoader.load(outputPath) try { reopened.file.nameNot(FileTraversal.UNKNOWN).name.sorted.l should contain allOf ( - "bc-prototype-params/input.luac", - "bc-constants-call/input.luac", - "bc-stripped-metadata/input.luac" + "bytecode-model/bc-prototype-params/input.luac", + "bytecode-model/bc-constants-call/input.luac", + "bytecode-model/bc-stripped-metadata/input.luac", + "OpenWrtDerived-luci/cgi.lua", + "OpenWrtDerived-luci/uci.lua", + "OpenWrtDerived-luci/version.lua" ) + // Expected rows were manually cross-checked against the referenceAnalyzer prototype YAML fixtures for + // bc-prototype-params, bc-constants-call, bc-stripped-metadata, and bc-malformed-diagnostic. val methodFullNames = reopened.method.fullName.sorted.l methodFullNames should contain allOf ( - "lua:bc-prototype-params/input.luac:root", - "lua:bc-prototype-params/input.luac:root.0", - "lua:bc-constants-call/input.luac:root", - "lua:bc-constants-call/input.luac:root.0", - "lua:bc-stripped-metadata/input.luac:root", - "lua:bc-stripped-metadata/input.luac:root.0" + "lua:bytecode-model/bc-prototype-params/input.luac:root", + "lua:bytecode-model/bc-prototype-params/input.luac:root.0", + "lua:bytecode-model/bc-constants-call/input.luac:root", + "lua:bytecode-model/bc-constants-call/input.luac:root.0", + "lua:bytecode-model/bc-stripped-metadata/input.luac:root", + "lua:bytecode-model/bc-stripped-metadata/input.luac:root.0" ) reopened.method - .fullNameExact("lua:bc-prototype-params/input.luac:root.0") + .fullNameExact("lua:bytecode-model/bc-prototype-params/input.luac:root.0") .parameter .indexGt(0) .index @@ -56,15 +61,15 @@ class BytecodeModelSmokeTest extends AnyWordSpec with Matchers { .fullName .l diagnostics should contain allOf ( - "lua:bc-malformed-diagnostic/not-lua-bytecode.bin:diagnostic:not-lua-bytecode", - "lua:bc-malformed-diagnostic/truncated.luac:diagnostic:truncated-bytecode", - "lua:bc-malformed-diagnostic/unsupported-version.luac:diagnostic:unsupported-bytecode-version", - "lua:bc-malformed-diagnostic/unsupported-profile.luac:diagnostic:unsupported-bytecode-profile", - "lua:bc-malformed-diagnostic/malformed-constant.luac:diagnostic:malformed-constant", - "lua:bc-stripped-metadata/input.luac:diagnostic:metadata-unavailable" + "lua:bytecode-model/bc-malformed-diagnostic/not-lua-bytecode.bin:diagnostic:not-lua-bytecode", + "lua:bytecode-model/bc-malformed-diagnostic/truncated.luac:diagnostic:truncated-bytecode", + "lua:bytecode-model/bc-malformed-diagnostic/unsupported-version.luac:diagnostic:unsupported-bytecode-version", + "lua:bytecode-model/bc-malformed-diagnostic/unsupported-profile.luac:diagnostic:unsupported-bytecode-profile", + "lua:bytecode-model/bc-malformed-diagnostic/malformed-constant.luac:diagnostic:malformed-constant", + "lua:bytecode-model/bc-stripped-metadata/input.luac:diagnostic:metadata-unavailable" ) - methodFullNames.filter(_.startsWith("lua:bc-malformed-diagnostic/")) shouldBe Nil + methodFullNames.filter(_.startsWith("lua:bytecode-model/bc-malformed-diagnostic/")) shouldBe Nil } finally { reopened.close() } From eb6597aea6f7974ea15d356faf59721dbd781c58 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 8 Jul 2026 06:28:24 -0400 Subject: [PATCH 010/105] style(lua2cpg): format Lua bytecode model --- .../lua2cpg/bytecode/LuaBytecodeDecoder.scala | 81 +++++++++---------- .../lua2cpg/bytecode/LuaBytecodeModel.scala | 16 +--- 2 files changed, 42 insertions(+), 55 deletions(-) diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaBytecodeDecoder.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaBytecodeDecoder.scala index 8bcfab4338f3..9aeea40d4817 100644 --- a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaBytecodeDecoder.scala +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaBytecodeDecoder.scala @@ -5,15 +5,15 @@ import java.nio.ByteOrder import java.nio.charset.StandardCharsets object LuaBytecodeDecoder { - private val LuaMagic: Array[Byte] = Array(0x1b.toByte, 0x4c.toByte, 0x75.toByte, 0x61.toByte) - private val Lua51Version: Int = 0x51 - private val DefaultInputKind: String = "lua-bytecode" - private val SuccessDiagnosticKind = "accepted" - private val SeverityInfo = "info" - private val SeverityError = "error" - private val NumberModeFloating = "floating" - private val NumberModeIntegral = "integral" - private val LuaByteStringCharset = StandardCharsets.ISO_8859_1 + private val LuaMagic: Array[Byte] = Array(0x1b.toByte, 0x4c.toByte, 0x75.toByte, 0x61.toByte) + private val Lua51Version: Int = 0x51 + private val DefaultInputKind: String = "lua-bytecode" + private val SuccessDiagnosticKind = "accepted" + private val SeverityInfo = "info" + private val SeverityError = "error" + private val NumberModeFloating = "floating" + private val NumberModeIntegral = "integral" + private val LuaByteStringCharset = StandardCharsets.ISO_8859_1 def decode(path: String, bytes: Array[Byte]): LuaBytecodeDecodeResult = { val reader = new Reader(bytes) @@ -21,16 +21,16 @@ object LuaBytecodeDecoder { } private final class Reader(bytes: Array[Byte]) { - private var index: Int = 0 - private var version: Int = 0 - private var format: Int = 0 - private var endianFlag: Int = 1 - private var byteOrder: ByteOrder = ByteOrder.LITTLE_ENDIAN - private var intSize: Int = 4 - private var sizeTSize: Int = 8 - private var instructionSize: Int = 4 - private var luaNumberSize: Int = 8 - private var integralFlag: Int = 0 + private var index: Int = 0 + private var version: Int = 0 + private var format: Int = 0 + private var endianFlag: Int = 1 + private var byteOrder: ByteOrder = ByteOrder.LITTLE_ENDIAN + private var intSize: Int = 4 + private var sizeTSize: Int = 8 + private var instructionSize: Int = 4 + private var luaNumberSize: Int = 8 + private var integralFlag: Int = 0 private var currentProfileId: Option[String] = None def decode(path: String): LuaBytecodeDecodeResult = { @@ -98,19 +98,22 @@ object LuaBytecodeDecoder { parentPrototypeId: Option[String], ordinalPath: Vector[Int] ): LuaPrototype = { - val sourceName = readString() - val firstLine = readUInt() - val lastLine = readUInt() - val upvalueCount = readByte() - val numParams = readByte() - val isVararg = (readByte() & 0x02) != 0 - val maxStack = readByte() - val instructions = readVector(readUInt(), pc => decodeInstruction(pc)) - val constants = readVector(readUInt(), constantIndex => decodeConstant(constantIndex)) - val nested = readVector(readUInt(), childOrdinal => { - val childId = s"$prototypeId.$childOrdinal" - decodePrototype(childId, Some(prototypeId), ordinalPath :+ childOrdinal) - }) + val sourceName = readString() + val firstLine = readUInt() + val lastLine = readUInt() + val upvalueCount = readByte() + val numParams = readByte() + val isVararg = (readByte() & 0x02) != 0 + val maxStack = readByte() + val instructions = readVector(readUInt(), pc => decodeInstruction(pc)) + val constants = readVector(readUInt(), constantIndex => decodeConstant(constantIndex)) + val nested = readVector( + readUInt(), + childOrdinal => { + val childId = s"$prototypeId.$childOrdinal" + decodePrototype(childId, Some(prototypeId), ordinalPath :+ childOrdinal) + } + ) val lineNumbers = readVector(readUInt(), _ => readUInt()) val locals = readVector(readUInt(), _ => LuaLocal(readString(), readUInt(), readUInt())) val upvalueNames = readVector(readUInt(), _ => readString()) @@ -136,9 +139,9 @@ object LuaBytecodeDecoder { } private def decodeInstruction(pc: Int): LuaInstruction = { - val raw = readUInt32() - val opcodeCode = bits(raw, 0, 6).toInt - val opcode = LuaOpcode.fromCode(opcodeCode).getOrElse { + val raw = readUInt32() + val opcodeCode = bits(raw, 0, 6).toInt + val opcode = LuaOpcode.fromCode(opcodeCode).getOrElse { reject("malformed-constant", s"invalid opcode $opcodeCode at pc $pc") } val a = bits(raw, 6, 8).toInt @@ -271,12 +274,8 @@ object LuaBytecodeDecoder { inputKind = DefaultInputKind, profileId = currentProfileId, accepted = false, - diagnostic = LuaDiagnostic( - kind = kind, - message = message, - severity = SeverityError, - successFactsAllowed = false - ) + diagnostic = + LuaDiagnostic(kind = kind, message = message, severity = SeverityError, successFactsAllowed = false) ), profile = currentProfileId.map(_ => buildProfile()), root = None diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaBytecodeModel.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaBytecodeModel.scala index 380898a993ab..ba2efc791a08 100644 --- a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaBytecodeModel.scala +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaBytecodeModel.scala @@ -43,23 +43,11 @@ final case class LuaPrototype( upvalueNames: Vector[LuaByteStringText] ) -final case class LuaInstruction( - pc: Int, - opcode: LuaOpcode, - mode: LuaInstructionMode, - a: Int, - b: Int, - c: Option[Int] -) +final case class LuaInstruction(pc: Int, opcode: LuaOpcode, mode: LuaInstructionMode, a: Int, b: Int, c: Option[Int]) final case class LuaConstant(index: Int, luaType: String, value: LuaConstantValue) -final case class LuaDiagnostic( - kind: String, - message: String, - severity: String, - successFactsAllowed: Boolean -) +final case class LuaDiagnostic(kind: String, message: String, severity: String, successFactsAllowed: Boolean) final case class LuaLocal(name: LuaByteStringText, startPc: Long, endPc: Long) From a3e5d41043fc1d0658ff114d7f183e4365bd75db Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 8 Jul 2026 06:28:35 -0400 Subject: [PATCH 011/105] docs(lua2cpg): add bytecode model smoke --- joern-cli/frontends/lua2cpg/README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/README.md b/joern-cli/frontends/lua2cpg/README.md index ee34ebd7e143..97bb61a223fb 100644 --- a/joern-cli/frontends/lua2cpg/README.md +++ b/joern-cli/frontends/lua2cpg/README.md @@ -27,3 +27,22 @@ Expected result: This E1 smoke proves only the runnable Lua frontend entry and file inventory. It does not claim Lua parsing, bytecode decode, AST semantics, dataflow, QueryDB, sanitizer, or report construction. + +## Bytecode Model Smoke + +```bash +sbt 'lua2cpg/testOnly io.joern.lua2cpg.BytecodeModelSmokeTest' +sbt 'lua2cpg/stage' +git status --short +``` + +Expected result: + +- `BytecodeModelSmokeTest` succeeds. +- `lua2cpg/stage` succeeds. +- `git status --short` is clean after the smoke. + +This E2 smoke proves bytecode artifact/profile/prototype/constant/diagnostic +CPG evidence only. It does not claim parser AST semantics, dataflow, QueryDB, +sanitizer, report construction, schema extension, distribution acceptance, or +official frontend acceptance. From 3df11390beafa7d8acb82affc76904ad57d5c5cf Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 8 Jul 2026 09:33:33 -0400 Subject: [PATCH 012/105] feat(lua2cpg): add Lua intraprocedural semantics smoke --- joern-cli/frontends/lua2cpg/README.md | 21 + .../bytecode/LuaInstructionSemantics.scala | 464 ++++++++++++++++++ .../bc-call-candidate-unresolved/input.luac | Bin 0 -> 444 bytes .../bc-kill-overwrite/input.luac | Bin 0 -> 417 bytes .../bc-table-global-upvalue/input.luac | Bin 0 -> 476 bytes .../d24-defuse-transitive-chain/input.luac | Bin 0 -> 460 bytes .../input.luac | Bin 0 -> 588 bytes .../input.luac | Bin 0 -> 354 bytes .../d24-table-dynamic-key-negative/input.luac | Bin 0 -> 369 bytes .../d24-upvalue-mutation-negative/input.luac | Bin 0 -> 405 bytes .../IntraproceduralSemanticsSmokeTest.scala | 159 ++++++ 11 files changed, 644 insertions(+) create mode 100644 joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/intraprocedural-semantics/bc-call-candidate-unresolved/input.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/intraprocedural-semantics/bc-kill-overwrite/input.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/intraprocedural-semantics/bc-table-global-upvalue/input.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/intraprocedural-semantics/d24-defuse-transitive-chain/input.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/intraprocedural-semantics/d24-defuse-unrelated-register-negative/input.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/intraprocedural-semantics/d24-global-dynamic-env-negative/input.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/intraprocedural-semantics/d24-table-dynamic-key-negative/input.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/intraprocedural-semantics/d24-upvalue-mutation-negative/input.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/IntraproceduralSemanticsSmokeTest.scala diff --git a/joern-cli/frontends/lua2cpg/README.md b/joern-cli/frontends/lua2cpg/README.md index 97bb61a223fb..dff4406223d0 100644 --- a/joern-cli/frontends/lua2cpg/README.md +++ b/joern-cli/frontends/lua2cpg/README.md @@ -46,3 +46,24 @@ This E2 smoke proves bytecode artifact/profile/prototype/constant/diagnostic CPG evidence only. It does not claim parser AST semantics, dataflow, QueryDB, sanitizer, report construction, schema extension, distribution acceptance, or official frontend acceptance. + +## Intraprocedural Semantics Smoke + +```bash +sbt 'lua2cpg/testOnly io.joern.lua2cpg.IntraproceduralSemanticsSmokeTest' +sbt 'lua2cpg/stage' +git status --short +``` + +Expected result: + +- `IntraproceduralSemanticsSmokeTest` succeeds. +- `lua2cpg/stage` succeeds. +- `git status --short` is clean after the smoke. + +This E3 smoke proves bytecode-local intraprocedural CPG evidence through +`CALL`, `IDENTIFIER`, `LITERAL`, `METHOD`, and `REACHING_DEF` evidence over +committed focused `.luac` fixtures. It does not claim interprocedural +arg/return, module require/export resolution, source parser AST semantics, +QueryDB, sanitizer classification, report construction, schema extension +acceptance, distribution acceptance, or official frontend acceptance. diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala new file mode 100644 index 000000000000..369ce3cb8d70 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala @@ -0,0 +1,464 @@ +package io.joern.lua2cpg.bytecode + +final case class LuaRegisterEvent(kind: String, prototypeId: String, pc: Int, slot: Int, valueRef: String) + +final case class LuaSemanticStep(sourceRef: String, destRef: String, kind: String) + +final case class LuaClosureValue(slot: Int, valueRef: String, targetPrototypeId: String, provenance: String) + +final case class LuaCallSite( + callsiteId: String, + prototypeId: String, + pc: Int, + opcode: String, + targetValueRef: String, + firstArgSlot: Option[Int], + argCount: Option[Int], + firstReturnSlot: Option[Int], + returnCount: Option[Int] +) + +final case class LuaLocalFlow(sourceRef: String, sinkRef: String, edgeKind: String, provenance: String) + +final case class LuaTableFieldFlow( + tableRef: String, + keyRef: String, + writeRef: String, + readRef: String, + provenance: String +) + +final case class LuaGlobalFlow( + globalName: String, + writeRef: String, + readRef: String, + valueRef: String, + provenance: String +) + +final case class LuaUpvalueFlow( + upvalueId: String, + captureRef: String, + readRef: String, + writeRef: String, + provenance: String +) + +final case class LuaCallTargetCandidate(callsiteId: String, targetRef: String, confidence: String, provenance: String) + +final case class LuaUnresolvedCall(callsiteId: String, unresolvedReason: String, provenance: String) + +final case class LuaKillOverwrite( + killId: String, + prototypeId: String, + firstWrite: String, + laterWrite: String, + readRef: String, + sinkRef: String, + reason: String +) + +final case class LuaNegativeExpectation( + negativeId: String, + sourceRef: String, + sinkRef: String, + kind: String, + reason: String +) + +final case class LuaPrototypeSemantics( + registerEvents: Vector[LuaRegisterEvent], + semanticSteps: Vector[LuaSemanticStep], + closureValues: Vector[LuaClosureValue], + callSites: Vector[LuaCallSite], + localFlows: Vector[LuaLocalFlow], + tableFieldFlows: Vector[LuaTableFieldFlow], + globalFlows: Vector[LuaGlobalFlow], + upvalueFlows: Vector[LuaUpvalueFlow], + callTargetCandidates: Vector[LuaCallTargetCandidate], + unresolvedCalls: Vector[LuaUnresolvedCall], + killOverwrites: Vector[LuaKillOverwrite], + negativeExpectations: Vector[LuaNegativeExpectation] +) + +object LuaInstructionSemantics { + private val RkConstantBase = 256 + private val BytecodeProvenance = "bytecode-only" + private val BoundaryProvenance = "bytecode-boundary" + private val ParamDerivedReason = "param-derived" + private val MutationBoundary = "upvalue-mutation-boundary" + private val UpvalueStaleBoundary = "no-stale-upvalue-reuse-after-setupval" + + def normalize(prototype: LuaPrototype): LuaPrototypeSemantics = { + val builder = Vector.newBuilder[LuaPrototypeSemantics] + builder += normalizeOne(prototype) + prototype.nested.foreach(child => builder += normalize(child)) + combine(builder.result()) + } + + def normalizePrototype(prototype: LuaPrototype): LuaPrototypeSemantics = normalizeOne(prototype) + + private def normalizeOne(prototype: LuaPrototype): LuaPrototypeSemantics = { + val state = new SemanticState(prototype) + prototype.instructions.sortBy(_.pc).foreach(state.visit) + state.result() + } + + private def combine(items: Vector[LuaPrototypeSemantics]): LuaPrototypeSemantics = + LuaPrototypeSemantics( + registerEvents = items.flatMap(_.registerEvents), + semanticSteps = items.flatMap(_.semanticSteps), + closureValues = items.flatMap(_.closureValues), + callSites = items.flatMap(_.callSites), + localFlows = items.flatMap(_.localFlows), + tableFieldFlows = items.flatMap(_.tableFieldFlows), + globalFlows = items.flatMap(_.globalFlows), + upvalueFlows = items.flatMap(_.upvalueFlows), + callTargetCandidates = items.flatMap(_.callTargetCandidates), + unresolvedCalls = items.flatMap(_.unresolvedCalls), + killOverwrites = items.flatMap(_.killOverwrites), + negativeExpectations = items.flatMap(_.negativeExpectations) + ) + + private final class SemanticState(prototype: LuaPrototype) { + private val registerEvents = Vector.newBuilder[LuaRegisterEvent] + private val semanticSteps = Vector.newBuilder[LuaSemanticStep] + private val closureValues = Vector.newBuilder[LuaClosureValue] + private val callSites = Vector.newBuilder[LuaCallSite] + private val localFlows = Vector.newBuilder[LuaLocalFlow] + private val tableFieldFlows = Vector.newBuilder[LuaTableFieldFlow] + private val globalFlows = Vector.newBuilder[LuaGlobalFlow] + private val upvalueFlows = Vector.newBuilder[LuaUpvalueFlow] + private val callTargetCandidates = Vector.newBuilder[LuaCallTargetCandidate] + private val unresolvedCalls = Vector.newBuilder[LuaUnresolvedCall] + private val killOverwrites = Vector.newBuilder[LuaKillOverwrite] + private val negativeExpectations = Vector.newBuilder[LuaNegativeExpectation] + + private var reaching = (0 until prototype.numParams).map(slot => slot -> Set(staticSlotRef(slot))).toMap + private var closuresBySlot = Map.empty[Int, LuaClosureValue] + private var tableWrites = Map.empty[(Int, String), Set[String]] + private var globalWrites = Map.empty[String, Set[String]] + private var mutatedUpvalues = Set.empty[Int] + + def visit(instruction: LuaInstruction): Unit = { + instruction.opcode match { + case LuaOpcode.Move => + val source = readSlot(instruction, instruction.b) + writeSlot(instruction, instruction.a, Set(source), "move") + closuresBySlot.get(instruction.b).foreach { closure => + val moved = closure.copy(slot = instruction.a, valueRef = slotRef(instruction.pc, instruction.a)) + closuresBySlot += instruction.a -> moved + closureValues += moved + } + case LuaOpcode.LoadK => + writeSlot(instruction, instruction.a, Set(constantRef(instruction.b)), "loadk") + case LuaOpcode.LoadBool | LuaOpcode.LoadNil | LuaOpcode.NewTable | LuaOpcode.Vararg => + writeSlot( + instruction, + instruction.a, + Set(slotRef(instruction.pc, instruction.a)), + instruction.opcode.mnemonic.toLowerCase + ) + case LuaOpcode.Closure => + val target = nestedPrototypeId(instruction.b) + val value = slotRef(instruction.pc, instruction.a) + writeSlot(instruction, instruction.a, Set(value), "closure") + val closure = LuaClosureValue(instruction.a, value, target, BytecodeProvenance) + closuresBySlot += instruction.a -> closure + closureValues += closure + case LuaOpcode.Call | LuaOpcode.TailCall => + handleCall(instruction) + case LuaOpcode.Concat => + handleConcat(instruction) + case LuaOpcode.GetUpval => + val read = slotRef(instruction.pc, instruction.a) + writeSlot(instruction, instruction.a, Set(read), "getupval") + upvalueFlows += LuaUpvalueFlow(upvalueRef(instruction.b), read, read, read, BytecodeProvenance) + if (mutatedUpvalues(instruction.b)) { + addBoundary(UpvalueStaleBoundary, read, read, "upvalue mutation invalidates earlier read") + } + case LuaOpcode.SetUpval => + val read = readSlot(instruction, instruction.a) + mutatedUpvalues += instruction.b + addBoundary( + MutationBoundary, + read, + upvalueRef(instruction.b), + "SETUPVAL introduces an explicit mutation boundary" + ) + case LuaOpcode.GetGlobal => + val write = slotRef(instruction.pc, instruction.a) + writeSlot(instruction, instruction.a, Set(write), "getglobal") + stringConstant(instruction.b).foreach { name => + globalWrites.get(name).foreach { sources => + sources.foreach { source => + globalFlows += LuaGlobalFlow(name, source, write, source, BytecodeProvenance) + } + } + } + case LuaOpcode.SetGlobal => + val value = readSlot(instruction, instruction.a) + stringConstant(instruction.b).foreach(name => globalWrites += name -> Set(value)) + case LuaOpcode.GetTable => + handleGetTable(instruction) + case LuaOpcode.SetTable => + handleSetTable(instruction) + case LuaOpcode.Self => + handleSelf(instruction) + case LuaOpcode.Return => + readReturnSlots(instruction).foreach(readSlot(instruction, _)) + case LuaOpcode.Eq | LuaOpcode.Lt | LuaOpcode.Le => + rkRegister(instruction.b).foreach(readSlot(instruction, _)) + instruction.c.flatMap(rkRegister).foreach(readSlot(instruction, _)) + case _ => + instruction.c.foreach { c => + rkRegister(instruction.b).foreach(readSlot(instruction, _)) + rkRegister(c).foreach(readSlot(instruction, _)) + } + } + } + + def result(): LuaPrototypeSemantics = { + LuaPrototypeSemantics( + registerEvents = registerEvents.result(), + semanticSteps = semanticSteps.result(), + closureValues = closureValues.result(), + callSites = callSites.result(), + localFlows = localFlows.result(), + tableFieldFlows = tableFieldFlows.result(), + globalFlows = globalFlows.result(), + upvalueFlows = upvalueFlows.result(), + callTargetCandidates = callTargetCandidates.result(), + unresolvedCalls = unresolvedCalls.result(), + killOverwrites = killOverwrites.result(), + negativeExpectations = negativeExpectations.result() + ) + } + + private def handleCall(instruction: LuaInstruction): Unit = { + val targetClosure = closuresBySlot.get(instruction.a) + val targetRead = readSlot(instruction, instruction.a) + val argSlots = callArgumentSlots(instruction) + argSlots.foreach(readSlot(instruction, _)) + val returnSlots = callReturnSlots(instruction) + returnSlots.foreach { slot => + writeSlot(instruction, slot, Set(slotRef(instruction.pc, slot)), "call-return") + } + val callsite = LuaCallSite( + callsiteId = instructionRef(instruction.pc), + prototypeId = prototype.prototypeId, + pc = instruction.pc, + opcode = instruction.opcode.mnemonic, + targetValueRef = targetRead, + firstArgSlot = argSlots.headOption, + argCount = Some(argSlots.size), + firstReturnSlot = returnSlots.headOption, + returnCount = Some(returnSlots.size) + ) + callSites += callsite + targetClosure match { + case Some(closure) => + callTargetCandidates += LuaCallTargetCandidate( + callsite.callsiteId, + closure.targetPrototypeId, + "candidate", + BytecodeProvenance + ) + case None => + if (isParamDerived(instruction.a)) { + unresolvedCalls += LuaUnresolvedCall(callsite.callsiteId, ParamDerivedReason, BoundaryProvenance) + } + } + } + + private def handleConcat(instruction: LuaInstruction): Unit = { + val c = requireC(instruction) + val sources = (instruction.b to c).map(slot => readSlot(instruction, slot)).toSet + writeSlot(instruction, instruction.a, sources, "concat") + } + + private def handleGetTable(instruction: LuaInstruction): Unit = { + val tableSlot = instruction.b + readSlot(instruction, tableSlot) + instruction.c.flatMap(rkRegister).foreach(readSlot(instruction, _)) + val write = slotRef(instruction.pc, instruction.a) + writeSlot(instruction, instruction.a, Set(write), "gettable") + instruction.c.flatMap(rkConstantRef).foreach { key => + tableWrites.get((tableSlot, key)).foreach { sources => + sources.foreach { source => + tableFieldFlows += LuaTableFieldFlow( + slotRef(instruction.pc, tableSlot), + key, + source, + write, + BytecodeProvenance + ) + } + } + } + if (isGlobalEnvironmentTable(tableSlot)) { + instruction.c.flatMap(rkConstantName).foreach { name => + globalWrites.get(name).foreach { sources => + sources.foreach { source => + globalFlows += LuaGlobalFlow(name, source, write, source, BytecodeProvenance) + } + } + } + } + } + + private def handleSetTable(instruction: LuaInstruction): Unit = { + val tableSlot = instruction.a + readSlot(instruction, tableSlot) + rkRegister(instruction.b).foreach(readSlot(instruction, _)) + val valueRefs = instruction.c.flatMap(rkRegister).map(readSlot(instruction, _)).toSet + instruction.bOptionConstantString.foreach { key => + tableWrites += (tableSlot, key) -> valueRefs + } + if (isGlobalEnvironmentTable(tableSlot)) { + instruction.bOptionConstantName.foreach { name => + if (valueRefs.nonEmpty) { + globalWrites += name -> valueRefs + } + } + } + } + + private def handleSelf(instruction: LuaInstruction): Unit = { + readSlot(instruction, instruction.b) + instruction.c.flatMap(rkRegister).foreach(readSlot(instruction, _)) + writeSlot(instruction, instruction.a + 1, Set(slotRef(instruction.pc, instruction.b)), "self-base") + writeSlot(instruction, instruction.a, Set(slotRef(instruction.pc, instruction.a)), "self-member") + } + + private def readReturnSlots(instruction: LuaInstruction): Seq[Int] = + instruction.b match { + case 0 => Seq.empty + case 1 => Seq.empty + case n => instruction.a until (instruction.a + n - 1) + } + + private def callArgumentSlots(instruction: LuaInstruction): Seq[Int] = + instruction.b match { + case 0 => Seq.empty + case 1 => Seq.empty + case n => (instruction.a + 1) until (instruction.a + n) + } + + private def callReturnSlots(instruction: LuaInstruction): Seq[Int] = + instruction.c match { + case Some(0) => Seq(instruction.a) + case Some(1) => Seq.empty + case Some(n) => instruction.a until (instruction.a + n - 1) + case None => Seq.empty + } + + private def readSlot(instruction: LuaInstruction, slot: Int): String = { + val read = slotRef(instruction.pc, slot) + registerEvents += LuaRegisterEvent("read", prototype.prototypeId, instruction.pc, slot, read) + reaching.get(slot).toSeq.flatten.foreach { source => + localFlows += LuaLocalFlow(source, read, "may-reaching-definition", BytecodeProvenance) + } + read + } + + private def writeSlot(instruction: LuaInstruction, slot: Int, sources: Set[String], kind: String): Unit = { + val write = slotRef(instruction.pc, slot) + registerEvents += LuaRegisterEvent("write", prototype.prototypeId, instruction.pc, slot, write) + sources.filterNot(_ == write).foreach { source => + localFlows += LuaLocalFlow(source, write, "same-instruction-dependence", BytecodeProvenance) + semanticSteps += LuaSemanticStep(source, write, kind) + } + reaching.get(slot).foreach { prior => + if (prior.nonEmpty && !prior.contains(write)) { + prior.foreach { first => + killOverwrites += LuaKillOverwrite( + s"${prototype.prototypeId}:pc${instruction.pc}:r$slot:kills:$first", + prototype.prototypeId, + first, + write, + write, + write, + "same-slot-overwrite-kills-prior-definition" + ) + } + } + } + } + reaching += slot -> Set(write) + closuresBySlot -= slot + + private def isParamDerived(slot: Int): Boolean = + reachesParameter(reaching.getOrElse(slot, Set.empty), Set.empty) + + private def reachesParameter(refs: Set[String], seen: Set[String]): Boolean = { + val pending = refs.diff(seen) + pending.exists(_.startsWith(s"${prototype.prototypeId}:r")) || { + val parents = localFlows + .result() + .collect { + case flow if pending(flow.sinkRef) => flow.sourceRef + } + .toSet + parents.nonEmpty && reachesParameter(parents, seen ++ pending) + } + } + + private def isGlobalEnvironmentTable(slot: Int): Boolean = + reaching.get(slot).toSeq.flatten.exists { ref => + val globalGet = prototype.instructions.exists { instruction => + slotRef(instruction.pc, instruction.a) == ref && + instruction.opcode == LuaOpcode.GetGlobal && + stringConstant(instruction.b).contains("_G") + } + globalGet + } + + private def addBoundary(kind: String, sourceRef: String, sinkRef: String, reason: String): Unit = + negativeExpectations += LuaNegativeExpectation(s"$kind:$sourceRef->$sinkRef", sourceRef, sinkRef, kind, reason) + + private def slotRef(pc: Int, slot: Int): String = s"${prototype.prototypeId}@pc$pc:r$slot" + + private def instructionRef(pc: Int): String = s"${prototype.prototypeId}@pc$pc" + + private def staticSlotRef(slot: Int): String = s"${prototype.prototypeId}:r$slot" + + private def constantRef(index: Int): String = s"${prototype.prototypeId}:k$index" + + private def upvalueRef(index: Int): String = s"${prototype.prototypeId}:u$index" + + private def nestedPrototypeId(ordinal: Int): String = s"${prototype.prototypeId}.$ordinal" + + private def stringConstant(index: Int): Option[String] = + prototype.constants.collectFirst { case LuaConstant(`index`, "string", LuaConstantValue.StringValue(value)) => + value + } + + private def rkRegister(value: Int): Option[Int] = + if (value < RkConstantBase) Some(value) else None + + private def rkConstantRef(value: Int): Option[String] = + if (value >= RkConstantBase) Some(constantRef(value - RkConstantBase)) else None + + private def rkConstantName(value: Int): Option[String] = + if (value >= RkConstantBase) stringConstant(value - RkConstantBase) else None + + private def requireC(instruction: LuaInstruction): Int = + instruction.c match { + case Some(value) => value + case None => + throw new IllegalArgumentException( + s"${instruction.opcode.mnemonic} at ${prototype.prototypeId}@pc${instruction.pc} has no C operand" + ) + } + + extension (instruction: LuaInstruction) { + private def bOptionConstantString: Option[String] = + if (instruction.b >= RkConstantBase) Some(constantRef(instruction.b - RkConstantBase)) else None + + private def bOptionConstantName: Option[String] = + if (instruction.b >= RkConstantBase) stringConstant(instruction.b - RkConstantBase) else None + } + + } +} diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/intraprocedural-semantics/bc-call-candidate-unresolved/input.luac b/joern-cli/frontends/lua2cpg/src/test/resources/intraprocedural-semantics/bc-call-candidate-unresolved/input.luac new file mode 100644 index 0000000000000000000000000000000000000000..9672d77f6b773d24835790d977ab7b9a5264f6db GIT binary patch literal 444 zcmZ`#F^|SKT^MF=LI1lFIvBcb UdRK?^H?*?dv}1R1Y5QzF0SF!~S^xk5 literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/intraprocedural-semantics/bc-kill-overwrite/input.luac b/joern-cli/frontends/lua2cpg/src/test/resources/intraprocedural-semantics/bc-kill-overwrite/input.luac new file mode 100644 index 0000000000000000000000000000000000000000..7ecd2a1f96b93674b93debece40e9b9938996301 GIT binary patch literal 417 zcmZ8d%MHRX40TF@0wW-C;##^w2jId8p$$?*)JjbP9LpXI!ypWUU(#^kN&dVS+i|iz zdU+y|7kNP?Mbu7NXRC{TI6o*`otyHijVb$^8t#L3sE~le%-N4Ge`C0-KtH zp4^<1C5WI5HEAFu02)?84K4%+9>GHa@Ip*S6>`{`ZS!ZThGmMM6^3GQECN{FUBfL9 O(`VeI@rrU*(i^|Yh9{N) literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/intraprocedural-semantics/bc-table-global-upvalue/input.luac b/joern-cli/frontends/lua2cpg/src/test/resources/intraprocedural-semantics/bc-table-global-upvalue/input.luac new file mode 100644 index 0000000000000000000000000000000000000000..9bbb5ed371ef94264fdfd3e94207508d979f7874 GIT binary patch literal 476 zcmZutF-`+95F7^w0yOkU{NT8P4_t>pG`t}4$?Iznf_vY!FWJ#54g$l9QLp~q7`b(w+ZOg5n&95r}VcK za6{HN`X8)eskOn%e5|oj?tNKF#z;oB)^d|Q`PZH1_!{=C%R}upF1c~nUz?SZ3Webw z=)XPmJ2lkz&`yUja6;%tmfDjt8jd(+(fbDd#6%<s( C?Kg!0 literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/intraprocedural-semantics/d24-defuse-transitive-chain/input.luac b/joern-cli/frontends/lua2cpg/src/test/resources/intraprocedural-semantics/d24-defuse-transitive-chain/input.luac new file mode 100644 index 0000000000000000000000000000000000000000..33f82f48d2da49442bfe50006b29a0f2358ae466 GIT binary patch literal 460 zcmZutOA5j;6nv@m?+t{m+}45!ZhHVX9zY?s5ra~pNyKFi;$b|RZ_-w}aNy--Ci6b+ za_{7UL_CQnw4(;ORmPg^qF>h4%4Fqco0jV0j7n`SE2FJ`sO!sWPPWBJ z(Rj}1ilfYlJm7+Dp)dZ#Zz{hve=9Uc c37!C>t6P``v70I>X4Y5Ep`fo6VP7b~7r+`QuK)l5 literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/intraprocedural-semantics/d24-defuse-unrelated-register-negative/input.luac b/joern-cli/frontends/lua2cpg/src/test/resources/intraprocedural-semantics/d24-defuse-unrelated-register-negative/input.luac new file mode 100644 index 0000000000000000000000000000000000000000..a7f53bfc1ba47700fd9e4a69f801a721ef29a24c GIT binary patch literal 588 zcmZuuOHRWu5FLj?%SXT-)D_zh32}gkH5(29l4+)uMPwv@P?zQ|9E5{#80OhYl}dfm zvuECn=Of=dNBu%-wwNvGi3-}lKKN#Bwjnz9P51Cvcf5|C>u4SKI&fDz-WVU))s{Cp zm@jVpXLKvR@1q{-;Hu^qa^4BMmgpxD{Swh%5<`)?{Z7=XL`hNgD*l@yT9U*BNBJy= z$fI9Dhrg5yxv*vUf}o9+>9T{wYT&e3$s{@!V-81}BX2AS34nGYL=Kw32e$cyGR3Jl zSXFR%M3e$nJ!_jS40R?v7vkx(iQDrKMK(r(eL3(-Zt(g5ck4;4d`&KiW(Wb^*pIvx YL`(wgzBqS8WL#Y5hg0P}PXq<{2hX`Ry#N3J literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/intraprocedural-semantics/d24-global-dynamic-env-negative/input.luac b/joern-cli/frontends/lua2cpg/src/test/resources/intraprocedural-semantics/d24-global-dynamic-env-negative/input.luac new file mode 100644 index 0000000000000000000000000000000000000000..7a93f17900261ffecbfd042f2a88a832535785c5 GIT binary patch literal 354 zcmZ8b!EVAZ40TvwY=SR9;t#q?({8(TCoWw00u)+PqJ?B?l4`y6!hyfy!hd16MM6C3 z+0Kjo?8Q2&4G1}vQ}~7+SwJ6ry4U|9R_N2>^D8Nh+bNS2ja5gTCur-$VyS|zG4(E1 zIewU^1_Mr`$vZ_s{K)`LEx?&Gy{{Jv0z@WSv1CFEk|a5!!02~vX%hmS)#Fb!#;F42 z%OGr5Am>AU)ZS}b_9Lv_caNy z*UxWWo*^qX#RiTrLpZd~)*tHI`4Mg1?hlod7mZar8QH6*`ogI)*h!~uSlhvm4ZdnG zmwI;jdPkOdBHjhSHU#(y0?LN~kp()Czmt7|00rgH0y9Q$oa*sUxXMWnvW22tGH5fT zXDaBGwaRoc$P+VSPFxY6h-)I}rj3&47*A%ni;i_$x+jf(8~d=9M&G3N7(27dof*yN QV9~^3EdO7Oi&j)&Kwi literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/intraprocedural-semantics/d24-upvalue-mutation-negative/input.luac b/joern-cli/frontends/lua2cpg/src/test/resources/intraprocedural-semantics/d24-upvalue-mutation-negative/input.luac new file mode 100644 index 0000000000000000000000000000000000000000..b12ecda4b10d3dcd6c6c28c92e53660d686076a4 GIT binary patch literal 405 zcmZXQOAdlC5QdAQ60cz54UojRai@t37j8U4mBE-G7}}~!E~01gX#RhJg*xP$2lME( zlbzFh6@*b3sikUUWyM&NWao!D)n^nF~W^AZ4EVA5ElGA5H{NguS4^8f?J^)Zh*MQg!e*tdKj> z%>XtIT86OPs)K`jZ49Fhl9iSCJy^nWiq5Q5euM5x97r)Dsh`HXa2gr^z!Sb2Slq1w MSNRtef93AT4-7XgRR910 literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/IntraproceduralSemanticsSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/IntraproceduralSemanticsSmokeTest.scala new file mode 100644 index 000000000000..a4273c260fc9 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/IntraproceduralSemanticsSmokeTest.scala @@ -0,0 +1,159 @@ +package io.joern.lua2cpg + +import io.shiftleft.codepropertygraph.cpgloading.CpgLoader +import io.shiftleft.codepropertygraph.generated.EdgeTypes +import io.shiftleft.codepropertygraph.generated.nodes.{Identifier, StoredNode} +import io.shiftleft.semanticcpg.language.* +import io.shiftleft.semanticcpg.utils.FileUtil +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec + +import java.nio.file.Paths + +class IntraproceduralSemanticsSmokeTest extends AnyWordSpec with Matchers { + + "Lua2Cpg" should { + "emit intraprocedural bytecode semantics that survive CPG reopen" in { + val resourceRoot = Paths.get(getClass.getClassLoader.getResource("intraprocedural-semantics").toURI) + + FileUtil.usingTemporaryDirectory("lua2cpg-intraprocedural-semantics-smoke") { tmpDir => + val outputPath = tmpDir.resolve("intraprocedural-semantics.cpg.bin").toString + val cpg = new Lua2Cpg() + .createCpg(Config().withInputPath(resourceRoot.toString).withOutputPath(outputPath)) + .get + cpg.close() + + val reopened = CpgLoader.load(outputPath) + try { + hasReachingDef(reopened, "bc-kill-overwrite", "root@pc4:r2", "root@pc7:r4") shouldBe true + hasReachingDef(reopened, "d24-defuse-transitive-chain", "root@pc3:r2", "root@pc8:r6") shouldBe true + + hasReachingDef(reopened, "bc-kill-overwrite", "root@pc3:r2", "root@pc7:r4") shouldBe false + hasReachingDef(reopened, "d24-defuse-unrelated-register-negative", "root@pc4:r2", "root@pc9:r6") shouldBe false + hasReachingDef(reopened, "d24-table-dynamic-key-negative", "root@pc3:r2", "root@pc4:r3") shouldBe false + hasReachingDef(reopened, "d24-table-dynamic-key-negative", "root@pc3:r2", "root@pc5:r4") shouldBe false + hasReachingDef(reopened, "d24-global-dynamic-env-negative", "root@pc3:r1", "root@pc4:r2") shouldBe false + hasReachingDef(reopened, "d24-global-dynamic-env-negative", "root@pc3:r1", "root@pc5:r3") shouldBe false + hasReachingDef(reopened, "d24-upvalue-mutation-negative", "root.0@pc0:r0", "root.0@pc3:r1") shouldBe false + + hasSemanticEdge( + reopened, + "bc-table-global-upvalue", + "table:root.0@pc3:r1:root.0:k0", + "root.0@pc1:r0", + "root.0@pc3:r3" + ) shouldBe true + hasReachingDef(reopened, "bc-table-global-upvalue", "root.0@pc6:r3", "root.0@pc8:r2") shouldBe true + hasSemanticEdge(reopened, "bc-table-global-upvalue", "upvalue:root.0:u0", "root.0@pc4:r4", "root.0@pc4:r4") shouldBe true + + reopened.call + .nameExact("lua.calltarget.candidate") + .codeExact("root@pc5 -> root.1") + .nonEmpty shouldBe true + reopened.call + .nameExact("lua.calltarget.unresolved") + .codeExact("root.1@pc2 unresolved=param-derived") + .nonEmpty shouldBe true + reopened.call + .nameExact("lua.calltarget.candidate") + .code(".*source-function-name.*") + .isEmpty shouldBe true + + val expectedBoundaries = Seq( + "bc-kill-overwrite:no-tainted-source-after-overwrite", + "d24-defuse-unrelated-register-negative:no-defuse-cross-prototype-register-reuse", + "d24-table-dynamic-key-negative:no-table-field-flow-dynamic-key", + "d24-table-dynamic-key-negative:no-table-field-flow-missing-field", + "d24-global-dynamic-env-negative:no-global-flow-dynamic-env-write", + "d24-global-dynamic-env-negative:no-global-flow-missing-precise-name", + "d24-upvalue-mutation-negative:no-stale-upvalue-reuse-after-setupval", + "d24-upvalue-mutation-negative:upvalue-mutation-boundary", + "bc-call-candidate-unresolved:no-guessed-source-target" + ) + val actualBoundaries = reopened.call.nameExact("lua.semantic.boundary").code.l.toSet + expectedBoundaries.diff(actualBoundaries.toSeq).toList.shouldBe(Nil) + + val nodeCount = reopened.graph.allNodes.size + val reachingDefCount = reopened.identifier.outE(EdgeTypes.REACHING_DEF).size + (nodeCount > 0) shouldBe true + (reachingDefCount > 0) shouldBe true + info(s"intraprocedural-semantics node_count=$nodeCount reaching_def_edge_count=$reachingDefCount") + } finally { + reopened.close() + } + } + } + } + + private def hasReachingDef( + cpg: io.shiftleft.codepropertygraph.generated.Cpg, + fixtureId: String, + sourceCode: String, + sinkCode: String + ): Boolean = + hasSemanticEdge(cpg, fixtureId, sourceCode, sourceCode, sinkCode) || transitiveReachingDef(cpg, fixtureId, sourceCode, sinkCode) + + private def hasSemanticEdge( + cpg: io.shiftleft.codepropertygraph.generated.Cpg, + fixtureId: String, + variable: String, + sourceCode: String, + sinkCode: String + ): Boolean = { + val fixtureIdentifiers = identifiersInFixture(cpg, fixtureId) + val sinkIds = fixtureIdentifiers + .filter(_.code == sinkCode) + .map(_.id) + .toSet + + fixtureIdentifiers + .filter(_.code == sourceCode) + .outE(EdgeTypes.REACHING_DEF) + .filter(edge => Option(edge.property).contains(variable)) + .exists { edge => + val sink = edge.dst.asInstanceOf[StoredNode] + sinkIds.contains(sink.id) + } + } + + private def transitiveReachingDef( + cpg: io.shiftleft.codepropertygraph.generated.Cpg, + fixtureId: String, + sourceCode: String, + sinkCode: String + ): Boolean = { + val fixtureIdentifiers = identifiersInFixture(cpg, fixtureId) + val fixtureIdentifierCodes = fixtureIdentifiers + .map(identifier => identifier.id -> identifier.code) + .toMap + + val graph = fixtureIdentifiers + .outE(EdgeTypes.REACHING_DEF) + .flatMap { edge => + val sourceNode = edge.src.asInstanceOf[StoredNode] + val sinkNode = edge.dst.asInstanceOf[StoredNode] + val source = fixtureIdentifierCodes.get(sourceNode.id) + val sink = fixtureIdentifierCodes.get(sinkNode.id) + source.zip(sink).headOption + } + .foldLeft(Map.empty[String, Set[String]]) { case (acc, (source, sink)) => + acc.updated(source, acc.getOrElse(source, Set.empty) + sink) + } + val seen = scala.collection.mutable.Set.empty[String] + val work = scala.collection.mutable.Stack(sourceCode) + while (work.nonEmpty) { + val current = work.pop() + if (current == sinkCode) { + return true + } + if (!seen(current)) { + seen += current + graph.getOrElse(current, Set.empty).diff(seen.toSet).foreach(work.push) + } + } + false + } + + private def identifiersInFixture(cpg: io.shiftleft.codepropertygraph.generated.Cpg, fixtureId: String): List[Identifier] = + cpg.method.filename(s".*$fixtureId/input\\.luac").ast.isIdentifier.l +} From 9748e4d7b0928e41c3e8008252221ac66ff176fc Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 8 Jul 2026 14:25:32 -0400 Subject: [PATCH 013/105] feat(lua2cpg): add interprocedural module taint semantics --- .../bytecode/LuaProgramSemantics.scala | 3513 +++++++++++++++++ .../SAMPLE-MANIFEST.md | 22 + .../bc-branch-negative/input.luac | Bin 0 -> 504 bytes .../bc-kill-overwrite/input.luac | Bin 0 -> 417 bytes .../bc-taint-minimal-path/input.luac | Bin 0 -> 398 bytes .../input.luac | Bin 0 -> 917 bytes .../controller.luac | Bin 0 -> 721 bytes .../mtkwifi.luac | Bin 0 -> 488 bytes .../input.luac | Bin 0 -> 783 bytes .../ambiguous.luac | Bin 0 -> 377 bytes .../controller.luac | Bin 0 -> 409 bytes .../left.luac | Bin 0 -> 341 bytes .../missing.luac | Bin 0 -> 376 bytes .../right.luac | Bin 0 -> 342 bytes .../controller.luac | Bin 0 -> 390 bytes .../library.luac | Bin 0 -> 328 bytes .../controller.luac | Bin 0 -> 844 bytes .../library.luac | Bin 0 -> 331 bytes .../InterproceduralModuleTaintSmokeTest.scala | 100 + 19 files changed, 3635 insertions(+) create mode 100644 joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaProgramSemantics.scala create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/SAMPLE-MANIFEST.md create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/bc-branch-negative/input.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/bc-kill-overwrite/input.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/bc-taint-minimal-path/input.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d16-rf-interprocedural-formvalue-execute/input.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d16-rf-webcmd-cross-module-popen/controller.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d16-rf-webcmd-cross-module-popen/mtkwifi.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-interproc-unresolved-callee-negative/input.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-ambiguous-unresolved-dynamic-negative/ambiguous.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-ambiguous-unresolved-dynamic-negative/controller.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-ambiguous-unresolved-dynamic-negative/left.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-ambiguous-unresolved-dynamic-negative/missing.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-ambiguous-unresolved-dynamic-negative/right.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-missing-field-negative/controller.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-missing-field-negative/library.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-return-table-field-call/controller.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-return-table-field-call/library.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/InterproceduralModuleTaintSmokeTest.scala diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaProgramSemantics.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaProgramSemantics.scala new file mode 100644 index 000000000000..e93c474a68e2 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaProgramSemantics.scala @@ -0,0 +1,3513 @@ +package io.joern.lua2cpg.bytecode + +final case class LuaModuleResolution( + requireCallsiteId: String, + requireString: String, + resolutionStatus: String, + fromModulePath: String, + targetModulePath: Option[String], + unresolvedReason: Option[String], + provenance: String +) + +final case class LuaModuleReturnTable( + modulePath: String, + tableRef: String, + fieldName: String, + targetPrototypeId: String, + provenance: String +) + +final case class LuaModuleFieldCallTarget( + fromModulePath: String, + callsiteId: String, + fieldName: String, + targetModulePath: String, + targetPrototypeId: String, + provenance: String +) + +final case class LuaInterproceduralArgFlow( + callsiteId: String, + fromArgumentRef: String, + argumentIndex: Int, + targetModulePath: String, + targetPrototypeId: String, + toParameterRef: String, + provenance: String +) + +final case class LuaInterproceduralReturnFlow( + callsiteId: String, + targetModulePath: String, + targetPrototypeId: String, + calleeReturnRef: String, + callerResultRef: String, + provenance: String +) + +final case class LuaCrossBoundaryCallTarget( + fromModulePath: String, + callsiteId: String, + targetModulePath: String, + targetPrototypeId: String, + confidence: String, + provenance: String +) + +final case class LuaTaintPath( + sourceRef: String, + sinkRef: String, + pathSteps: Vector[String], + classification: String, + provenance: String +) + +final case class LuaE4Boundary(boundaryId: String, boundaryKind: String, reason: String) + +final case class LuaRuleMatch( + callsiteId: String, + ruleKind: String, + trigger: String, + matchedName: String, + parameterIndex: Option[Int], + provenance: String +) + +final case class LuaSourceEndpoint(sourceRef: String, callsiteId: String, trigger: String, provenance: String) + +final case class LuaSinkEndpoint( + sinkRef: String, + callsiteId: String, + trigger: String, + parameterIndex: Int, + provenance: String +) + +final case class LuaSanitizerCall( + callsiteId: String, + sanitizerName: String, + sanitizedValueRef: String, + provenance: String +) + +final case class LuaSanitizerClassification( + sourceRef: String, + sinkRef: String, + sanitizerCallsiteId: String, + sanitizerName: String, + appliesToSink: Boolean, + onDataflowChain: Boolean, + classification: String +) + +final case class LuaReportClassification(sourceRef: String, sinkRef: String, classification: String, reason: String) + +final case class LuaVulnerabilityReport( + sourceRef: String, + sinkRef: String, + pathStatus: String, + classification: String, + pathSteps: Vector[String], + provenance: String +) + +final case class LuaE5Boundary(boundaryId: String, boundaryKind: String, reason: String) + +final case class LuaProgramSemantics( + moduleResolutions: Vector[LuaModuleResolution], + moduleReturnTables: Vector[LuaModuleReturnTable], + moduleFieldCallTargets: Vector[LuaModuleFieldCallTarget], + interproceduralArgFlows: Vector[LuaInterproceduralArgFlow], + interproceduralReturnFlows: Vector[LuaInterproceduralReturnFlow], + crossBoundaryCallTargets: Vector[LuaCrossBoundaryCallTarget], + taintPaths: Vector[LuaTaintPath], + boundaries: Vector[LuaE4Boundary], + ruleMatches: Vector[LuaRuleMatch], + sourceEndpoints: Vector[LuaSourceEndpoint], + sinkEndpoints: Vector[LuaSinkEndpoint], + sanitizerCalls: Vector[LuaSanitizerCall], + sanitizerClassifications: Vector[LuaSanitizerClassification], + reportClassifications: Vector[LuaReportClassification], + vulnerabilityReports: Vector[LuaVulnerabilityReport], + e5Boundaries: Vector[LuaE5Boundary], + pathSearchStats: LuaPathSearchStats, + performanceAttribution: LuaPerformanceAttribution +) + +final case class LuaPairPerformanceProfile( + sourceRef: String, + sinkRef: String, + sourceCallsiteId: String, + sinkCallsiteId: String, + sourceTrigger: String, + sinkTrigger: String, + counters: Map[String, Long] +) { + val pairId: String = + s"$sourceRef|$sourceCallsiteId|$sourceTrigger->$sinkRef|$sinkCallsiteId|$sinkTrigger" +} + +final case class LuaPerformanceAttribution( + p1CandidateCount: Long, + p1RejectedCount: Long, + p1AcceptedCount: Long, + unattributedChangedFamilyWork: Long, + pairProfiles: Vector[LuaPairPerformanceProfile], + aggregateCounters: Map[String, Long] = Map.empty +) + +final case class LuaPathSearchStats( + localPathGraphModuleCount: Int, + localPathGraphBuildCount: Int, + localPathSearchCount: Int, + distinctLocalPathQueryCount: Int, + sourceSinkPairCount: Int, + qualifiedSourceSinkPairCount: Int, + prototypePrunedSourceSinkPairCount: Int +) + +object LuaProgramSemantics { + private val RkConstantBase = 256 + private val Provenance = "bytecode-only" + private val BoundaryProvenance = "bytecode-boundary" + private val RealFirmwareSanitizerSuffixes = Set( + "shellquote", + "tonumber", + "parseCmdline", + "_strformat", + "_cmdformat", + "macaddr", + "macFormat", + "ip4addr", + "injection_test", + "check_iface_name", + "includeQuote", + "checkTime", + "doShell", + "checkIp", + "includeXxs", + "filterExecShell", + "binaryBase64Enc", + "decCiphertext", + "sha256", + "setMacFilter", + "setIpFilter", + "apcli_get_connect", + "setWifiAPMode", + "cmdSafeCheck", + "checkLanIpMask", + "ipaddr", + "lan_wan_ip_conflict_chk", + "licenseActivated", + "is_activated", + "check_mac", + "set_mac_filter", + "encode", + "getDstRule", + "local_dev_data_check", + "stat", + "check_if_whitelist_opcode", + "param_safety_check", + "sqlite3_db_execute", + "getStorageMountPathByUuid", + "getWanIfname", + "hackCharsCheck", + "open", + "del", + "ip4mac", + "match", + "r29_0", + "r3_0", + "filePathGet", + "checkPort", + "setPTRules", + "apcli_get_ifname_form_band" + ) + private val PairCounterNames = Vector( + "source_reachability_check_count", + "source_reachability_accepted_count", + "prototype_unreachable_pair_count", + "source_specific_provenance_pruned_pair_count", + "parameter_position_check_count", + "parameter_position_accepted_count", + "parameter_position_pruned_count", + "path_constructor_check_count", + "path_constructor_accepted_count", + "path_constructor_pruned_count", + "bridge_argument_provenance_candidate_count", + "bridge_candidate_pc_pruned_count", + "bridge_candidate_reachability_pruned_count", + "bridge_local_path_attempt_count", + "bridge_local_path_success_count", + "local_path_search_count", + "distinct_local_path_query_count", + "local_path_cache_hit_count", + "local_path_cache_miss_count", + "local_path_graph_build_count", + "local_path_graph_cache_hit_count", + "local_path_graph_cache_miss_count", + "bridge_path_cache_hit_count", + "bridge_path_cache_miss_count", + "targeted_search_node_visit_count", + "targeted_search_edge_visit_count", + "early_candidate_short_circuit_count", + "taint_path_count", + "report_count" + ) + + private final class PerformanceAttributionCollector { + private final case class PairKey(source: LuaSourceEndpoint, sink: LuaSinkEndpoint) + private final case class ActivePair( + key: PairKey, + var counters: scala.collection.mutable.Map[String, Long], + var bridgeCandidates: Long = 0L, + var bridgePcPruned: Long = 0L, + var bridgeReachabilityPruned: Long = 0L, + var earlyShortCircuits: Long = 0L, + var producedPath: Boolean = false, + var pathAccepted: Long = 0L, + var pathPruned: Long = 0L + ) + private val profiles = + scala.collection.mutable.LinkedHashMap.empty[PairKey, scala.collection.mutable.Map[String, Long]] + private val aggregates = scala.collection.mutable.Map.empty[String, Long] + private var activePair: Option[ActivePair] = None + private var p1Candidates = 0L + private var p1Rejected = 0L + private var p1Accepted = 0L + private var unattributed = 0L + + def recordP1(accepted: Boolean): Unit = { + p1Candidates += 1 + if (accepted) p1Accepted += 1 else p1Rejected += 1 + } + + def withPair[A](source: LuaSourceEndpoint, sink: LuaSinkEndpoint)(work: => A): A = { + val previous = activePair + val key = PairKey(source, sink) + val current = ActivePair( + key, + scala.collection.mutable.Map( + "source_reachability_check_count" -> 1L, + "source_reachability_accepted_count" -> 1L, + "path_constructor_check_count" -> 1L, + "path_constructor_accepted_count" -> 1L, + "parameter_position_check_count" -> 1L, + "parameter_position_accepted_count" -> 1L + ) + ) + activePair = Some(current) + try work + finally { + val bridgeCandidates = current.counters.getOrElse("bridge_argument_provenance_candidate_count", 0L) + val shortCircuits = current.counters.getOrElse("early_candidate_short_circuit_count", 0L) + val localSearches = current.counters.getOrElse("local_path_search_count", 0L) + if (current.producedPath || (bridgeCandidates > shortCircuits && localSearches > 0L)) { + if (profiles.contains(key)) throw new IllegalStateException(s"duplicate attributed pair: ${pairId(key)}") + profiles += key -> current.counters + } + activePair = previous + } + } + + def increment(name: String, amount: Long = 1L): Unit = { + increment(aggregates, name, amount) + activePair match { + case Some(current) if current.counters != null => increment(current.counters, name, amount) + case Some(current) => incrementPending(current, name, amount) + case None => unattributed += amount + } + } + + def incrementAggregate(name: String, amount: Long = 1L): Unit = increment(aggregates, name, amount) + + def markPathProduced(): Unit = + activePair match { + case Some(current) => current.producedPath = true + case None => throw new IllegalStateException("path produced without active attributed pair") + } + + def materializeActivePair(): Unit = + activePair match { + case Some(current) if current.counters == null => + current.counters = scala.collection.mutable.Map( + "path_constructor_accepted_count" -> current.pathAccepted, + "path_constructor_pruned_count" -> current.pathPruned, + "bridge_argument_provenance_candidate_count" -> current.bridgeCandidates, + "bridge_candidate_pc_pruned_count" -> current.bridgePcPruned, + "bridge_candidate_reachability_pruned_count" -> current.bridgeReachabilityPruned, + "early_candidate_short_circuit_count" -> current.earlyShortCircuits + ) + case Some(_) => + case None => unattributed += 1 + } + + def incrementFor(source: LuaSourceEndpoint, sink: LuaSinkEndpoint, name: String): Unit = + increment( + profiles.getOrElse(PairKey(source, sink), throw new IllegalStateException("missing attributed pair")), + name, + 1L + ) + + def result(reports: Vector[LuaReportClassification]): LuaPerformanceAttribution = { + profiles.foreach { case (key, counters) => + val reportCount = + reports.count(report => report.sourceRef == key.source.sourceRef && report.sinkRef == key.sink.sinkRef) + if (reportCount > 0) increment(counters, "report_count", reportCount.toLong) + } + val rows = profiles.toVector.map { case (key, values) => + LuaPairPerformanceProfile( + key.source.sourceRef, + key.sink.sinkRef, + scopedCallsite(key.source.callsiteId), + scopedCallsite(key.sink.callsiteId), + key.source.trigger, + key.sink.trigger, + PairCounterNames.map(name => name -> values.getOrElse(name, 0L)).toMap + ) + } + LuaPerformanceAttribution( + p1Candidates, + p1Rejected, + p1Accepted, + unattributed, + rows, + PairCounterNames.map(name => name -> aggregates.getOrElse(name, 0L)).toMap + ) + } + + private def increment(values: scala.collection.mutable.Map[String, Long], name: String, amount: Long): Unit = + values.update(name, values.getOrElse(name, 0L) + amount) + + private def incrementPending(current: ActivePair, name: String, amount: Long): Unit = + name match { + case "path_constructor_check_count" => + case "path_constructor_accepted_count" => current.pathAccepted += amount + case "path_constructor_pruned_count" => current.pathPruned += amount + case "bridge_argument_provenance_candidate_count" => current.bridgeCandidates += amount + case "bridge_candidate_pc_pruned_count" => current.bridgePcPruned += amount + case "bridge_candidate_reachability_pruned_count" => current.bridgeReachabilityPruned += amount + case "early_candidate_short_circuit_count" => current.earlyShortCircuits += amount + case _ => unattributed += amount + } + + private def scopedCallsite(ref: String): String = { + val split = parseQualifiedValueRef(ref) + s"${split.modulePath}::${split.localRef.split(":r", 2).head}" + } + + private def pairId(key: PairKey): String = + s"${key.source.sourceRef}|${key.source.callsiteId}|${key.source.trigger}->${key.sink.sinkRef}|${key.sink.callsiteId}|${key.sink.trigger}" + } + + def normalize(artifacts: Vector[(String, LuaBytecodeDecodeResult)]): LuaProgramSemantics = { + val attribution = new PerformanceAttributionCollector + val accepted = artifacts.collect { + case (path, result) if result.artifact.accepted => + ProgramArtifact(path, result.root) + } + val modules = accepted.flatMap(ModuleSummary.fromArtifact) + val moduleIndex = new ModuleIndex(modules) + val resolutions = modules.flatMap(module => module.requireCalls.map(resolveRequire(module, _, moduleIndex))) + val returnTables = modules.flatMap(_.exports.map { moduleExport => + LuaModuleReturnTable( + moduleExport.modulePath, + moduleExport.tableRef, + moduleExport.fieldName, + moduleExport.targetPrototypeId, + Provenance + ) + }) + val fieldTargets = + modules.flatMap(module => module.fieldCalls.flatMap(resolveFieldCall(module, _, resolutions, moduleIndex))) + val crossTargets = fieldTargets.map(target => + LuaCrossBoundaryCallTarget( + target.fromModulePath, + target.callsiteId, + target.targetModulePath, + target.targetPrototypeId, + "candidate", + Provenance + ) + ) + val localArgFlows = modules.flatMap(localInterproceduralArgFlows) + val localReturnFlows = modules.flatMap(localInterproceduralReturnFlows) + val crossInterprocedural = fieldTargets.flatMap(target => crossModuleFlows(target, moduleIndex)) + val sourceEndpoints = modules.flatMap(sourceEndpointsForModule).distinct + val sinkEndpoints = modules.flatMap(sinkEndpointsForModule(_, attribution)).distinct + val allArgFlows = (localArgFlows ++ crossInterprocedural.map(_._1)).distinct + val allReturnFlows = (localReturnFlows ++ crossInterprocedural.flatMap(_._2)).distinct + val pathSearch = new LocalPathSearch(modules, attribution) + val sourceSinkPruning = + new SourceSinkPruning(modules, fieldTargets, allArgFlows, allReturnFlows, sinkEndpoints, attribution) + val qualifiedSinksBySource = sourceEndpoints.map { sourceEndpoint => + sourceEndpoint -> sourceSinkPruning.qualifiedSinkEndpoints(sourceEndpoint, sinkEndpoints) + }.toMap + val taintPaths = + realFirmwareTaintPaths( + modules, + sourceEndpoints, + qualifiedSinksBySource, + allArgFlows, + allReturnFlows, + pathSearch, + attribution + ) + val boundaries = semanticBoundaries(modules, resolutions, fieldTargets) + val ruleMatches = ruleMatchesFor(sourceEndpoints, sinkEndpoints) + val sanitizerCalls = sanitizerCallsFor(modules) + val sanitizerClassifications = sanitizerClassificationsFor(taintPaths, sanitizerCalls) + val reportClassifications = reportClassificationsFor(taintPaths, sanitizerClassifications) + val vulnerabilityReports = vulnerabilityReportsFor(taintPaths, reportClassifications) + val e5Boundaries = Vector.empty[LuaE5Boundary] + + LuaProgramSemantics( + moduleResolutions = resolutions, + moduleReturnTables = returnTables, + moduleFieldCallTargets = fieldTargets, + interproceduralArgFlows = allArgFlows, + interproceduralReturnFlows = allReturnFlows, + crossBoundaryCallTargets = crossTargets.distinct, + taintPaths = taintPaths.distinct, + boundaries = boundaries.distinct, + ruleMatches = ruleMatches.distinct, + sourceEndpoints = sourceEndpoints.distinct, + sinkEndpoints = sinkEndpoints.distinct, + sanitizerCalls = sanitizerCalls.distinct, + sanitizerClassifications = sanitizerClassifications.distinct, + reportClassifications = reportClassifications.distinct, + vulnerabilityReports = vulnerabilityReports.distinct, + e5Boundaries = e5Boundaries.distinct, + pathSearchStats = pathSearch.stats( + sourceSinkPairCount = sourceEndpoints.size * sinkEndpoints.size, + qualifiedSourceSinkPairCount = qualifiedSinksBySource.values.map(_.size).sum + ), + performanceAttribution = attribution.result(reportClassifications) + ) + } + + private def resolveRequire( + module: ModuleSummary, + requireCall: RequireCall, + moduleIndex: ModuleIndex + ): LuaModuleResolution = + requireCall.requireString match { + case Some(name) => + moduleIndex.resolve(name) match { + case ModuleResolutionResult.Matched(target) => + LuaModuleResolution( + requireCall.callsiteId, + name, + "matched", + module.path, + Some(target.path), + None, + Provenance + ) + case ModuleResolutionResult.Unresolved => + LuaModuleResolution( + requireCall.callsiteId, + name, + "unresolved", + module.path, + None, + Some("unresolved-module"), + BoundaryProvenance + ) + } + case None => + LuaModuleResolution( + requireCall.callsiteId, + "dynamic", + "dynamic", + module.path, + None, + Some("dynamic-require"), + BoundaryProvenance + ) + } + + private def resolveFieldCall( + module: ModuleSummary, + call: FieldCall, + resolutions: Vector[LuaModuleResolution], + moduleIndex: ModuleIndex + ): Option[LuaModuleFieldCallTarget] = { + val targetModulePath = resolutions + .filter(resolution => resolution.fromModulePath == module.path && resolution.resolutionStatus == "matched") + .find(resolution => call.requireRef.exists(ref => requireResolutionReturns(module, resolution, ref))) + .flatMap(_.targetModulePath) + targetModulePath.flatMap { path => + moduleIndex + .module(path) + .flatMap(_.exports.find(_.fieldName == call.fieldName)) + .map(moduleExport => + LuaModuleFieldCallTarget( + module.path, + call.callsiteId, + call.fieldName, + path, + moduleExport.targetPrototypeId, + Provenance + ) + ) + } + } + + private def requireResolutionReturns( + module: ModuleSummary, + resolution: LuaModuleResolution, + requireRef: String + ): Boolean = + module.requireCalls.exists(call => + call.callsiteId == resolution.requireCallsiteId && + call.resultRef.contains(requireRef) + ) + + private def localInterproceduralArgFlows(module: ModuleSummary): Vector[LuaInterproceduralArgFlow] = + module.localCalls.flatMap { call => + module.prototype(call.targetPrototypeId).toVector.flatMap { callee => + for { + (fromArg, argumentIndex) <- call.argumentRefs.zipWithIndex + toParam <- callee.parameterRefs.lift(argumentIndex) + if !parameterFlowsToCallTarget(callee, argumentIndex) + } yield LuaInterproceduralArgFlow( + call.callsiteId, + qualify(module.path, fromArg), + argumentIndex, + module.path, + call.targetPrototypeId, + qualify(module.path, toParam), + Provenance + ) + } + } + + private def localInterproceduralReturnFlows(module: ModuleSummary): Vector[LuaInterproceduralReturnFlow] = + module.localCalls.flatMap { call => + module.prototype(call.targetPrototypeId).flatMap { callee => + for { + calleeReturn <- callee.returnRefs.headOption + callerResult <- call.resultRef + } yield LuaInterproceduralReturnFlow( + call.callsiteId, + module.path, + call.targetPrototypeId, + calleeReturn, + qualify(module.path, callerResult), + Provenance + ) + } + } + + private def crossModuleFlows( + target: LuaModuleFieldCallTarget, + moduleIndex: ModuleIndex + ): Vector[(LuaInterproceduralArgFlow, Option[LuaInterproceduralReturnFlow])] = { + val module = moduleIndex + .module(target.fromModulePath) + .getOrElse( + throw new IllegalStateException( + s"missing source module for resolved field target: module=${target.fromModulePath}" + ) + ) + val call = module.fieldCalls + .find(_.callsiteId == target.callsiteId) + .getOrElse( + throw new IllegalStateException( + s"missing field call for resolved field target: module=${target.fromModulePath} callsite=${target.callsiteId}" + ) + ) + val callee = moduleIndex + .module(target.targetModulePath) + .flatMap(_.prototype(target.targetPrototypeId)) + .getOrElse( + throw new IllegalStateException( + "missing callee prototype for resolved field target: " + + s"module=${target.targetModulePath} prototype=${target.targetPrototypeId}" + ) + ) + + val returnFlow = for { + calleeReturn <- callee.returnRefs.headOption + callerResult <- call.resultRef + } yield LuaInterproceduralReturnFlow( + target.callsiteId, + target.targetModulePath, + target.targetPrototypeId, + calleeReturn, + qualify(module.path, callerResult), + Provenance + ) + call.argumentRefs.zipWithIndex.flatMap { case (fromArg, argumentIndex) => + callee.parameterRefs + .lift(argumentIndex) + .toVector + .filter(_ => !parameterFlowsToCallTarget(callee, argumentIndex)) + .map { toParam => + LuaInterproceduralArgFlow( + target.callsiteId, + qualify(module.path, fromArg), + argumentIndex, + target.targetModulePath, + target.targetPrototypeId, + qualify(target.targetModulePath, toParam), + Provenance + ) -> returnFlow + } + } + } + + private def realFirmwareTaintPaths( + modules: Vector[ModuleSummary], + sourceEndpoints: Vector[LuaSourceEndpoint], + qualifiedSinksBySource: Map[LuaSourceEndpoint, Vector[LuaSinkEndpoint]], + interproceduralArgFlows: Vector[LuaInterproceduralArgFlow], + interproceduralReturnFlows: Vector[LuaInterproceduralReturnFlow], + pathSearch: LocalPathSearch, + attribution: PerformanceAttributionCollector + ): Vector[LuaTaintPath] = { + val crossModuleBridgeIndex = + new CrossModuleBridgeIndex( + interproceduralArgFlows, + interproceduralReturnFlows, + realFirmwareSanitizerProducedRefs(modules), + attribution + ) + sourceEndpoints.flatMap { source => + val qualifiedSinks = qualifiedSinksBySource + .get(source) + .getOrElse(throw new IllegalStateException(s"missing qualified sink set for source ${source.sourceRef}")) + qualifiedSinks.flatMap { sink => + val path = attribution.withPair(source, sink) { + val result = semanticBridgePath(source, sink, crossModuleBridgeIndex, pathSearch) + .orElse(samePrototypeForwardPath(pathSearch, source.sourceRef, sink.sinkRef)) + if (result.nonEmpty) attribution.markPathProduced() + result + } + path.map { pathSteps => + attribution.incrementFor(source, sink, "taint_path_count") + LuaTaintPath(source.sourceRef, sink.sinkRef, pathSteps, "true-positive", Provenance) + } + } + }.distinct + } + + private def samePrototypeForwardPath( + pathSearch: LocalPathSearch, + sourceRef: String, + sinkRef: String + ): Option[Vector[String]] = { + val source = parseQualifiedValueRef(sourceRef) + val sink = parseQualifiedValueRef(sinkRef) + if ( + source.modulePath == sink.modulePath && + source.prototypeId == sink.prototypeId && + source.pc <= sink.pc + ) + pathSearch.moduleLocalPathMode( + sourceRef, + sinkRef, + includeRepresentativeCallReturns = true, + includeRepresentativeValueEdges = true + ) + else None + } + + private def semanticBridgePath( + sourceEndpoint: LuaSourceEndpoint, + sinkEndpoint: LuaSinkEndpoint, + crossModuleBridgeIndex: CrossModuleBridgeIndex, + pathSearch: LocalPathSearch + ): Option[Vector[String]] = { + val provenanceBridge = crossModuleBridgeIndex.sourceScopedBridgePath( + sourceEndpoint, + sinkEndpoint, + pathSearch, + includeRepresentativeValueEdges = true + ) + if ( + provenanceBridge + .exists(path => !crossModuleBridgeIndex.pathCrossesSourceSanitizerProducedRef(path, sourceEndpoint.sourceRef)) + ) { + provenanceBridge + } else { + val needsArgumentReturnBridge = + crossModuleBridgeIndex.sourceRequiresRepresentativeReturnBridge(sourceEndpoint, sinkEndpoint, pathSearch) + val argumentReturnBridge = + if (provenanceBridge.nonEmpty || needsArgumentReturnBridge) { + crossModuleBridgeIndex.sourceScopedRepresentativeBridgePath(sourceEndpoint, sinkEndpoint, pathSearch) + } else { + None + } + selectUnsanitizedAlternative(provenanceBridge, argumentReturnBridge, sourceEndpoint, crossModuleBridgeIndex) + } + } + + private def selectUnsanitizedAlternative( + preferredPath: Option[Vector[String]], + alternativePath: Option[Vector[String]], + sourceEndpoint: LuaSourceEndpoint, + crossModuleBridgeIndex: CrossModuleBridgeIndex + ): Option[Vector[String]] = + (preferredPath, alternativePath) match { + case (Some(preferred), Some(alternative)) + if crossModuleBridgeIndex.pathCrossesSourceSanitizerProducedRef(preferred, sourceEndpoint.sourceRef) && + !crossModuleBridgeIndex.pathCrossesSourceSanitizerProducedRef(alternative, sourceEndpoint.sourceRef) => + Some(alternative) + case (Some(preferred), _) => Some(preferred) + case (None, alternative) => alternative + } + + private def representativeSinkSeeds(module: ModuleSummary, sinkEndpoint: LuaSinkEndpoint): Vector[String] = { + val sink = parseQualifiedValueRef(sinkEndpoint.sinkRef) + module + .prototype(sink.prototypeId) + .map { prototype => + val sanitizerReturn = + prototype.calls + .filter(call => call.pc < sink.pc && call.returnRefs.nonEmpty) + .filter(call => isRepresentativeSanitizerCall(call.resolvedName)) + .sortBy(_.pc) + .flatMap(_.returnRefs.headOption) + .map(qualify(module.path, _)) + + val sinkSlot = slotFromValueRef(sinkEndpoint.sinkRef) + val slotSeeds = prototype.instructions + .filter(instruction => instruction.pc < sink.pc && instruction.a == sinkSlot) + .sortBy(_.pc) + .map(instruction => qualify(module.path, s"${prototype.prototypeId}@pc${instruction.pc}:r${instruction.a}")) + + (sanitizerReturn ++ slotSeeds).distinct + } + .getOrElse(Vector.empty) + } + + private final class CrossModuleBridgeIndex( + interproceduralArgFlows: Vector[LuaInterproceduralArgFlow], + interproceduralReturnFlows: Vector[LuaInterproceduralReturnFlow], + sanitizerProducedRefs: Set[String], + attribution: PerformanceAttributionCollector + ) { + private final case class BridgeFlow( + sourcePrototype: String, + targetPrototype: String, + fromRef: String, + toRef: String, + callsiteId: String, + callsitePc: Int, + sortIndex: Int, + isArgumentFlow: Boolean + ) + + private val argumentBridgeFlows: Vector[BridgeFlow] = interproceduralArgFlows.map { flow => + val source = parseQualifiedValueRef(flow.fromArgumentRef) + BridgeFlow( + prototypeRef(source.modulePath, source.prototypeId), + prototypeRef(flow.targetModulePath, flow.targetPrototypeId), + flow.fromArgumentRef, + flow.toParameterRef, + flow.callsiteId, + requiredCallsitePc(flow.callsiteId), + flow.argumentIndex, + isArgumentFlow = true + ) + } + private val returnBridgeFlows: Vector[BridgeFlow] = interproceduralReturnFlows.map { flow => + val caller = parseQualifiedValueRef(flow.callerResultRef) + BridgeFlow( + prototypeRef(flow.targetModulePath, flow.targetPrototypeId), + prototypeRef(caller.modulePath, caller.prototypeId), + qualify(flow.targetModulePath, flow.calleeReturnRef), + flow.callerResultRef, + flow.callsiteId, + requiredCallsitePc(flow.callsiteId), + sortIndex = 0, + isArgumentFlow = false + ) + } + private val representativeBridgeFlows = (argumentBridgeFlows ++ returnBridgeFlows).distinct + private val directEdges: Set[(String, String)] = + argumentBridgeFlows.map(flow => flow.sourcePrototype -> flow.targetPrototype).toSet + private val representativeDirectEdges: Set[(String, String)] = + representativeBridgeFlows.map(flow => flow.sourcePrototype -> flow.targetPrototype).toSet + private val outgoingPrototypeEdges: Map[String, Set[String]] = + directEdges.groupMap(_._1)(_._2).view.mapValues(_.toSet).toMap + private val representativeIncomingPrototypeEdges: Map[String, Set[String]] = + representativeDirectEdges.groupMap(_._2)(_._1).view.mapValues(_.toSet).toMap + private val reachabilityCache = scala.collection.mutable.Map.empty[(String, String), Boolean] + private val representativeDistanceCache = scala.collection.mutable.Map.empty[String, Map[String, Int]] + private val representativeValueReachabilityCache = scala.collection.mutable.Map.empty[String, Set[String]] + private val localBridgePathCache = + scala.collection.mutable.Map.empty[(String, String, Boolean, Boolean), Option[Vector[String]]] + + private val argumentFlowsBySourcePrototype: Map[String, Vector[BridgeFlow]] = + argumentBridgeFlows.groupBy(_.sourcePrototype) + private val representativeFlowsBySourcePrototype: Map[String, Vector[BridgeFlow]] = + representativeBridgeFlows.groupBy(_.sourcePrototype) + + def sourceScopedBridgePath( + sourceEndpoint: LuaSourceEndpoint, + sinkEndpoint: LuaSinkEndpoint, + pathSearch: LocalPathSearch, + includeRepresentativeValueEdges: Boolean + ): Option[Vector[String]] = { + val source = parseQualifiedValueRef(sourceEndpoint.sourceRef) + val sink = parseQualifiedValueRef(sinkEndpoint.sinkRef) + val sinkPrototype = prototypeRef(sink.modulePath, sink.prototypeId) + val pending = scala.collection.mutable.Queue((sourceEndpoint.sourceRef, Vector(sourceEndpoint.sourceRef), 0)) + val seen = scala.collection.mutable.Set(sourceEndpoint.sourceRef) + val maxDepth = 4 + var firstSanitizedPath: Option[Vector[String]] = None + var unsanitizedPath: Option[Vector[String]] = None + + while (pending.nonEmpty && unsanitizedPath.isEmpty) { + val (currentRef, pathPrefix, depth) = pending.dequeue() + val currentPrototype = prototypeRefFromAnyQualifiedRef(currentRef) + val currentPc = pcFromAnyValueRef(currentRef) + + val currentIsEntryParameter = isPrototypeEntryParameterRef(currentRef) + val candidateFlows = argumentFlowsBySourcePrototype + .getOrElse(currentPrototype, Vector.empty) + .filter { flow => + attribution.increment("bridge_argument_provenance_candidate_count") + val flowPc = flow.callsitePc + val accepted = currentPc match { + case Some(pc) if flowPc >= pc => true + case Some(_) => false + case None if currentIsEntryParameter => true + case None => + throw new IllegalStateException( + s"missing pc provenance for cross-module bridge ref: current_ref=$currentRef" + ) + } + if (!accepted) { + attribution.increment("bridge_candidate_pc_pruned_count") + attribution.increment("early_candidate_short_circuit_count") + } + accepted + } + .filter { flow => + val accepted = + flow.targetPrototype == sinkPrototype || reachesSinkPrototype(flow.targetPrototype, sinkPrototype) + if (!accepted) { + attribution.increment("bridge_candidate_reachability_pruned_count") + attribution.increment("early_candidate_short_circuit_count") + } + accepted + } + .sortBy(flow => (flow.callsitePc, flow.targetPrototype, flow.sortIndex)) + + val reachableArgumentPaths = { + val candidateRefs = candidateFlows.map(_.fromRef).toSet + if (includeRepresentativeValueEdges) + pathSearch.moduleLocalPathsToAnyWithRepresentativeValues(currentRef, candidateRefs) + else + pathSearch.moduleLocalPathsToAny(currentRef, candidateRefs, includeRepresentativeCallReturns = true) + } + + val candidateIterator = candidateFlows.filter(flow => reachableArgumentPaths.contains(flow.fromRef)).iterator + while (candidateIterator.hasNext && unsanitizedPath.isEmpty) { + val flow = candidateIterator.next() + attribution.materializeActivePair() + val targetParamRef = flow.toRef + val targetPrototype = flow.targetPrototype + val currentToArgumentPath = preferredLocalPathMode( + pathSearch, + currentRef, + flow.fromRef, + includeRepresentativeCallReturns = true, + includeRepresentativeValueEdges = includeRepresentativeValueEdges + ) + currentToArgumentPath + .foreach { currentToArgument => + val bridgePrefix = pathPrefix ++ currentToArgument.drop(1) :+ targetParamRef + if (targetPrototype == sinkPrototype) { + preferredLocalPathMode( + pathSearch, + targetParamRef, + sinkEndpoint.sinkRef, + includeRepresentativeCallReturns = true, + includeRepresentativeValueEdges = includeRepresentativeValueEdges + ) + .map(parameterToSink => bridgePrefix ++ parameterToSink.drop(1)) + .foreach { completePath => + if (crossesSourceSanitizerProducedRef(completePath, sourceEndpoint.sourceRef)) { + if (firstSanitizedPath.isEmpty) firstSanitizedPath = Some(completePath) + } else { + unsanitizedPath = Some(completePath) + } + } + } else if (depth < maxDepth && !seen(targetParamRef)) { + seen += targetParamRef + pending.enqueue((targetParamRef, bridgePrefix, depth + 1)) + } + } + } + + } + + unsanitizedPath.orElse(firstSanitizedPath) + } + + def sourceScopedRepresentativeBridgePath( + sourceEndpoint: LuaSourceEndpoint, + sinkEndpoint: LuaSinkEndpoint, + pathSearch: LocalPathSearch + ): Option[Vector[String]] = { + val source = parseQualifiedValueRef(sourceEndpoint.sourceRef) + val sink = parseQualifiedValueRef(sinkEndpoint.sinkRef) + val sourcePrototype = prototypeRef(source.modulePath, source.prototypeId) + val sinkPrototype = prototypeRef(sink.modulePath, sink.prototypeId) + val distanceToSink = representativeDistancesToSink(sinkPrototype) + val pending = + scala.collection.mutable.Queue((sourceEndpoint.sourceRef, Vector(sourceEndpoint.sourceRef), sourcePrototype, 0)) + val seen = scala.collection.mutable.Set(sourceEndpoint.sourceRef) + val maxDepth = 4 + var found: Option[Vector[String]] = None + + while (pending.nonEmpty && found.isEmpty) { + val (currentRef, pathPrefix, currentPrototype, depth) = pending.dequeue() + val currentPc = pcFromAnyValueRef(currentRef) + val currentIsEntryParameter = isPrototypeEntryParameterRef(currentRef) + val currentDistance = distanceToSink.getOrElse(currentPrototype, Int.MaxValue) + val candidateFlows = + (if (depth == 0) argumentFlowsBySourcePrototype else representativeFlowsBySourcePrototype) + .getOrElse(currentPrototype, Vector.empty) + .filter { flow => + attribution.increment("bridge_argument_provenance_candidate_count") + val pcAccepted = + if (depth == 0) flow.callsitePc > source.pc + else if (!flow.isArgumentFlow) true + else { + val flowPc = flow.callsitePc + currentPc match { + case Some(pc) if flowPc >= pc => true + case Some(_) => false + case None if currentIsEntryParameter => true + case None => + throw new IllegalStateException( + s"missing pc provenance for cross-module bridge ref: current_ref=$currentRef" + ) + } + } + if (!pcAccepted) { + attribution.increment("bridge_candidate_pc_pruned_count") + attribution.increment("early_candidate_short_circuit_count") + } + pcAccepted + } + .filter { flow => + val reachable = + if (depth == 0 && sourcePrototype == sinkPrototype) + flow.targetPrototype != sinkPrototype && distanceToSink.contains(flow.targetPrototype) + else distanceToSink.get(flow.targetPrototype).contains(currentDistance - 1) + if (!reachable) { + attribution.increment("bridge_candidate_reachability_pruned_count") + attribution.increment("early_candidate_short_circuit_count") + } + reachable + } + .sortBy(flow => (flow.callsitePc, flow.targetPrototype, flow.sortIndex)) + + val reachableBridgePrefixes = + reachableRepresentativeBridgePrefixes( + pathSearch, + currentRef, + candidateFlows, + includeArgumentRepresentativeValues = depth > 0 + ) + + val candidateIterator = candidateFlows.filter(flow => reachableBridgePrefixes.contains(flow.fromRef)).iterator + while (candidateIterator.hasNext && found.isEmpty) { + val flow = candidateIterator.next() + val bridgePrefix = pathPrefix ++ reachableBridgePrefixes(flow.fromRef).drop(1) :+ flow.toRef + if (flow.targetPrototype == sinkPrototype) { + found = preferredLocalPathForBridgeFlow(pathSearch, flow, sinkEndpoint.sinkRef) + .map(targetToSink => bridgePrefix ++ targetToSink.drop(1)) + } else if (depth < maxDepth && !seen(flow.toRef)) { + seen += flow.toRef + pending.enqueue((flow.toRef, bridgePrefix, flow.targetPrototype, depth + 1)) + } + } + } + + found + } + + private def reachableRepresentativeBridgePrefixes( + pathSearch: LocalPathSearch, + currentRef: String, + candidateFlows: Vector[BridgeFlow], + includeArgumentRepresentativeValues: Boolean + ): Map[String, Vector[String]] = { + val argumentFlowRefs = candidateFlows.filter(_.isArgumentFlow).map(_.fromRef).toSet + val argumentFlowPaths = + if (includeArgumentRepresentativeValues) + pathSearch.moduleLocalPathsToAnyWithRepresentativeValues(currentRef, argumentFlowRefs) + else + pathSearch.moduleLocalPathsToAny(currentRef, argumentFlowRefs, includeRepresentativeCallReturns = true) + val returnFlowPaths = pathSearch.moduleLocalPathsToAny( + currentRef, + candidateFlows.filterNot(_.isArgumentFlow).map(_.fromRef).toSet, + includeRepresentativeCallReturns = true + ) + argumentFlowPaths ++ returnFlowPaths + } + + private def representativeDistancesToSink(sinkPrototype: String): Map[String, Int] = + representativeDistanceCache.getOrElseUpdate( + sinkPrototype, { + val pending = scala.collection.mutable.Queue(sinkPrototype) + val distance = scala.collection.mutable.Map(sinkPrototype -> 0) + while (pending.nonEmpty) { + val current = pending.dequeue() + val currentDistance = distance(current) + representativeIncomingPrototypeEdges.getOrElse(current, Set.empty).foreach { previous => + if (!distance.contains(previous)) { + distance(previous) = currentDistance + 1 + pending.enqueue(previous) + } + } + } + distance.toMap + } + ) + + def sourceRequiresRepresentativeReturnBridge( + sourceEndpoint: LuaSourceEndpoint, + sinkEndpoint: LuaSinkEndpoint, + pathSearch: LocalPathSearch + ): Boolean = { + val sinkPrototype = prototypeRef(sinkEndpoint.sinkRef) + representativeValueReachableReturnPrototypes(sourceEndpoint, pathSearch)(sinkPrototype) + } + + private def representativeValueReachableReturnPrototypes( + sourceEndpoint: LuaSourceEndpoint, + pathSearch: LocalPathSearch + ): Set[String] = + representativeValueReachabilityCache.getOrElseUpdate( + sourceEndpoint.sourceRef, { + val source = parseQualifiedValueRef(sourceEndpoint.sourceRef) + val sourcePrototype = prototypeRef(source.modulePath, source.prototypeId) + val pending = scala.collection.mutable.Queue((sourceEndpoint.sourceRef, sourcePrototype, 0, false)) + val seen = scala.collection.mutable.Set((sourceEndpoint.sourceRef, sourcePrototype, 0, false)) + val reachable = scala.collection.mutable.Set.empty[String] + val maxDepth = 4 + + while (pending.nonEmpty) { + val (currentRef, currentPrototype, depth, usedReturnBridge) = pending.dequeue() + val currentPc = pcFromAnyValueRef(currentRef) + val currentIsEntryParameter = isPrototypeEntryParameterRef(currentRef) + val candidateFlows = + (if (depth == 0) argumentFlowsBySourcePrototype else representativeFlowsBySourcePrototype) + .getOrElse(currentPrototype, Vector.empty) + .filter { flow => + attribution.increment("bridge_argument_provenance_candidate_count") + val pcAccepted = + if (depth == 0) flow.callsitePc > source.pc + else if (!flow.isArgumentFlow) true + else { + currentPc match { + case Some(pc) if flow.callsitePc >= pc => true + case Some(_) => false + case None if currentIsEntryParameter => true + case None => + throw new IllegalStateException( + s"missing pc provenance for cross-module bridge ref: current_ref=$currentRef" + ) + } + } + if (!pcAccepted) { + attribution.increment("bridge_candidate_pc_pruned_count") + attribution.increment("early_candidate_short_circuit_count") + } + pcAccepted + } + + val reachableBridgePrefixes = + reachableRepresentativeBridgePrefixes( + pathSearch, + currentRef, + candidateFlows, + includeArgumentRepresentativeValues = depth > 0 + ) + candidateFlows + .filter(flow => reachableBridgePrefixes.contains(flow.fromRef)) + .sortBy(flow => (flow.callsitePc, flow.targetPrototype, flow.sortIndex)) + .foreach { flow => + val nextUsedReturnBridge = usedReturnBridge || !flow.isArgumentFlow + if (nextUsedReturnBridge) reachable += flow.targetPrototype + if (depth < maxDepth) { + val state = (flow.toRef, flow.targetPrototype, depth + 1, nextUsedReturnBridge) + if (!seen(state)) { + seen += state + pending.enqueue(state) + } + } + } + } + reachable.toSet + } + ) + + private def cachedLocalPath( + pathSearch: LocalPathSearch, + sourceRef: String, + sinkRef: String, + includeRepresentativeCallReturns: Boolean, + includeRepresentativeValueEdges: Boolean + ): Option[Vector[String]] = { + attribution.increment("bridge_local_path_attempt_count") + val key = (sourceRef, sinkRef, includeRepresentativeCallReturns, includeRepresentativeValueEdges) + attribution.increment( + if (localBridgePathCache.contains(key)) "bridge_path_cache_hit_count" else "bridge_path_cache_miss_count" + ) + val result = localBridgePathCache.getOrElseUpdate( + key, + pathSearch.moduleLocalPathMode( + sourceRef, + sinkRef, + includeRepresentativeCallReturns, + includeRepresentativeValueEdges + ) + ) + if (result.nonEmpty) attribution.increment("bridge_local_path_success_count") + result + } + + private def preferredLocalPath( + pathSearch: LocalPathSearch, + sourceRef: String, + sinkRef: String, + includeRepresentativeCallReturns: Boolean + ): Option[Vector[String]] = + preferredLocalPathMode( + pathSearch, + sourceRef, + sinkRef, + includeRepresentativeCallReturns, + includeRepresentativeValueEdges = false + ) + + private def preferredLocalPathForBridgeFlow( + pathSearch: LocalPathSearch, + flow: BridgeFlow, + sinkRef: String + ): Option[Vector[String]] = + if (flow.isArgumentFlow) + preferredLocalPath(pathSearch, flow.toRef, sinkRef, includeRepresentativeCallReturns = true) + else + preferredLocalPathMode( + pathSearch, + flow.toRef, + sinkRef, + includeRepresentativeCallReturns = false, + includeRepresentativeValueEdges = true + ) + + private def preferredLocalPathMode( + pathSearch: LocalPathSearch, + sourceRef: String, + sinkRef: String, + includeRepresentativeCallReturns: Boolean, + includeRepresentativeValueEdges: Boolean + ): Option[Vector[String]] = + cachedLocalPath(pathSearch, sourceRef, sinkRef, includeRepresentativeCallReturns, includeRepresentativeValueEdges) + .map { localPath => + if (crossesSanitizerProducedRef(localPath)) { + pathSearch.moduleLocalPathAvoidingMode( + sourceRef, + sinkRef, + sanitizerProducedRefs, + includeRepresentativeCallReturns, + includeRepresentativeValueEdges + ) match { + case Some(unsanitizedPath) => unsanitizedPath + case None => localPath + } + } else { + localPath + } + } + + private def crossesSanitizerProducedRef(path: Vector[String]): Boolean = + path.exists(sanitizerProducedRefs) + + private def crossesSourceSanitizerProducedRef(path: Vector[String], sourceRef: String): Boolean = { + val sourceModulePath = modulePathFromQualifiedRef(sourceRef) + path.exists(ref => sanitizerProducedRefs(ref) && modulePathFromQualifiedRef(ref) == sourceModulePath) + } + + def pathCrossesSourceSanitizerProducedRef(path: Vector[String], sourceRef: String): Boolean = + crossesSourceSanitizerProducedRef(path, sourceRef) + + private def pcFromAnyValueRef(valueRef: String): Option[Int] = + localRefFromQualifiedRef(valueRef) + .split("@pc", 2) + .lift(1) + .flatMap(_.split(":r", 2).headOption) + .flatMap(_.toIntOption) + + private def requiredCallsitePc(callsiteId: String): Int = + callsitePc(callsiteId).getOrElse(throw new IllegalStateException(s"missing callsite pc: callsite_id=$callsiteId")) + + private def isPrototypeEntryParameterRef(valueRef: String): Boolean = + !localRefFromQualifiedRef(valueRef).contains("@pc") + + private def reachesSinkPrototype(start: String, sinkPrototype: String): Boolean = + reachabilityCache.getOrElseUpdate( + start -> sinkPrototype, { + val pending = scala.collection.mutable.Queue(start) + val seen = scala.collection.mutable.Set.empty[String] + var found = false + while (pending.nonEmpty && !found) { + val current = pending.dequeue() + if (!seen(current)) { + seen += current + outgoingPrototypeEdges.getOrElse(current, Set.empty).foreach { target => + if (target == sinkPrototype) found = true + else if (!seen(target)) pending.enqueue(target) + } + } + } + found + } + ) + + } + + private final class LocalPathSearch(modules: Vector[ModuleSummary], attribution: PerformanceAttributionCollector) { + private final class BlockedRefsIdentityKey(val refs: Set[String]) { + override def equals(other: Any): Boolean = + other match { + case that: BlockedRefsIdentityKey => refs.asInstanceOf[AnyRef] eq that.refs.asInstanceOf[AnyRef] + case _ => false + } + override def hashCode(): Int = System.identityHashCode(refs.asInstanceOf[AnyRef]) + } + + private val modulesByPath = modules.map(module => module.path -> module).toMap + private val graphCache = + scala.collection.mutable.Map.empty[(String, Boolean, Boolean), Map[String, Vector[String]]] + private val pathCache = + scala.collection.mutable.Map.empty[(String, String, Boolean, Boolean), Option[Vector[String]]] + private val avoidingGraphCache = + scala.collection.mutable.Map + .empty[(String, Boolean, Boolean, BlockedRefsIdentityKey), Map[String, Vector[String]]] + private val avoidingPathCache = + scala.collection.mutable.Map + .empty[(String, String, Boolean, Boolean, BlockedRefsIdentityKey), Option[Vector[String]]] + private val representativeSinkPathCache = scala.collection.mutable.Map.empty[String, Vector[Vector[String]]] + private val graphModules = scala.collection.mutable.Set.empty[String] + private var graphBuildCount = 0 + private var localPathSearchCount = 0 + private var batchedDistinctPathCount = 0 + private var uncachedDistinctPathCount = 0 + + def moduleLocalPath( + sourceRef: String, + sinkRef: String, + includeRepresentativeCallReturns: Boolean = false + ): Option[Vector[String]] = + moduleLocalPathMode(sourceRef, sinkRef, includeRepresentativeCallReturns, includeRepresentativeValueEdges = false) + + def moduleLocalPathMode( + sourceRef: String, + sinkRef: String, + includeRepresentativeCallReturns: Boolean, + includeRepresentativeValueEdges: Boolean + ): Option[Vector[String]] = { + attribution.materializeActivePair() + attribution.increment("local_path_search_count") + localPathSearchCount += 1 + val sourceModulePath = modulePathFromQualifiedRef(sourceRef) + val sinkModulePath = modulePathFromQualifiedRef(sinkRef) + if (sourceModulePath != sinkModulePath) { + attribution.increment("local_path_cache_miss_count") + attribution.increment("distinct_local_path_query_count") + uncachedDistinctPathCount += 1 + None + } else { + val key = (sourceRef, sinkRef, includeRepresentativeCallReturns, includeRepresentativeValueEdges) + val hit = pathCache.contains(key) + attribution.increment(if (hit) "local_path_cache_hit_count" else "local_path_cache_miss_count") + if (!hit) attribution.increment("distinct_local_path_query_count") + pathCache.getOrElseUpdate( + key, + modulesByPath.get(sourceModulePath).flatMap { module => + val bySource = graphFor(module, includeRepresentativeCallReturns, includeRepresentativeValueEdges) + breadthFirstPath(bySource, sourceRef, sinkRef) + } + ) + } + } + + def moduleLocalPathAvoiding( + sourceRef: String, + sinkRef: String, + blockedRefs: Set[String], + includeRepresentativeCallReturns: Boolean = false + ): Option[Vector[String]] = + moduleLocalPathAvoidingMode( + sourceRef, + sinkRef, + blockedRefs, + includeRepresentativeCallReturns, + includeRepresentativeValueEdges = false + ) + + def moduleLocalPathAvoidingMode( + sourceRef: String, + sinkRef: String, + blockedRefs: Set[String], + includeRepresentativeCallReturns: Boolean, + includeRepresentativeValueEdges: Boolean + ): Option[Vector[String]] = { + attribution.materializeActivePair() + attribution.increment("local_path_search_count") + localPathSearchCount += 1 + val sourceModulePath = modulePathFromQualifiedRef(sourceRef) + val sinkModulePath = modulePathFromQualifiedRef(sinkRef) + if (sourceModulePath != sinkModulePath || blockedRefs(sourceRef) || blockedRefs(sinkRef)) { + attribution.increment("local_path_cache_miss_count") + attribution.increment("distinct_local_path_query_count") + uncachedDistinctPathCount += 1 + None + } else { + val blockedKey = BlockedRefsIdentityKey(blockedRefs) + val key = (sourceRef, sinkRef, includeRepresentativeCallReturns, includeRepresentativeValueEdges, blockedKey) + val hit = avoidingPathCache.contains(key) + attribution.increment(if (hit) "local_path_cache_hit_count" else "local_path_cache_miss_count") + if (!hit) attribution.increment("distinct_local_path_query_count") + avoidingPathCache.getOrElseUpdate( + key, + modulesByPath.get(sourceModulePath).flatMap { module => + val bySource = avoidingGraphFor( + module, + includeRepresentativeCallReturns, + includeRepresentativeValueEdges, + blockedRefs, + blockedKey + ) + breadthFirstPath(bySource, sourceRef, sinkRef) + } + ) + } + } + + def moduleLocalPathsToAny( + sourceRef: String, + sinkRefs: Set[String], + includeRepresentativeCallReturns: Boolean = false + ): Map[String, Vector[String]] = + moduleLocalPathsToAnyMode( + sourceRef, + sinkRefs, + includeRepresentativeCallReturns, + includeRepresentativeValueEdges = false + ) + + def moduleLocalPathsToAnyWithRepresentativeValues( + sourceRef: String, + sinkRefs: Set[String] + ): Map[String, Vector[String]] = + moduleLocalPathsToAnyMode( + sourceRef, + sinkRefs, + includeRepresentativeCallReturns = true, + includeRepresentativeValueEdges = true + ) + + private def moduleLocalPathsToAnyMode( + sourceRef: String, + sinkRefs: Set[String], + includeRepresentativeCallReturns: Boolean, + includeRepresentativeValueEdges: Boolean + ): Map[String, Vector[String]] = + if (sinkRefs.isEmpty) Map.empty + else { + attribution.materializeActivePair() + attribution.increment("local_path_search_count") + attribution.increment("local_path_cache_miss_count") + attribution.increment("distinct_local_path_query_count") + localPathSearchCount += 1 + batchedDistinctPathCount += 1 + val sourceModulePath = modulePathFromQualifiedRef(sourceRef) + val invalidSinkModules = sinkRefs + .map(modulePathFromQualifiedRef) + .filter(_ != sourceModulePath) + if (invalidSinkModules.nonEmpty) { + throw new IllegalStateException( + s"cross-module refs in local path batch: source_ref=$sourceRef sink_modules=${invalidSinkModules.toVector.sorted + .mkString("[", ",", "]")}" + ) + } + modulesByPath + .get(sourceModulePath) + .map { module => + val bySource = graphFor(module, includeRepresentativeCallReturns, includeRepresentativeValueEdges) + breadthFirstPathsToAny(bySource, sourceRef, sinkRefs) + } + .getOrElse(Map.empty) + } + + def representativeSinkPaths( + module: ModuleSummary, + sinkEndpoint: LuaSinkEndpoint, + seeds: => Vector[String] + ): Vector[Vector[String]] = + representativeSinkPathCache.getOrElseUpdate( + sinkEndpoint.sinkRef, + seeds.flatMap(seed => moduleLocalPath(seed, sinkEndpoint.sinkRef)) + ) + + def stats(sourceSinkPairCount: Int, qualifiedSourceSinkPairCount: Int): LuaPathSearchStats = { + val distinctLocalPathQueryCount = + pathCache.size + avoidingPathCache.size + batchedDistinctPathCount + uncachedDistinctPathCount + LuaPathSearchStats( + localPathGraphModuleCount = graphModules.size, + localPathGraphBuildCount = graphBuildCount, + localPathSearchCount = localPathSearchCount, + distinctLocalPathQueryCount = distinctLocalPathQueryCount, + sourceSinkPairCount = sourceSinkPairCount, + qualifiedSourceSinkPairCount = qualifiedSourceSinkPairCount, + prototypePrunedSourceSinkPairCount = sourceSinkPairCount - qualifiedSourceSinkPairCount + ) + } + + private def graphFor( + module: ModuleSummary, + includeRepresentativeCallReturns: Boolean, + includeRepresentativeValueEdges: Boolean + ): Map[String, Vector[String]] = { + val key = (module.path, includeRepresentativeCallReturns, includeRepresentativeValueEdges) + val hit = graphCache.contains(key) + attribution.increment(if (hit) "local_path_graph_cache_hit_count" else "local_path_graph_cache_miss_count") + if (!hit) attribution.increment("local_path_graph_build_count") + graphCache.getOrElseUpdate( + key, { + graphBuildCount += 1 + graphModules += module.path + val localEdges = module.localFlows + .map(flow => qualify(module.path, flow.sourceRef) -> qualify(module.path, flow.sinkRef)) + val globalEdges = + module.globalFlows.map(flow => qualify(module.path, flow.writeRef) -> qualify(module.path, flow.readRef)) + val representativeCallReturn = + if (includeRepresentativeCallReturns) + representativeCallReturnEdges(module) + else Vector.empty + val representativeValueEdges = + if (includeRepresentativeValueEdges) + representativeTableValueEdges(module) ++ + representativeParameterTableReadEdges(module) ++ + representativeIteratorValueEdges(module) ++ + representativeExpressionResultEdges(module) + else Vector.empty + (localEdges ++ globalEdges ++ representativeCallReturn ++ representativeValueEdges).groupMap(_._1)(_._2) + } + ) + } + + private def avoidingGraphFor( + module: ModuleSummary, + includeRepresentativeCallReturns: Boolean, + includeRepresentativeValueEdges: Boolean, + blockedRefs: Set[String], + blockedKey: BlockedRefsIdentityKey + ): Map[String, Vector[String]] = + avoidingGraphCache.getOrElseUpdate( + (module.path, includeRepresentativeCallReturns, includeRepresentativeValueEdges, blockedKey), { + graphFor(module, includeRepresentativeCallReturns, includeRepresentativeValueEdges).flatMap { + case (source, targets) => + if (blockedRefs(source)) None + else { + val retainedTargets = targets.filterNot(blockedRefs) + Option.when(retainedTargets.nonEmpty)(source -> retainedTargets) + } + } + } + ) + + private def breadthFirstPath( + bySource: Map[String, Vector[String]], + sourceRef: String, + sinkRef: String + ): Option[Vector[String]] = { + val queue = scala.collection.mutable.Queue(Vector(sourceRef)) + val seen = scala.collection.mutable.Set(sourceRef) + var found: Option[Vector[String]] = None + while (queue.nonEmpty && found.isEmpty) { + val path = queue.dequeue() + attribution.increment("targeted_search_node_visit_count") + if (path.last == sinkRef) found = Some(path) + else { + bySource.getOrElse(path.last, Vector.empty).foreach { next => + attribution.increment("targeted_search_edge_visit_count") + if (!seen(next)) { + seen += next + queue.enqueue(path :+ next) + } + } + } + } + found + } + + private def breadthFirstPathsToAny( + bySource: Map[String, Vector[String]], + sourceRef: String, + sinkRefs: Set[String] + ): Map[String, Vector[String]] = { + val remaining = scala.collection.mutable.Set.from(sinkRefs) + val found = scala.collection.mutable.Map.empty[String, Vector[String]] + val queue = scala.collection.mutable.Queue(Vector(sourceRef)) + val seen = scala.collection.mutable.Set(sourceRef) + while (queue.nonEmpty && remaining.nonEmpty) { + val path = queue.dequeue() + attribution.increment("targeted_search_node_visit_count") + if (remaining(path.last)) { + found += path.last -> path + remaining -= path.last + } + if (remaining.nonEmpty) { + bySource.getOrElse(path.last, Vector.empty).foreach { next => + attribution.increment("targeted_search_edge_visit_count") + if (!seen(next)) { + seen += next + queue.enqueue(path :+ next) + } + } + } + } + found.toMap + } + } + + private final class SourceSinkPruning( + modules: Vector[ModuleSummary], + fieldTargets: Vector[LuaModuleFieldCallTarget], + interproceduralArgFlows: Vector[LuaInterproceduralArgFlow], + interproceduralReturnFlows: Vector[LuaInterproceduralReturnFlow], + sinkEndpoints: Vector[LuaSinkEndpoint], + attribution: PerformanceAttributionCollector + ) { + private val callGraphAdjacency = buildCallGraphAdjacency() + private val sinkPrototypeByRef = sinkEndpoints.map(row => row.sinkRef -> prototypeRef(row.sinkRef)).toMap + private val directReachableSinkRefsBySource = buildDirectReachableSinkRefsBySource() + private val directInterproceduralPairs = interproceduralArgFlows.map { flow => + val source = parseQualifiedValueRef(flow.fromArgumentRef) + prototypeRef(source.modulePath, source.prototypeId) -> prototypeRef(flow.targetModulePath, flow.targetPrototypeId) + }.toSet + private val fedArgumentIndexesBySourceAndTarget = interproceduralArgFlows + .groupMap { flow => + val source = parseQualifiedValueRef(flow.fromArgumentRef) + prototypeRef(source.modulePath, source.prototypeId) -> prototypeRef( + flow.targetModulePath, + flow.targetPrototypeId + ) + }(_.argumentIndex) + .view + .mapValues(_.toSet) + .toMap + private val interproceduralFlowsBySourcePrototype = interproceduralArgFlows.groupBy { flow => + val source = parseQualifiedValueRef(flow.fromArgumentRef) + prototypeRef(source.modulePath, source.prototypeId) + } + private val reachablePrototypesByStartPrototype = scala.collection.mutable.Map.empty[String, Set[String]] + private val sourceScopedBridgePrototypeCache = scala.collection.mutable.Map.empty[String, Set[String]] + + def qualifiedSinkEndpoints( + sourceEndpoint: LuaSourceEndpoint, + allSinkEndpoints: Vector[LuaSinkEndpoint] + ): Vector[LuaSinkEndpoint] = { + val sourcePrototypeRef = prototypeRef(sourceEndpoint.sourceRef) + val reachablePrototypes = reachableFrom(callGraphAdjacency, sourcePrototypeRef) + val sourceSpecificSinks = directReachableSinkRefsBySource.getOrElse(sourcePrototypeRef, Set.empty) + val missingProvenanceRefs = Vector.newBuilder[String] + val qualified = allSinkEndpoints.filter { sinkEndpoint => + val sinkPrototypeRef = sinkPrototypeByRef + .get(sinkEndpoint.sinkRef) + .getOrElse( + throw new IllegalStateException( + s"missing sink prototype mapping for path-search prefilter: source_ref=${sourceEndpoint.sourceRef} sink_ref=${sinkEndpoint.sinkRef}" + ) + ) + attribution.incrementAggregate("source_reachability_check_count") + val reachesAndHasSourceSpecificProvenance = + if (!reachablePrototypes(sinkPrototypeRef)) { + attribution.incrementAggregate("prototype_unreachable_pair_count") + false + } else if (sinkPrototypeRef == sourcePrototypeRef) { + attribution.incrementAggregate("source_reachability_accepted_count") + true + } else if (sourceSpecificSinks.isEmpty) { + missingProvenanceRefs += sinkEndpoint.sinkRef + attribution.incrementAggregate("source_specific_provenance_pruned_pair_count") + false + } else { + val accepted = sourceSpecificSinks(sinkEndpoint.sinkRef) + attribution.incrementAggregate( + if (accepted) "source_reachability_accepted_count" + else "source_specific_provenance_pruned_pair_count" + ) + accepted + } + val accepted = if (!reachesAndHasSourceSpecificProvenance) { + false + } else { + attribution.incrementAggregate("path_constructor_check_count") + val constructorAccepted = pathConstructorCanAttempt(sourceEndpoint, sinkEndpoint) + attribution.incrementAggregate( + if (constructorAccepted) "path_constructor_accepted_count" else "path_constructor_pruned_count" + ) + if (!constructorAccepted) false + else { + attribution.incrementAggregate("parameter_position_check_count") + val parameterAccepted = parameterPositionAllows(sourcePrototypeRef, sinkPrototypeRef, sinkEndpoint) + attribution.incrementAggregate( + if (parameterAccepted) "parameter_position_accepted_count" else "parameter_position_pruned_count" + ) + parameterAccepted + } + } + accepted + } + val missing = missingProvenanceRefs.result() + if (missing.nonEmpty) { + throw new IllegalStateException( + "missing source-specific sink reachability provenance for path-search prefilter: " + + s"source_ref=${sourceEndpoint.sourceRef} source_prototype_ref=$sourcePrototypeRef missing_sink_refs=${missing.sorted + .mkString("[", ",", "]")}" + ) + } + qualified + } + + private def parameterPositionAllows( + sourcePrototypeRef: String, + sinkPrototypeRef: String, + sinkEndpoint: LuaSinkEndpoint + ): Boolean = + if (sinkPrototypeRef == sourcePrototypeRef) true + else { + val mappingKey = sourcePrototypeRef -> sinkPrototypeRef + if (!directInterproceduralPairs(mappingKey)) true + else { + fedArgumentIndexesBySourceAndTarget.get(mappingKey) match { + case Some(fedIndexes) => fedIndexes(sinkEndpoint.parameterIndex) + case None => + throw new IllegalStateException( + "missing fed-argument provenance for parameter-position filter: " + + s"source_prototype_ref=$sourcePrototypeRef sink_ref=${sinkEndpoint.sinkRef}" + ) + } + } + } + + private def pathConstructorCanAttempt(sourceEndpoint: LuaSourceEndpoint, sinkEndpoint: LuaSinkEndpoint): Boolean = { + val source = parseQualifiedValueRef(sourceEndpoint.sourceRef) + val sink = parseQualifiedValueRef(sinkEndpoint.sinkRef) + val bridgePrototypes = sourceScopedBridgePrototypes(sourceEndpoint.sourceRef, source) + val samePrototypeForward = + source.modulePath == sink.modulePath && source.prototypeId == sink.prototypeId && source.pc <= sink.pc + samePrototypeForward || bridgePrototypes(prototypeRef(sink.modulePath, sink.prototypeId)) + } + + private def sourceScopedBridgePrototypes(sourceRef: String, source: QualifiedValueRef): Set[String] = + sourceScopedBridgePrototypeCache.getOrElseUpdate( + sourceRef, { + val sourcePrototype = prototypeRef(source.modulePath, source.prototypeId) + interproceduralFlowsBySourcePrototype + .getOrElse(sourcePrototype, Vector.empty) + .filter { flow => + attribution.incrementAggregate("bridge_argument_provenance_candidate_count") + val accepted = requiredCallsitePc(flow.callsiteId) >= source.pc + if (!accepted) { + attribution.incrementAggregate("bridge_candidate_pc_pruned_count") + attribution.incrementAggregate("early_candidate_short_circuit_count") + } + accepted + } + .flatMap { flow => + val targetPrototype = prototypeRef(flow.targetModulePath, flow.targetPrototypeId) + reachablePrototypesIncludingSelf(targetPrototype) + } + .toSet + } + ) + + private def reachablePrototypesIncludingSelf(startPrototype: String): Set[String] = + reachablePrototypesByStartPrototype.getOrElseUpdate( + startPrototype, + reachableFrom(callGraphAdjacency, startPrototype) + ) + + private def buildCallGraphAdjacency(): Map[String, Set[String]] = { + val adjacency = scala.collection.mutable.Map.empty[String, scala.collection.mutable.Set[String]] + def addNode(ref: String): Unit = + adjacency.getOrElseUpdate(ref, scala.collection.mutable.Set.empty) + def addEdge(source: String, target: String): Unit = { + addNode(source) + addNode(target) + adjacency(source) += target + } + + modules.foreach { module => + module.prototypes.foreach(prototype => addNode(prototypeRef(module.path, prototype.prototypeId))) + module.localCalls.foreach { call => + addEdge( + prototypeRef(module.path, prototypeIdFromCallsiteId(call.callsiteId)), + prototypeRef(module.path, call.targetPrototypeId) + ) + } + } + fieldTargets.foreach { target => + addEdge( + prototypeRef(target.fromModulePath, prototypeIdFromCallsiteId(target.callsiteId)), + prototypeRef(target.targetModulePath, target.targetPrototypeId) + ) + } + interproceduralArgFlows.foreach { flow => + val source = parseQualifiedValueRef(flow.fromArgumentRef) + addEdge( + prototypeRef(source.modulePath, source.prototypeId), + prototypeRef(flow.targetModulePath, flow.targetPrototypeId) + ) + } + interproceduralReturnFlows.foreach { flow => + val caller = parseQualifiedValueRef(flow.callerResultRef) + addEdge( + prototypeRef(flow.targetModulePath, flow.targetPrototypeId), + prototypeRef(caller.modulePath, caller.prototypeId) + ) + } + adjacency.view.mapValues(_.toSet).toMap + } + + private def buildDirectReachableSinkRefsBySource(): Map[String, Set[String]] = { + val sinkRefsByPrototype = sinkPrototypeByRef.groupMap(_._2)(_._1).view.mapValues(_.toSet).toMap + val reverseAdjacency = scala.collection.mutable.Map.empty[String, scala.collection.mutable.Set[String]] + callGraphAdjacency.foreach { case (sourcePrototype, targetPrototypes) => + reverseAdjacency.getOrElseUpdate(sourcePrototype, scala.collection.mutable.Set.empty) + targetPrototypes.foreach { targetPrototype => + reverseAdjacency.getOrElseUpdate(targetPrototype, scala.collection.mutable.Set.empty) += sourcePrototype + } + } + + val result = scala.collection.mutable.Map.empty[String, scala.collection.mutable.Set[String]] + sinkRefsByPrototype.foreach { case (sinkPrototypeRef, sinkRefs) => + val pending = scala.collection.mutable.Queue(sinkPrototypeRef) + val seen = scala.collection.mutable.Set.empty[String] + while (pending.nonEmpty) { + val prototypeRef = pending.dequeue() + if (!seen(prototypeRef)) { + seen += prototypeRef + result.getOrElseUpdate(prototypeRef, scala.collection.mutable.Set.empty) ++= sinkRefs + reverseAdjacency.getOrElse(prototypeRef, Set.empty).foreach(pending.enqueue(_)) + } + } + } + result.view.mapValues(_.toSet).toMap + } + } + + private def reachableFrom(graph: Map[String, Set[String]], source: String): Set[String] = { + val pending = scala.collection.mutable.Queue(source) + val seen = scala.collection.mutable.Set.empty[String] + while (pending.nonEmpty) { + val current = pending.dequeue() + if (!seen(current)) { + seen += current + graph.getOrElse(current, Set.empty).diff(seen.toSet).toVector.sorted.foreach(pending.enqueue(_)) + } + } + seen.toSet + } + + private def prototypeRef(valueRef: String): String = { + val parsed = parseQualifiedValueRef(valueRef) + prototypeRef(parsed.modulePath, parsed.prototypeId) + } + + private def prototypeRefFromAnyQualifiedRef(valueRef: String): String = + prototypeRef(modulePathFromQualifiedRef(valueRef), prototypeIdFromLocalValueRef(localRefFromQualifiedRef(valueRef))) + + private def prototypeRef(modulePath: String, prototypeId: String): String = s"$modulePath::$prototypeId" + + private def prototypeIdFromLocalValueRef(valueRef: String): String = + valueRef.split("@pc", 2).headOption.getOrElse(valueRef).split(":r", 2).headOption.getOrElse(valueRef) + + private def representativeCallReturnEdges(module: ModuleSummary): Vector[(String, String)] = + (module.prototypes.flatMap { prototype => + prototype.calls + .filter(call => isRepresentativeTaintPreservingCall(call.resolvedName)) + .flatMap { call => + call.returnRefs.headOption.toVector.flatMap { returnRef => + call.argumentRefs.map(argumentRef => qualify(module.path, argumentRef) -> qualify(module.path, returnRef)) + } + } + } ++ module.fieldCalls + .filter(call => isRepresentativeFieldReturnCall(call)) + .flatMap { call => + call.resultRef.toVector.flatMap { returnRef => + call.argumentRefs.map(argumentRef => qualify(module.path, argumentRef) -> qualify(module.path, returnRef)) + } + }).distinct + + private def isRepresentativeFieldReturnCall(call: FieldCall): Boolean = + call.resolvedName.exists(name => isRepresentativeTaintPreservingCall(Some(name))) + + private def isRepresentativeTaintPreservingCall(name: Option[String]): Boolean = + name.exists(resolved => + isRepresentativeSanitizerCall(Some(resolved)) || + Set("ciphertextFormat", "json.encode", "cjson.encode", "string.format", "string.lower", "string.upper") + .contains(resolved) + ) + + private def representativeTableValueEdges(module: ModuleSummary): Vector[(String, String)] = + module.prototypes.flatMap { prototype => + val tableSourcesBySlot = scala.collection.mutable.Map.empty[Int, Set[String]].withDefaultValue(Set.empty) + val edges = Vector.newBuilder[(String, String)] + prototype.instructions.sortBy(_.pc).foreach { instruction => + if (instruction.opcode == LuaOpcode.GetTable && tableSourcesBySlot(instruction.b).nonEmpty) { + val tableRead = valueRef(prototype.prototypeId, instruction.pc, instruction.b) + val result = valueRef(prototype.prototypeId, instruction.pc, instruction.a) + edges += qualify(module.path, tableRead) -> qualify(module.path, result) + } + representativeReadSlots(prototype, instruction).foreach { slot => + val read = valueRef(prototype.prototypeId, instruction.pc, slot) + tableSourcesBySlot(slot).foreach { source => + edges += qualify(module.path, source) -> qualify(module.path, read) + } + } + if (instruction.opcode == LuaOpcode.SetTable && instruction.b >= RkConstantBase) { + for { + valueSlot <- instruction.c.filter(_ < RkConstantBase) + } { + val tableRead = valueRef(prototype.prototypeId, instruction.pc, instruction.a) + val valueRead = valueRef(prototype.prototypeId, instruction.pc, valueSlot) + edges += qualify(module.path, valueRead) -> qualify(module.path, tableRead) + tableSourcesBySlot += instruction.a -> (tableSourcesBySlot(instruction.a) + valueRead) + } + } + if (instruction.opcode == LuaOpcode.SetList && instruction.b > 0) { + val tableRead = valueRef(prototype.prototypeId, instruction.pc, instruction.a) + setListValueSlots(instruction).foreach { valueSlot => + val valueRead = valueRef(prototype.prototypeId, instruction.pc, valueSlot) + edges += qualify(module.path, valueRead) -> qualify(module.path, tableRead) + tableSourcesBySlot += instruction.a -> (tableSourcesBySlot(instruction.a) + valueRead) + } + } + } + edges.result() + } + + private def representativeParameterTableReadEdges(module: ModuleSummary): Vector[(String, String)] = + module.prototypes.flatMap { prototype => + val parameterSlots = (0 until prototype.numParams).toSet + prototype.instructions.collect { + case instruction + if instruction.opcode == LuaOpcode.GetTable && + parameterSlots(instruction.b) && + instruction.c.exists(_ >= RkConstantBase) => + qualify(module.path, valueRef(prototype.prototypeId, instruction.pc, instruction.b)) -> + qualify(module.path, valueRef(prototype.prototypeId, instruction.pc, instruction.a)) + } + } + + private def representativeIteratorValueEdges(module: ModuleSummary): Vector[(String, String)] = + module.prototypes.flatMap { prototype => + prototype.calls + .filter(call => call.resolvedName.contains("ipairs")) + .flatMap { iteratorCall => + val iteratorInputs = iteratorCall.argumentRefs.map(qualify(module.path, _)) + for { + callInstruction <- prototype.instructions + .find(instruction => instruction.pc == iteratorCall.pc && instruction.opcode == LuaOpcode.Call) + .toVector + loop <- ipairsTForLoop(prototype, callInstruction).toVector + input <- iteratorInputs + edge <- tforLoopTableReadEdges(module.path, prototype, loop, input) + } yield edge + } + }.distinct + + private final case class TForLoopRegion(bodyStartPc: Int, loopInstruction: LuaInstruction) + + private def ipairsTForLoop(prototype: PrototypeSummary, iteratorCall: LuaInstruction): Option[TForLoopRegion] = + for { + jump <- prototype.instructions.find(instruction => + instruction.pc == iteratorCall.pc + 1 && instruction.opcode == LuaOpcode.Jmp + ) + tforLoop <- prototype.instructions + .filter(instruction => + instruction.opcode == LuaOpcode.TForLoop && + instruction.a == iteratorCall.a && + instruction.pc > jump.pc + ) + .sortBy(_.pc) + .headOption + } yield TForLoopRegion(jump.pc, tforLoop) + + private def tforLoopTableReadEdges( + modulePath: String, + prototype: PrototypeSummary, + loop: TForLoopRegion, + iteratorInput: String + ): Vector[(String, String)] = { + val tforLoop = loop.loopInstruction + val loopValueSlots = tforLoop.c + .map(count => (tforLoop.a + 3 until tforLoop.a + 3 + count).toSet) + .getOrElse(Set.empty) + prototype.instructions + .filter(instruction => instruction.pc > loop.bodyStartPc && instruction.pc < tforLoop.pc) + .collect { + case instruction + if instruction.opcode == LuaOpcode.GetTable && + loopValueSlots(instruction.b) && + instruction.c.exists(_ >= RkConstantBase) => + val tableRead = qualify(modulePath, valueRef(prototype.prototypeId, instruction.pc, instruction.b)) + val fieldRead = qualify(modulePath, valueRef(prototype.prototypeId, instruction.pc, instruction.a)) + Vector(iteratorInput -> tableRead, tableRead -> fieldRead) + } + .flatten + } + + private def representativeExpressionResultEdges(module: ModuleSummary): Vector[(String, String)] = + module.prototypes.flatMap { prototype => + val sortedInstructions = prototype.instructions.sortBy(_.pc) + sortedInstructions.flatMap { instruction => + val result = valueRef(prototype.prototypeId, instruction.pc, instruction.a) + val operandEdges = instruction.opcode match { + case LuaOpcode.Add | LuaOpcode.Sub | LuaOpcode.Mul | LuaOpcode.Div | LuaOpcode.Mod | LuaOpcode.Pow => + (Vector(Some(instruction.b)) ++ Vector(instruction.c)).flatten + .filter(_ < RkConstantBase) + .map(slot => + qualify(module.path, valueRef(prototype.prototypeId, instruction.pc, slot)) -> qualify( + module.path, + result + ) + ) + case LuaOpcode.Concat => + (instruction.b to instruction.c.getOrElse(instruction.b)).toVector + .map(slot => + qualify(module.path, valueRef(prototype.prototypeId, instruction.pc, slot)) -> qualify( + module.path, + result + ) + ) + case _ => Vector.empty + } + val reachingEdges = + if (operandEdges.nonEmpty) + expressionResultReadEdges(module.path, prototype, sortedInstructions, instruction) + else Vector.empty + operandEdges ++ reachingEdges + } + }.distinct + + private def expressionResultReadEdges( + modulePath: String, + prototype: PrototypeSummary, + sortedInstructions: Vector[LuaInstruction], + expression: LuaInstruction + ): Vector[(String, String)] = { + val resultSlot = expression.a + val resultRef = valueRef(prototype.prototypeId, expression.pc, resultSlot) + val edges = Vector.newBuilder[(String, String)] + var stopped = false + sortedInstructions + .filter(_.pc > expression.pc) + .foreach { instruction => + if (!stopped) { + representativeReadSlots(prototype, instruction) + .filter(_ == resultSlot) + .foreach(slot => + edges += qualify(modulePath, resultRef) -> qualify( + modulePath, + valueRef(prototype.prototypeId, instruction.pc, slot) + ) + ) + if (overwritesSlot(instruction, resultSlot)) { + stopped = true + } + } + } + edges.result() + } + + private def overwritesSlot(instruction: LuaInstruction, slot: Int): Boolean = + instruction.opcode match { + case LuaOpcode.SetGlobal | LuaOpcode.SetUpval | LuaOpcode.SetTable | LuaOpcode.SetList | LuaOpcode.Return | + LuaOpcode.Eq | LuaOpcode.Lt | LuaOpcode.Le | LuaOpcode.Test | LuaOpcode.Jmp => + false + case LuaOpcode.Call | LuaOpcode.TailCall => + instruction.c match { + case Some(0) => instruction.a <= slot + case Some(1) => false + case Some(n) => slot >= instruction.a && slot < instruction.a + n - 1 + case None => false + } + case _ => instruction.a == slot + } + + private def representativeReadSlots(prototype: PrototypeSummary, instruction: LuaInstruction): Vector[Int] = + instruction.opcode match { + case LuaOpcode.Move => Vector(instruction.b) + case LuaOpcode.GetTable => Vector(Some(instruction.b), instruction.c.filter(_ < RkConstantBase)).flatten + case LuaOpcode.SetTable => + Vector( + Some(instruction.a), + Some(instruction.b).filter(_ < RkConstantBase), + instruction.c.filter(_ < RkConstantBase) + ).flatten + case LuaOpcode.SetList => Vector(instruction.a) ++ setListValueSlots(instruction) + case LuaOpcode.Call | LuaOpcode.TailCall => + (Vector(instruction.a) ++ callArgumentRefs(prototype, instruction).map(slotFromLocalValueRef)).distinct + case LuaOpcode.Return => + instruction.b match { + case 0 | 1 => Vector.empty + case n => (instruction.a until (instruction.a + n - 1)).toVector + } + case LuaOpcode.Eq | LuaOpcode.Lt | LuaOpcode.Le => + Vector(Some(instruction.b).filter(_ < RkConstantBase), instruction.c.filter(_ < RkConstantBase)).flatten + case LuaOpcode.Add | LuaOpcode.Sub | LuaOpcode.Mul | LuaOpcode.Div | LuaOpcode.Mod | LuaOpcode.Pow => + Vector(Some(instruction.b).filter(_ < RkConstantBase), instruction.c.filter(_ < RkConstantBase)).flatten + case LuaOpcode.Unm | LuaOpcode.Not | LuaOpcode.Len => + Vector(instruction.b) + case LuaOpcode.Concat => + (instruction.b to instruction.c.getOrElse(instruction.b)).toVector + case _ => Vector.empty + } + + private def setListValueSlots(instruction: LuaInstruction): Vector[Int] = + if (instruction.b > 0) (1 to instruction.b).map(offset => instruction.a + offset).toVector else Vector.empty + + private def valueRef(prototypeId: String, pc: Int, slot: Int): String = + s"$prototypeId@pc$pc:r$slot" + + private def slotFromLocalValueRef(ref: String): Int = + ref + .split(":r", 2) + .lift(1) + .flatMap(_.toIntOption) + .getOrElse(throw new IllegalArgumentException(s"Lua local value ref is missing slot: $ref")) + + private def sourceEndpointsForModule(module: ModuleSummary): Vector[LuaSourceEndpoint] = { + val directEndpoints = module.prototypes.flatMap { prototype => + prototype.calls.flatMap { call => + call.resolvedName + .filter(name => triggerMatches("*.formvalue", name)) + .flatMap(name => + call.returnRefs.headOption.map(sourceRef => + LuaSourceEndpoint( + qualify(module.path, sourceRef), + qualify(module.path, call.callsiteId), + name, + Provenance + ) + ) + ) + } + } + val requireStringsByRef = module.requireCalls.flatMap(call => call.resultRef.zip(call.requireString)).toMap + val requireFieldEndpoints = module.fieldCalls.flatMap { call => + for { + requireRef <- call.requireRef + requireString <- requireStringsByRef.get(requireRef) + if requireString == "luci.http" && call.fieldName == "formvalue" + resultRef <- call.resultRef + } yield LuaSourceEndpoint( + qualify(module.path, resultRef), + qualify(module.path, call.callsiteId), + s"$requireString.${call.fieldName}", + Provenance + ) + } + preferCanonicalSourceEndpoints(directEndpoints ++ requireFieldEndpoints) + } + + private def preferCanonicalSourceEndpoints(endpoints: Vector[LuaSourceEndpoint]): Vector[LuaSourceEndpoint] = + endpoints + .groupBy(_.sourceRef) + .values + .map { candidates => + candidates.sortBy(endpoint => if (endpoint.trigger == "luci.http.formvalue") 0 else 1).head + } + .toVector + + private def sinkEndpointsForModule( + module: ModuleSummary, + attribution: PerformanceAttributionCollector + ): Vector[LuaSinkEndpoint] = + (module.prototypes.flatMap { prototype => + prototype.calls.flatMap { call => + call.resolvedName + .flatMap(name => directCommandSinkTrigger(module, name)) + .flatMap { name => + sinkValueRef(call) + .filter { argumentRef => + val accepted = !isConcreteStringArgument(prototype, call.pc, argumentRef) + attribution.recordP1(accepted) + accepted + } + .map(argumentRef => + LuaSinkEndpoint( + qualify(module.path, argumentRef), + qualify(module.path, call.callsiteId), + canonicalSinkTrigger(name), + 0, + Provenance + ) + ) + } + } + } ++ module.fieldCalls + .flatMap(call => + fieldCallSinkTrigger(module, call).flatMap { trigger => + for { + prototype <- module.prototype(prototypeIdFromCallsiteId(call.callsiteId)) + pc <- callsitePc(call.callsiteId) + argumentRef <- call.argumentRefs.headOption + accepted = !isConcreteStringArgument(prototype, pc, argumentRef) + _ = attribution.recordP1(accepted) + if accepted + } yield LuaSinkEndpoint( + qualify(module.path, argumentRef), + qualify(module.path, call.callsiteId), + trigger, + 0, + Provenance + ) + } + )).distinct + + private def directCommandSinkTrigger(module: ModuleSummary, name: String): Option[String] = + name match { + case "os.execute" | "io.popen" => Some(canonicalSinkTrigger(name)) + case "forkExec" if modulePathDeclaresRequire(module.path, "xiaoqiang.common.XQFunction") => + Some("forkExec") + case _ => None + } + + private def fieldCallSinkTrigger(module: ModuleSummary, call: FieldCall): Option[String] = + if ( + call.fieldName == "exec" && + (call.resolvedName.contains("luci.util.exec") || + fieldCallRequirePath(module, call).contains(modulePathForRequire("luci.util"))) + ) { + Some("luci.util.exec") + } else if ( + call.fieldName == "forkExec" && + fieldCallRequirePath(module, call).contains(modulePathForRequire("xiaoqiang.common.XQFunction")) + ) { + Some("xiaoqiang.common.XQFunction.forkExec") + } else None + + private def fieldCallRequirePath(module: ModuleSummary, call: FieldCall): Option[String] = + ( + call.requireRef.flatMap(module.requireResultRefs.get).toVector ++ + capturedUpvalueRequirePath(module, call).toVector + ).distinct match { + case Vector(single) => Some(single) + case Vector() => None + case many => + throw new IllegalStateException( + s"ambiguous field-call require path: module=${module.path} callsite=${call.callsiteId} paths=${many.sorted + .mkString("[", ",", "]")}" + ) + } + + private def capturedUpvalueRequirePath(module: ModuleSummary, call: FieldCall): Option[String] = + for { + targetRef <- call.targetRef + target <- registerWrite(targetRef) + prototype <- module.prototype(prototypeIdFromCallsiteId(call.callsiteId)) + getTable <- prototype.instructions.find(instruction => + instruction.pc == target.pc && + instruction.a == target.slot && + instruction.opcode == LuaOpcode.GetTable + ) + upvalueSlot <- getUpvalueBefore(prototype, getTable.pc, getTable.b) + requireRef <- module.capturedRequireRefs.get(capturedRequireRefKey(prototype.prototypeId, upvalueSlot)) + requirePath <- module.requireResultRefs.get(requireRef) + } yield requirePath + + private def canonicalSinkTrigger(name: String): String = + name match { + case "require.popen" => "io.popen" + case other => other + } + + private def sinkValueRef(call: ResolvedCall): Option[String] = + call.argumentRefs.headOption + + private def isRepresentativeSanitizerCall(name: Option[String]): Boolean = + name.exists { resolved => + resolved == "tonumber" || + resolved == "tostring" || + resolved == "string.format" || + resolved.endsWith("._cmdformat") || + resolved.endsWith(".macFormat") || + resolved.endsWith(".binaryBase64Enc") + } + + private def ruleMatchesFor( + sourceEndpoints: Vector[LuaSourceEndpoint], + sinkEndpoints: Vector[LuaSinkEndpoint] + ): Vector[LuaRuleMatch] = { + val sourceRules = + sourceEndpoints.map(row => LuaRuleMatch(row.callsiteId, "source", "formvalue", row.trigger, None, row.provenance)) + val sinkRules = sinkEndpoints.map(row => + LuaRuleMatch( + row.callsiteId, + "sink", + finalSegment(row.trigger), + row.trigger, + Some(row.parameterIndex), + row.provenance + ) + ) + sourceRules ++ sinkRules + } + + private def sanitizerCallsFor(modules: Vector[ModuleSummary]): Vector[LuaSanitizerCall] = + modules.flatMap(sanitizerCallsForModule).distinct + + private def sanitizerCallsForModule(module: ModuleSummary): Vector[LuaSanitizerCall] = { + val resolvedCalls = module.prototypes.flatMap { prototype => + prototype.calls.flatMap { call => + for { + name <- call.resolvedName + if isRealFirmwareSanitizerName(name) + sanitizedValueRef <- sanitizerProducedValueRef(prototype, call) + } yield LuaSanitizerCall( + qualify(module.path, call.callsiteId), + name, + qualify(module.path, sanitizedValueRef), + Provenance + ) + } + } + resolvedCalls.distinct + } + + private def realFirmwareSanitizerProducedRefs(modules: Vector[ModuleSummary]): Set[String] = + modules + .flatMap(sanitizerCallsForModule) + .map(_.sanitizedValueRef) + .toSet + + private def isRealFirmwareSanitizerName(name: String): Boolean = + RealFirmwareSanitizerSuffixes.contains(name.split('.').lastOption.getOrElse(name)) + + private def sanitizerProducedValueRef(prototype: PrototypeSummary, call: ResolvedCall): Option[String] = + (call.returnRefs ++ callTargetValueRef(prototype, call).toVector).find(_.nonEmpty) + + private def callTargetValueRef(prototype: PrototypeSummary, call: ResolvedCall): Option[String] = + prototype.instructions + .find(instruction => + instruction.pc == call.pc && (instruction.opcode == LuaOpcode.Call || instruction.opcode == LuaOpcode.TailCall) + ) + .map(instruction => s"${prototype.prototypeId}@pc${instruction.pc}:r${instruction.a}") + + private def sanitizerClassificationsFor( + taintPaths: Vector[LuaTaintPath], + sanitizerCalls: Vector[LuaSanitizerCall] + ): Vector[LuaSanitizerClassification] = + taintPaths.flatMap { path => + sanitizerCalls + .flatMap { call => + val onChain = path.pathSteps.contains(call.sanitizedValueRef) + val comparableCall = sameModuleRef(call.sanitizedValueRef, path.sourceRef) + val classification = if (onChain) "sanitized" else "not-sanitized" + Option.when(onChain || comparableCall)( + LuaSanitizerClassification( + path.sourceRef, + path.sinkRef, + call.callsiteId, + call.sanitizerName, + appliesToSink = true, + onDataflowChain = onChain, + classification + ) + ) + } + } + + private def reportClassificationsFor( + taintPaths: Vector[LuaTaintPath], + sanitizerClassifications: Vector[LuaSanitizerClassification] + ): Vector[LuaReportClassification] = + taintPaths.map { path => + val sanitized = sanitizerClassifications.exists(row => + row.sourceRef == path.sourceRef && row.sinkRef == path.sinkRef && row.onDataflowChain + ) + if (sanitized) { + LuaReportClassification(path.sourceRef, path.sinkRef, "sanitized", "on-chain-sanitizer") + } else { + LuaReportClassification(path.sourceRef, path.sinkRef, "true-positive", "no-on-chain-sanitizer") + } + } + + private def vulnerabilityReportsFor( + taintPaths: Vector[LuaTaintPath], + reportClassifications: Vector[LuaReportClassification] + ): Vector[LuaVulnerabilityReport] = + taintPaths.flatMap { path => + reportClassifications + .find(row => row.sourceRef == path.sourceRef && row.sinkRef == path.sinkRef) + .filter(_.classification == "true-positive") + .map(row => + LuaVulnerabilityReport( + path.sourceRef, + path.sinkRef, + "path-proven", + row.classification, + path.pathSteps, + path.provenance + ) + ) + } + + private def semanticBoundaries( + modules: Vector[ModuleSummary], + resolutions: Vector[LuaModuleResolution], + fieldTargets: Vector[LuaModuleFieldCallTarget] + ): Vector[LuaE4Boundary] = { + val resolutionBoundaries = resolutions.collect { + case resolution if resolution.resolutionStatus != "matched" => + LuaE4Boundary( + s"${resolution.fromModulePath}:require:${resolution.requireString}", + "module-resolution", + unresolvedReason(resolution) + ) + } + val unresolvedCalls = modules.flatMap { module => + module.unresolvedLocalCalls.map(call => + LuaE4Boundary(qualify(module.path, call.callsiteId), "interprocedural", "unresolved-callee") + ) + } + val fieldTargetCallsites = fieldTargets.map(_.callsiteId).toSet + val missingFields = modules.flatMap { module => + module.fieldCalls + .filter(call => !fieldTargetCallsites(call.callsiteId)) + .filter(call => module.requireResultRefs.keySet.exists(ref => call.requireRef.contains(ref))) + .map(call => LuaE4Boundary(qualify(module.path, call.callsiteId), "module-field", "missing-export-field")) + } + resolutionBoundaries ++ unresolvedCalls ++ missingFields + } + + private def qualify(modulePath: String, ref: String): String = s"$modulePath:$ref" + + private final case class ProgramArtifact(path: String, root: Option[LuaPrototype]) + + private final case class ModuleSummary( + path: String, + declaredModuleNames: Set[String], + prototypes: Vector[PrototypeSummary], + requireCalls: Vector[RequireCall], + requireResultRefs: Map[String, String], + capturedRequireRefs: Map[String, String], + exports: Vector[ModuleExport], + fieldCalls: Vector[FieldCall], + localCalls: Vector[LocalCall], + unresolvedLocalCalls: Vector[UnresolvedLocalCall], + localFlows: Vector[LuaLocalFlow], + globalFlows: Vector[LuaGlobalFlow] + ) { + def prototype(id: String): Option[PrototypeSummary] = prototypes.find(_.prototypeId == id) + } + + private object ModuleSummary { + def fromArtifact(artifact: ProgramArtifact): Option[ModuleSummary] = + artifact.root.map { root => + val capturedNames = capturedUpvalueNames(root) + val prototypes = allPrototypes(root).map(prototype => PrototypeSummary.fromPrototype(prototype, capturedNames)) + val declaredNames = declaredModuleNames(root) + val requireCalls = prototypes.flatMap(detectRequireCalls) + val exports = detectExports(artifact.path, root) + val localCalls = + detectLocalCalls(root) ++ detectPlainModuleGlobalExportCalls(artifact.path, prototypes, exports) + val localSemantic = LuaInstructionSemantics.normalize(root) + val capturedRefs = capturedRequireRefs(root, requireCalls) + val fieldCalls = + prototypes.flatMap(prototype => detectFieldCalls(artifact.path, prototype, requireCalls, capturedRefs)) + ModuleSummary( + path = artifact.path, + declaredModuleNames = declaredNames, + prototypes = prototypes, + requireCalls = requireCalls, + requireResultRefs = requireCalls.collect { + case call if call.resultRef.nonEmpty && call.requireString.exists(_.nonEmpty) => + call.resultRef.get -> modulePathForRequire(call.requireString.get) + }.toMap, + capturedRequireRefs = capturedRefs, + exports = exports, + fieldCalls = fieldCalls, + localCalls = localCalls, + unresolvedLocalCalls = localSemantic.unresolvedCalls.map(call => UnresolvedLocalCall(call.callsiteId)), + localFlows = localSemantic.localFlows, + globalFlows = localSemantic.globalFlows + ) + } + } + + private final case class PrototypeSummary( + prototypeId: String, + numParams: Int, + maxStack: Int, + instructions: Vector[LuaInstruction], + constants: Vector[LuaConstant], + parameterRefs: Vector[String], + returnRefs: Vector[String], + calls: Vector[ResolvedCall], + capturedNames: Map[Int, String] + ) + + private object PrototypeSummary { + def fromPrototype( + prototype: LuaPrototype, + capturedNamesByPrototype: Map[String, Map[Int, String]] = Map.empty + ): PrototypeSummary = { + val capturedNames = capturedNamesByPrototype.getOrElse(prototype.prototypeId, Map.empty) + PrototypeSummary( + prototype.prototypeId, + prototype.numParams, + prototype.maxStack, + prototype.instructions, + prototype.constants, + (0 until prototype.numParams).map(slot => s"${prototype.prototypeId}:r$slot").toVector, + returnRefs(prototype), + resolvedCalls(prototype, capturedNames), + capturedNames + ) + } + } + + private final case class RequireCall( + callsiteId: String, + prototypeId: String, + resultRef: Option[String], + requireString: Option[String] + ) + + private final case class ModuleExport( + modulePath: String, + tableRef: String, + fieldName: String, + targetPrototypeId: String + ) + + private final case class FieldCall( + callsiteId: String, + fieldName: String, + resolvedName: Option[String], + targetRef: Option[String], + requireRef: Option[String], + argumentRefs: Vector[String], + resultRef: Option[String] + ) + + private final case class RegisterFieldTarget( + fieldName: String, + resolvedName: Option[String], + targetRef: String, + requireRef: Option[String] + ) + + private final case class LocalCall( + callsiteId: String, + targetPrototypeId: String, + argumentRefs: Vector[String], + resultRef: Option[String] + ) + + private final case class UnresolvedLocalCall(callsiteId: String) + + private final case class ResolvedCall( + callsiteId: String, + prototypeId: String, + pc: Int, + resolvedName: Option[String], + argumentRefs: Vector[String], + returnRefs: Vector[String] + ) + + private final class ModuleIndex(modules: Vector[ModuleSummary]) { + private val byPath = modules.map(module => module.path -> module).toMap + private val byDeclaredName = + modules.flatMap(module => module.declaredModuleNames.map(name => name -> module)).groupMap(_._1)(_._2) + + def module(path: String): Option[ModuleSummary] = byPath.get(path) + + def resolve(requireString: String): ModuleResolutionResult = + if (requireString.isEmpty) ModuleResolutionResult.Unresolved + else { + val requiredSuffix = modulePathForRequire(requireString) + val pathCandidates = + modules.filter(module => module.path == requiredSuffix || module.path.endsWith(s"/$requiredSuffix")) + val candidates = (pathCandidates ++ byDeclaredName.getOrElse(requireString, Vector.empty)).distinct + .sortBy(module => (module.path.count(_ == '/'), module.path)) + candidates.headOption match { + case Some(module) => ModuleResolutionResult.Matched(module) + case None => ModuleResolutionResult.Unresolved + } + } + } + + private enum ModuleResolutionResult { + case Matched(module: ModuleSummary) + case Unresolved + } + + private def allPrototypes(prototype: LuaPrototype): Vector[LuaPrototype] = + prototype +: prototype.nested.flatMap(allPrototypes) + + private def detectRequireCalls(prototype: PrototypeSummary): Vector[RequireCall] = + prototype.instructions + .filter(instruction => instruction.opcode == LuaOpcode.Call && isGlobalName(prototype, instruction.a, "require")) + .map { instruction => + val argumentString = precedingLoadString(prototype, instruction.pc, instruction.a + 1) + RequireCall( + callsiteId = s"${prototype.prototypeId}@pc${instruction.pc}", + prototypeId = prototype.prototypeId, + resultRef = callReturnRefs(instruction).headOption.map(ref => s"${prototype.prototypeId}$ref"), + requireString = argumentString + ) + } + + private def detectExports(path: String, root: LuaPrototype): Vector[ModuleExport] = { + val returnedTableExports = root.instructions + .filter(instruction => instruction.opcode == LuaOpcode.SetTable) + .flatMap { instruction => + for { + fieldName <- constantName(root.constants, instruction.b - RkConstantBase) + .filter(_ => instruction.b >= RkConstantBase) + targetSlot <- instruction.c + target <- closureInSlotBefore(root, instruction.pc, targetSlot) + if isReturnedTable(root, instruction.a) + } yield ModuleExport(path, s"${root.prototypeId}:r${instruction.a}", fieldName, target) + } + returnedTableExports ++ detectPlainModuleGlobalExports(path, root) ++ + detectPlainModuleGlobalTableFieldExports(path, root) + } + + private def detectPlainModuleGlobalExports(path: String, root: LuaPrototype): Vector[ModuleExport] = + if (hasPlainModuleLiteralCall(root)) { + root.instructions + .filter(_.opcode == LuaOpcode.SetGlobal) + .flatMap { instruction => + for { + fieldName <- constantName(root.constants, instruction.b) + target <- closureInSlotBefore(root, instruction.pc, instruction.a) + } yield ModuleExport(path, s"$path:module-global", fieldName, target) + } + } else Vector.empty + + private def detectPlainModuleGlobalTableFieldExports(path: String, root: LuaPrototype): Vector[ModuleExport] = { + val summary = PrototypeSummary.fromPrototype(root) + val registerStrings = scala.collection.mutable.Map.empty[Int, String] + val registerClosures = scala.collection.mutable.Map.empty[Int, String] + val registerTables = scala.collection.mutable.Map.empty[Int, String] + val tableFields = scala.collection.mutable.Map.empty[String, scala.collection.mutable.Map[String, String]] + val globalTables = scala.collection.mutable.Map.empty[String, String] + val prototypeIds = allPrototypes(root).map(_.prototypeId).toSet + var moduleCallSeen = false + + def clearRegister(slot: Int): Unit = { + registerStrings -= slot + registerClosures -= slot + registerTables -= slot + } + + def copyRegister(dest: Int, source: Int): Unit = { + val stringValue = registerStrings.get(source) + val closureValue = registerClosures.get(source) + val tableValue = registerTables.get(source) + clearRegister(dest) + stringValue.foreach(registerStrings += dest -> _) + closureValue.foreach(registerClosures += dest -> _) + tableValue.foreach(registerTables += dest -> _) + } + + def clearCallWrites(instruction: LuaInstruction): Unit = + callReturnRefs(instruction).flatMap(registerWrite).foreach(write => clearRegister(write.slot)) + + def setTableKey(instruction: LuaInstruction): Option[String] = + if (instruction.b >= RkConstantBase) constantName(root.constants, instruction.b - RkConstantBase) + else registerStrings.get(instruction.b) + + def setTableClosureValue(instruction: LuaInstruction): Option[String] = + instruction.c.filter(_ < RkConstantBase).flatMap(registerClosures.get) + + root.instructions.sortBy(_.pc).foreach { + case instruction if instruction.opcode == LuaOpcode.LoadK => + clearRegister(instruction.a) + constantName(root.constants, instruction.b).foreach(registerStrings += instruction.a -> _) + + case instruction if instruction.opcode == LuaOpcode.Closure => + clearRegister(instruction.a) + registerClosures += instruction.a -> s"${root.prototypeId}.${instruction.b}" + + case instruction if instruction.opcode == LuaOpcode.NewTable => + clearRegister(instruction.a) + val tableRef = s"${root.prototypeId}@pc${instruction.pc}:r${instruction.a}" + registerTables += instruction.a -> tableRef + tableFields.getOrElseUpdate(tableRef, scala.collection.mutable.Map.empty) + + case instruction if instruction.opcode == LuaOpcode.Move => + copyRegister(instruction.a, instruction.b) + + case instruction if instruction.opcode == LuaOpcode.GetGlobal => + clearRegister(instruction.a) + constantName(root.constants, instruction.b).foreach { fieldName => + globalTables.get(fieldName).foreach(registerTables += instruction.a -> _) + } + + case instruction if instruction.opcode == LuaOpcode.SetGlobal => + for { + fieldName <- constantName(root.constants, instruction.b) + tableRef <- registerTables.get(instruction.a) + if moduleCallSeen + } globalTables += fieldName -> tableRef + + case instruction if instruction.opcode == LuaOpcode.SetTable => + for { + tableRef <- registerTables.get(instruction.a) + key <- setTableKey(instruction) + target <- setTableClosureValue(instruction) + } tableFields.getOrElseUpdate(tableRef, scala.collection.mutable.Map.empty) += key -> target + + case instruction if instruction.opcode == LuaOpcode.Call || instruction.opcode == LuaOpcode.TailCall => + if ( + summary.calls.exists(call => call.callsiteId == s"${root.prototypeId}@pc${instruction.pc}") && + callTargetNameAt(summary, instruction.pc, instruction.a).contains("module") && + precedingLoadString(summary, instruction.pc, instruction.a + 1).nonEmpty + ) { + moduleCallSeen = true + } + clearCallWrites(instruction) + + case instruction if instruction.opcode == LuaOpcode.Return || instruction.opcode == LuaOpcode.SetList => + + case instruction => + clearRegister(instruction.a) + } + + globalTables.toVector.flatMap { case (globalName, tableRef) => + tableFields.get(tableRef).toVector.flatMap { fields => + fields.toVector.collect { + case (fieldName, targetPrototypeId) if prototypeIds(targetPrototypeId) => + ModuleExport(path, s"$path:module-global", s"$globalName.$fieldName", targetPrototypeId) + } + } + }.distinct + } + + private def hasPlainModuleLiteralCall(root: LuaPrototype): Boolean = { + declaredModuleNames(root).nonEmpty + } + + private def declaredModuleNames(root: LuaPrototype): Set[String] = { + val summary = PrototypeSummary.fromPrototype(root) + summary.calls.flatMap { call => + if (call.resolvedName.contains("module")) { + call.argumentRefs.headOption + .flatMap(ref => registerWrite(ref)) + .flatMap(write => precedingStringConstant(summary, call.pc, write.slot)) + } else None + }.toSet + } + + private def detectLocalCalls(root: LuaPrototype): Vector[LocalCall] = { + val semantics = LuaInstructionSemantics.normalize(root) + val closureCalls = semantics.callTargetCandidates.flatMap { candidate => + semantics.callSites + .find(_.callsiteId == candidate.callsiteId) + .map { callsite => + LocalCall( + callsiteId = callsite.callsiteId, + targetPrototypeId = candidate.targetRef, + argumentRefs = callsite.firstArgSlot + .zip(callsite.argCount) + .toVector + .flatMap { case (firstSlot, count) => + (firstSlot until (firstSlot + count)).map(slot => s"${callsite.prototypeId}@pc${callsite.pc}:r$slot") + }, + resultRef = callsite.firstReturnSlot.map(slot => s"${callsite.prototypeId}@pc${callsite.pc}:r$slot") + ) + } + } + closureCalls.distinct + } + + private def detectPlainModuleGlobalExportCalls( + path: String, + prototypes: Vector[PrototypeSummary], + exports: Vector[ModuleExport] + ): Vector[LocalCall] = { + val uniquePlainGlobalExports = exports + .filter(_.tableRef == s"$path:module-global") + .groupBy(_.fieldName) + .collect { + case (fieldName, exportsForField) if exportsForField.size == 1 => + fieldName -> exportsForField.head.targetPrototypeId + } + .toMap + + prototypes.flatMap { prototype => + prototype.calls.flatMap { call => + for { + resolvedName <- call.resolvedName + targetPrototypeId <- uniquePlainGlobalExports.get(resolvedName) + } yield LocalCall( + callsiteId = call.callsiteId, + targetPrototypeId = targetPrototypeId, + argumentRefs = call.argumentRefs, + resultRef = call.returnRefs.headOption + ) + } + }.distinct + } + + private def detectFieldCalls( + path: String, + prototype: PrototypeSummary, + requireCalls: Vector[RequireCall], + capturedRequireRefs: Map[String, String] + ): Vector[FieldCall] = { + val requireCallsByCallsite = requireCalls.map(call => call.callsiteId -> call).toMap + val requireStringByRef = requireCalls + .flatMap(call => call.resultRef.zip(call.requireString)) + .toMap + val registerNames = scala.collection.mutable.Map.empty[Int, Vector[String]] + val registerRequireRefs = scala.collection.mutable.Map.empty[Int, String] + val registerFieldTargets = scala.collection.mutable.Map.empty[Int, RegisterFieldTarget] + val rows = Vector.newBuilder[FieldCall] + + def clearRegister(slot: Int): Unit = { + registerNames -= slot + registerRequireRefs -= slot + registerFieldTargets -= slot + } + + def clearCallWrites(instruction: LuaInstruction): Unit = + callReturnRefs(instruction).flatMap(registerWrite).foreach(write => clearRegister(write.slot)) + + def clearDefaultWrite(instruction: LuaInstruction): Unit = + instruction.opcode match { + case LuaOpcode.SetGlobal | LuaOpcode.SetTable | LuaOpcode.SetList | LuaOpcode.Return | LuaOpcode.Eq | + LuaOpcode.Lt | LuaOpcode.Le | LuaOpcode.Test | LuaOpcode.Jmp => + case LuaOpcode.Call | LuaOpcode.TailCall => + clearCallWrites(instruction) + case _ => + clearRegister(instruction.a) + } + + def copyRegister(dest: Int, source: Int): Unit = { + clearRegister(dest) + registerNames.get(source).foreach(registerNames += dest -> _) + registerRequireRefs.get(source).foreach(registerRequireRefs += dest -> _) + registerFieldTargets.get(source).foreach(registerFieldTargets += dest -> _) + } + + prototype.instructions.sortBy(_.pc).foreach { + case instruction if instruction.opcode == LuaOpcode.GetGlobal => + clearRegister(instruction.a) + constantName(prototype.constants, instruction.b).foreach(name => registerNames += instruction.a -> Vector(name)) + + case instruction if instruction.opcode == LuaOpcode.GetUpval => + clearRegister(instruction.a) + capturedRequireRefs.get(capturedRequireRefKey(prototype.prototypeId, instruction.b)).foreach { ref => + registerRequireRefs += instruction.a -> ref + requireStringByRef.get(ref).foreach(name => registerNames += instruction.a -> Vector(name)) + } + prototype.capturedNames.get(instruction.b).foreach(name => registerNames += instruction.a -> Vector(name)) + + case instruction if instruction.opcode == LuaOpcode.Move => + copyRegister(instruction.a, instruction.b) + + case instruction if instruction.opcode == LuaOpcode.GetTable => + val key = fieldName(prototype.constants, instruction) + val baseName = registerNames.get(instruction.b) + val baseTarget = registerFieldTargets.get(instruction.b) + val requireRef = registerRequireRefs.get(instruction.b).orElse(baseTarget.flatMap(_.requireRef)) + clearRegister(instruction.a) + key.foreach { field => + val resolvedName = baseName.map(parts => (parts :+ field).mkString(".")) + baseName.foreach(parts => registerNames += instruction.a -> (parts :+ field)) + requireRef.foreach(registerRequireRefs += instruction.a -> _) + if (requireRef.nonEmpty) { + val exportField = baseTarget.map(target => s"${target.fieldName}.$field").getOrElse(field) + registerFieldTargets += instruction.a -> RegisterFieldTarget( + exportField, + resolvedName, + s"${prototype.prototypeId}@pc${instruction.pc}:r${instruction.a}", + requireRef + ) + } + } + + case instruction if instruction.opcode == LuaOpcode.Self => + val key = instruction.c.flatMap(value => constantName(prototype.constants, value - RkConstantBase)) + val baseName = registerNames.get(instruction.b) + val baseTarget = registerFieldTargets.get(instruction.b) + val requireRef = registerRequireRefs.get(instruction.b).orElse(baseTarget.flatMap(_.requireRef)) + clearRegister(instruction.a) + copyRegister(instruction.a + 1, instruction.b) + key.foreach { field => + val resolvedName = baseName.map(parts => (parts :+ field).mkString(".")) + baseName.foreach(parts => registerNames += instruction.a -> (parts :+ field)) + requireRef.foreach(registerRequireRefs += instruction.a -> _) + if (requireRef.nonEmpty) { + val exportField = baseTarget.map(target => s"${target.fieldName}.$field").getOrElse(field) + registerFieldTargets += instruction.a -> RegisterFieldTarget( + exportField, + resolvedName, + s"${prototype.prototypeId}@pc${instruction.pc}:r${instruction.a}", + requireRef + ) + } + } + + case instruction if instruction.opcode == LuaOpcode.Call || instruction.opcode == LuaOpcode.TailCall => + val callsiteId = s"${prototype.prototypeId}@pc${instruction.pc}" + registerFieldTargets.get(instruction.a).foreach { target => + rows += FieldCall( + callsiteId, + target.fieldName, + target.resolvedName, + Some(target.targetRef), + target.requireRef, + callArgumentRefs(prototype, instruction), + callReturnRefs(instruction).headOption.map(ref => s"${prototype.prototypeId}$ref") + ) + } + val requireCall = requireCallsByCallsite.get(callsiteId).filter { call => + call.requireString.nonEmpty && callTargetNameAt(prototype, instruction.pc, instruction.a).contains("require") + } + clearCallWrites(instruction) + requireCall.foreach { call => + call.resultRef.foreach { ref => + registerWrite(ref) + .filter(_.slot == instruction.a) + .foreach { _ => + registerRequireRefs += instruction.a -> ref + call.requireString.foreach(name => registerNames += instruction.a -> Vector(name)) + } + } + } + + case instruction + if instruction.opcode == LuaOpcode.LoadK || instruction.opcode == LuaOpcode.LoadBool || + instruction.opcode == LuaOpcode.LoadNil || instruction.opcode == LuaOpcode.NewTable || + instruction.opcode == LuaOpcode.Closure || instruction.opcode == LuaOpcode.Vararg => + clearRegister(instruction.a) + + case instruction => + clearDefaultWrite(instruction) + } + + val statefulRows = rows.result() + val directRows = prototype.instructions + .filter(instruction => instruction.opcode == LuaOpcode.Call || instruction.opcode == LuaOpcode.TailCall) + .flatMap { instruction => + precedingGetTable(prototype, instruction.pc, instruction.a).flatMap { getTable => + fieldName(prototype.constants, getTable).map { field => + val baseName = callTargetNameAt(prototype, getTable.pc, getTable.b) + FieldCall( + callsiteId = s"${prototype.prototypeId}@pc${instruction.pc}", + fieldName = field, + resolvedName = baseName.map(name => s"$name.$field"), + targetRef = Some(s"${prototype.prototypeId}@pc${getTable.pc}:r${getTable.a}"), + requireRef = requireRefForGetTable(prototype, getTable, requireCalls, capturedRequireRefs), + argumentRefs = callArgumentRefs(prototype, instruction), + resultRef = callReturnRefs(instruction).headOption.map(ref => s"${prototype.prototypeId}$ref") + ) + } + } + } + (statefulRows ++ directRows).distinct + } + + private def requireRefForGetTable( + prototype: PrototypeSummary, + getTable: LuaInstruction, + requireCalls: Vector[RequireCall], + capturedRequireRefs: Map[String, String] + ): Option[String] = { + val localRegisterRefs = localRequireRefForGetTable(prototype, getTable, requireCalls).toVector + val capturedRefs = getUpvalueBefore(prototype, getTable.pc, getTable.b) + .flatMap(upvalueSlot => capturedRequireRefs.get(capturedRequireRefKey(prototype.prototypeId, upvalueSlot))) + .toVector + + (localRegisterRefs ++ capturedRefs).distinct match { + case Vector(single) => Some(single) + case _ => None + } + } + + private def localRequireRefForGetTable( + prototype: PrototypeSummary, + getTable: LuaInstruction, + requireCalls: Vector[RequireCall] + ): Option[String] = + requireCalls + .filter(_.prototypeId == prototype.prototypeId) + .flatMap(_.resultRef) + .flatMap(ref => registerWrite(ref).map(write => write -> ref)) + .filter { case (write, _) => write.slot == getTable.b && write.pc < getTable.pc } + .sortBy { case (write, _) => write.pc } + .lastOption + .map { case (_, ref) => ref } + + private def capturedRequireRefs(root: LuaPrototype, requireCalls: Vector[RequireCall]): Map[String, String] = { + def collect(prototype: LuaPrototype, inheritedUpvalueRefs: Map[Int, String]): Map[String, String] = { + val registerRequireRefs = scala.collection.mutable.Map.empty[Int, (Int, String)] + val captured = scala.collection.mutable.Map.empty[String, String] + val sorted = prototype.instructions.sortBy(_.pc) + val bindingPcs = closureBindingPcs(prototype) + + def clearSlot(slot: Int): Unit = + registerRequireRefs -= slot + + def newestRequireRef(slot: Int, pc: Int): Option[String] = + registerRequireRefs + .get(slot) + .filter(_._1 < pc) + .map(_._2) + + def boundLocalRequireRef(slot: Int, closurePc: Int): Option[String] = + requireCalls + .filter(call => call.prototypeId == prototype.prototypeId && call.requireString.nonEmpty) + .flatMap(call => call.resultRef.flatMap(ref => registerWrite(ref).map(write => (write, ref)))) + .filter { case (write, _) => + write.slot == slot && + write.pc < closurePc && + !hasRuntimeOverwrite(slot, write.pc, closurePc, sorted, bindingPcs) + } + .sortBy { case (write, _) => write.pc } + .lastOption + .map { case (_, ref) => ref } + + def childUpvalueRefs(closure: LuaInstruction): Map[Int, String] = { + val childPrototypeId = s"${prototype.prototypeId}.${closure.b}" + prototype.nested + .find(_.prototypeId == childPrototypeId) + .map { child => + sorted + .dropWhile(_.pc <= closure.pc) + .take(effectiveUpvalueCount(child)) + .zipWithIndex + .flatMap { + case (binder, upvalueSlot) if binder.opcode == LuaOpcode.Move => + boundLocalRequireRef(binder.b, closure.pc).map(upvalueSlot -> _) + case (binder, upvalueSlot) if binder.opcode == LuaOpcode.GetUpval => + inheritedUpvalueRefs.get(binder.b).map(upvalueSlot -> _) + case _ => None + } + .toMap + } + .getOrElse(Map.empty) + } + + sorted.foreach { + case instruction if bindingPcs(instruction.pc) => + case instruction if instruction.opcode == LuaOpcode.Call => + val callsiteId = s"${prototype.prototypeId}@pc${instruction.pc}" + val resolvedRequire = requireCalls + .find(call => + call.prototypeId == prototype.prototypeId && + call.callsiteId == callsiteId && + call.requireString.nonEmpty + ) + .flatMap(_.resultRef) + clearSlot(instruction.a) + resolvedRequire.foreach(ref => registerRequireRefs += instruction.a -> (instruction.pc -> ref)) + case instruction if instruction.opcode == LuaOpcode.GetUpval => + val inherited = inheritedUpvalueRefs.get(instruction.b) + clearSlot(instruction.a) + inherited.foreach(ref => registerRequireRefs += instruction.a -> (instruction.pc -> ref)) + case instruction if instruction.opcode == LuaOpcode.Move => + val moved = newestRequireRef(instruction.b, instruction.pc) + clearSlot(instruction.a) + moved.foreach(ref => registerRequireRefs += instruction.a -> (instruction.pc -> ref)) + case instruction if instruction.opcode == LuaOpcode.Closure => + clearSlot(instruction.a) + val childPrototypeId = s"${prototype.prototypeId}.${instruction.b}" + val childRefs = childUpvalueRefs(instruction) + childRefs.foreach { case (upvalueSlot, ref) => + captured += capturedRequireRefKey(childPrototypeId, upvalueSlot) -> ref + } + prototype.nested + .find(_.prototypeId == childPrototypeId) + .foreach(child => captured ++= collect(child, childRefs)) + case instruction + if instruction.opcode == LuaOpcode.LoadK || instruction.opcode == LuaOpcode.LoadBool || + instruction.opcode == LuaOpcode.LoadNil || instruction.opcode == LuaOpcode.GetGlobal || + instruction.opcode == LuaOpcode.GetTable || instruction.opcode == LuaOpcode.NewTable || + instruction.opcode == LuaOpcode.Self || instruction.opcode == LuaOpcode.Vararg || + instruction.opcode == LuaOpcode.TailCall => + clearSlot(instruction.a) + case _ => + } + + captured.toMap + } + + collect(root, Map.empty) + } + + private def hasRuntimeOverwrite( + slot: Int, + fromPc: Int, + toPc: Int, + sortedInstructions: Vector[LuaInstruction], + bindingPcs: Set[Int] + ): Boolean = + sortedInstructions.exists { instruction => + instruction.pc > fromPc && + instruction.pc < toPc && + !bindingPcs(instruction.pc) && + !(instruction.opcode == LuaOpcode.Move && instruction.a == slot && instruction.b == slot) && + overwritesSlot(instruction, slot) + } + + private def parameterFlowsToCallTarget(prototype: PrototypeSummary, parameterSlot: Int): Boolean = { + var aliases = Set(parameterSlot) + prototype.instructions.sortBy(_.pc).exists { instruction => + val isTarget = + (instruction.opcode == LuaOpcode.Call || instruction.opcode == LuaOpcode.TailCall) && aliases(instruction.a) + if (!isTarget) { + instruction.opcode match { + case LuaOpcode.Move => + val sourceIsAlias = aliases(instruction.b) + if (aliases(instruction.a)) { + aliases -= instruction.a + } + if (sourceIsAlias) { + aliases += instruction.a + } + case LuaOpcode.LoadK | LuaOpcode.LoadBool | LuaOpcode.LoadNil | LuaOpcode.GetUpval | LuaOpcode.GetGlobal | + LuaOpcode.GetTable | LuaOpcode.NewTable | LuaOpcode.Self | + LuaOpcode.Closure | LuaOpcode.Vararg | LuaOpcode.Call | LuaOpcode.TailCall => + aliases -= instruction.a + case _ => + } + } + isTarget + } + } + + private final case class RegisterWrite(pc: Int, slot: Int) + + private def registerWrite(ref: String): Option[RegisterWrite] = + ref.split("@pc", 2).lift(1).flatMap { afterPc => + afterPc.split(":r", 2).toList match { + case pcText :: slotText :: Nil => + pcText.toIntOption.zip(slotText.toIntOption).map((pc, slot) => RegisterWrite(pc, slot)) + case _ => None + } + } + + private def getUpvalueBefore(prototype: PrototypeSummary, pc: Int, slot: Int): Option[Int] = + prototype.instructions + .filter(instruction => instruction.pc < pc && instruction.a == slot && instruction.opcode == LuaOpcode.GetUpval) + .lastOption + .map(_.b) + + private def capturedRequireRefKey(prototypeId: String, upvalueSlot: Int): String = s"$prototypeId:u$upvalueSlot" + + private def capturedUpvalueNames(root: LuaPrototype): Map[String, Map[Int, String]] = { + def collect(prototype: LuaPrototype, currentUpvalueNames: Map[Int, String]): Map[String, Map[Int, String]] = { + val namesBySlot = scala.collection.mutable.Map.empty[Int, String] + val capturedByPrototype = scala.collection.mutable.Map.empty[String, Map[Int, String]] + val bindingPcs = closureBindingPcs(prototype) + def clearWrittenSlot(instruction: LuaInstruction): Unit = + namesBySlot -= instruction.a + def childUpvalueNames(closure: LuaInstruction): Map[Int, String] = { + val childPrototypeId = s"${prototype.prototypeId}.${closure.b}" + prototype.nested + .find(_.prototypeId == childPrototypeId) + .map { child => + prototype.instructions + .filter(_.pc > closure.pc) + .sortBy(_.pc) + .take(effectiveUpvalueCount(child)) + .zipWithIndex + .flatMap { + case (binder, upvalueSlot) if binder.opcode == LuaOpcode.Move => + namesBySlot.get(binder.b).map(name => upvalueSlot -> name) + case (binder, upvalueSlot) if binder.opcode == LuaOpcode.GetUpval => + currentUpvalueNames.get(binder.b).map(name => upvalueSlot -> name) + case _ => None + } + .toMap + } + .getOrElse(Map.empty) + } + + prototype.instructions.sortBy(_.pc).foreach { + case instruction if bindingPcs(instruction.pc) => + case instruction if instruction.opcode == LuaOpcode.GetGlobal => + clearWrittenSlot(instruction) + constantName(prototype.constants, instruction.b).foreach(name => namesBySlot += instruction.a -> name) + case instruction if instruction.opcode == LuaOpcode.GetUpval => + clearWrittenSlot(instruction) + currentUpvalueNames.get(instruction.b).foreach(name => namesBySlot += instruction.a -> name) + case instruction if instruction.opcode == LuaOpcode.GetTable => + val resolved = for { + base <- namesBySlot.get(instruction.b) + field <- fieldName(prototype.constants, instruction) + } yield s"$base.$field" + clearWrittenSlot(instruction) + resolved.foreach(name => namesBySlot += instruction.a -> name) + case instruction if instruction.opcode == LuaOpcode.Move => + val resolved = namesBySlot.get(instruction.b) + clearWrittenSlot(instruction) + resolved.foreach(name => namesBySlot += instruction.a -> name) + case instruction if instruction.opcode == LuaOpcode.Closure => + val childPrototypeId = s"${prototype.prototypeId}.${instruction.b}" + val childNames = childUpvalueNames(instruction) + capturedByPrototype += childPrototypeId -> childNames + prototype.nested + .find(_.prototypeId == childPrototypeId) + .foreach(child => capturedByPrototype ++= collect(child, childNames)) + case instruction if instruction.opcode == LuaOpcode.Call => + val resolved = for { + targetName <- namesBySlot.get(instruction.a) + if targetName == "require" + if instruction.c.forall(_ != 1) + moduleName <- precedingLoadString(prototype, instruction.pc, instruction.a + 1) + } yield moduleName + clearWrittenSlot(instruction) + resolved.foreach(name => namesBySlot += instruction.a -> name) + case instruction if instruction.opcode == LuaOpcode.LoadK => + clearWrittenSlot(instruction) + case instruction + if instruction.opcode == LuaOpcode.LoadBool || instruction.opcode == LuaOpcode.LoadNil || + instruction.opcode == LuaOpcode.NewTable || instruction.opcode == LuaOpcode.Vararg || + instruction.opcode == LuaOpcode.TailCall => + clearWrittenSlot(instruction) + case instruction + if instruction.opcode != LuaOpcode.SetGlobal && instruction.opcode != LuaOpcode.SetTable && + instruction.opcode != LuaOpcode.SetUpval && instruction.opcode != LuaOpcode.Return => + clearWrittenSlot(instruction) + case _ => + } + + capturedByPrototype.toMap + } + + collect(root, Map.empty) + } + + private def closureBindingPcs(prototype: LuaPrototype): Set[Int] = + prototype.instructions + .sortBy(_.pc) + .zipWithIndex + .flatMap { + case (closure, index) if closure.opcode == LuaOpcode.Closure => + prototype.nested.find(_.prototypeId == s"${prototype.prototypeId}.${closure.b}").toVector.flatMap { child => + (1 to effectiveUpvalueCount(child)) + .takeWhile { offset => + prototype.instructions + .sortBy(_.pc) + .lift(index + offset) + .exists(binding => + binding.pc == closure.pc + offset && + (binding.opcode == LuaOpcode.Move || binding.opcode == LuaOpcode.GetUpval) + ) + } + .flatMap(offset => prototype.instructions.sortBy(_.pc).lift(index + offset).map(_.pc)) + } + case _ => Vector.empty + } + .toSet + + private def effectiveUpvalueCount(prototype: LuaPrototype): Int = { + val usedUpvalueCount = prototype.instructions + .collect { + case instruction if instruction.opcode == LuaOpcode.GetUpval || instruction.opcode == LuaOpcode.SetUpval => + instruction.b + 1 + } + .maxOption + .getOrElse(0) + prototype.upvalueCount.max(usedUpvalueCount) + } + + private def returnRefs(prototype: LuaPrototype): Vector[String] = { + val explicitReturns = prototype.instructions + .filter(_.opcode == LuaOpcode.Return) + .flatMap { instruction => + val slots = instruction.b match { + case 0 => Vector.empty + case 1 => Vector.empty + case n => (instruction.a until (instruction.a + n - 1)).toVector + } + slots.map(slot => s"${prototype.prototypeId}@pc${instruction.pc}:r$slot") + } + val tailCallReturns = prototype.instructions + .filter(_.opcode == LuaOpcode.TailCall) + .map(instruction => s"${prototype.prototypeId}@pc${instruction.pc}:r${instruction.a}") + explicitReturns ++ tailCallReturns + } + + private def resolvedCalls( + prototype: LuaPrototype, + capturedNames: Map[Int, String] = Map.empty + ): Vector[ResolvedCall] = { + val summary = PrototypeSummary( + prototype.prototypeId, + prototype.numParams, + prototype.maxStack, + prototype.instructions, + prototype.constants, + Vector.empty, + Vector.empty, + Vector.empty, + capturedNames + ) + prototype.instructions + .filter(instruction => instruction.opcode == LuaOpcode.Call || instruction.opcode == LuaOpcode.TailCall) + .map(instruction => + ResolvedCall( + callsiteId = s"${prototype.prototypeId}@pc${instruction.pc}", + prototypeId = prototype.prototypeId, + pc = instruction.pc, + resolvedName = resolvedCallName(summary, instruction), + argumentRefs = callArgumentRefs(prototype, instruction), + returnRefs = callReturnRefs(instruction).map(ref => s"${prototype.prototypeId}$ref") + ) + ) + } + + private def resolvedCallName(prototype: PrototypeSummary, call: LuaInstruction): Option[String] = + callTargetNameAt(prototype, call.pc, call.a) + + private def callTargetNameAt(prototype: PrototypeSummary, pc: Int, slot: Int): Option[String] = + prototype.instructions + .filter(instruction => instruction.pc < pc && instruction.a == slot) + .sortBy(_.pc) + .lastOption + .flatMap { + case instruction if instruction.opcode == LuaOpcode.GetGlobal => + constantName(prototype.constants, instruction.b) + case instruction if instruction.opcode == LuaOpcode.GetUpval => + prototype.capturedNames.get(instruction.b) + case instruction if instruction.opcode == LuaOpcode.GetTable => + for { + baseName <- callTargetNameAt(prototype, instruction.pc, instruction.b) + field <- fieldName(prototype.constants, instruction) + } yield s"$baseName.$field" + case instruction if instruction.opcode == LuaOpcode.Call && instruction.c.forall(_ != 1) => + for { + targetName <- callTargetNameAt(prototype, instruction.pc, instruction.a) + if targetName == "require" + moduleName <- precedingLoadString(prototype, instruction.pc, instruction.a + 1) + } yield moduleName + case _ => None + } + + private def precedingGlobalLoadName(prototype: PrototypeSummary, pc: Int, slot: Int): Option[String] = + prototype.instructions + .filter(instruction => instruction.pc < pc && instruction.a == slot && instruction.opcode == LuaOpcode.GetGlobal) + .lastOption + .flatMap(instruction => constantName(prototype.constants, instruction.b)) + + private def isGlobalName(prototype: PrototypeSummary, slot: Int, name: String): Boolean = + prototype.instructions.exists(instruction => + instruction.opcode == LuaOpcode.GetGlobal && + instruction.a == slot && + constantName(prototype.constants, instruction.b).contains(name) + ) + + private def precedingLoadString(prototype: PrototypeSummary, pc: Int, slot: Int): Option[String] = + prototype.instructions + .filter(instruction => instruction.pc < pc && instruction.a == slot && instruction.opcode == LuaOpcode.LoadK) + .lastOption + .flatMap(instruction => constantName(prototype.constants, instruction.b)) + + private def precedingLoadString(prototype: LuaPrototype, pc: Int, slot: Int): Option[String] = + prototype.instructions + .filter(instruction => instruction.pc < pc && instruction.a == slot && instruction.opcode == LuaOpcode.LoadK) + .lastOption + .flatMap(instruction => constantName(prototype.constants, instruction.b)) + + private def precedingStringConstant(prototype: PrototypeSummary, pc: Int, slot: Int): Option[String] = + precedingLoadString(prototype, pc, slot) + + private def closureInSlotBefore(prototype: LuaPrototype, pc: Int, slot: Int): Option[String] = + prototype.instructions + .filter(instruction => instruction.pc < pc && instruction.a == slot && instruction.opcode == LuaOpcode.Closure) + .lastOption + .map(instruction => s"${prototype.prototypeId}.${instruction.b}") + + private def isReturnedTable(root: LuaPrototype, slot: Int): Boolean = + root.instructions.exists(instruction => + instruction.opcode == LuaOpcode.Return && instruction.a == slot && instruction.b == 2 + ) + + private def precedingGetTable(prototype: PrototypeSummary, pc: Int, slot: Int): Option[LuaInstruction] = + prototype.instructions + .filter(instruction => instruction.pc < pc && overwritesSlot(instruction, slot)) + .sortBy(_.pc) + .lastOption + .filter(_.opcode == LuaOpcode.GetTable) + + private def fieldName(constants: Vector[LuaConstant], instruction: LuaInstruction): Option[String] = + instruction.c.filter(_ >= RkConstantBase).flatMap(value => constantName(constants, value - RkConstantBase)) + + private def callArgumentRefs(prototype: LuaPrototype, instruction: LuaInstruction): Vector[String] = + callArgumentRefs(prototype.prototypeId, prototype.maxStack, instruction) + + private def callArgumentRefs(prototype: PrototypeSummary, instruction: LuaInstruction): Vector[String] = + callArgumentRefs(prototype.prototypeId, prototype.maxStack, instruction) + + private def callArgumentRefs(prototypeId: String, maxStack: Int, instruction: LuaInstruction): Vector[String] = { + val slots = instruction.b match { + case 0 => ((instruction.a + 1) until maxStack).toVector + case 1 => Vector.empty + case n => ((instruction.a + 1) until (instruction.a + n)).toVector + } + slots.map(slot => s"$prototypeId@pc${instruction.pc}:r$slot") + } + + private def callReturnRefs(instruction: LuaInstruction): Vector[String] = { + val slots = instruction.c match { + case Some(0) => Vector(instruction.a) + case Some(1) => Vector.empty + case Some(n) => (instruction.a until (instruction.a + n - 1)).toVector + case None => Vector.empty + } + slots.map(slot => s"@pc${instruction.pc}:r$slot") + } + + private def isConcreteStringArgument(prototype: PrototypeSummary, callPc: Int, argumentRef: String): Boolean = + registerWrite(argumentRef).exists(write => + prototype.instructions + .filter(instruction => instruction.pc < callPc && instruction.a == write.slot) + .sortBy(_.pc) + .lastOption + .exists(instruction => + instruction.opcode == LuaOpcode.LoadK && + constantName(prototype.constants, instruction.b).nonEmpty + ) + ) + + private def triggerMatches(pattern: String, name: String): Boolean = + pattern.split('.').lastOption.contains(finalSegment(name)) + + private def finalSegment(name: String): String = + name.split('.').lastOption match { + case Some(segment) if segment.nonEmpty => segment + case _ => name + } + + private def sameModuleRef(left: String, right: String): Boolean = + left.split(':').headOption.nonEmpty && left.split(':').headOption == right.split(':').headOption + + private final case class QualifiedValueRef(modulePath: String, prototypeId: String, pc: Int, localRef: String) + + private def parseQualifiedValueRef(ref: String): QualifiedValueRef = { + val splitAt = Vector(".luac:") + .flatMap { marker => + val index = ref.indexOf(marker) + if (index >= 0) Some(index + marker.length - 1) else None + } + .headOption + .getOrElse(throw new IllegalArgumentException(s"Lua qualified ref is missing module path: $ref")) + val modulePath = ref.substring(0, splitAt) + val localRef = ref.substring(splitAt + 1) + val prototypeId = localRef + .split("@pc", 2) + .headOption + .getOrElse(throw new IllegalArgumentException(s"Lua value ref is missing prototype id: $ref")) + val pc = localRef + .split("@pc", 2) + .lift(1) + .flatMap(_.split(":r", 2).headOption) + .flatMap(_.toIntOption) + .getOrElse(throw new IllegalArgumentException(s"Lua value ref is missing pc: $ref")) + QualifiedValueRef(modulePath, prototypeId, pc, localRef) + } + + private def slotFromValueRef(ref: String): Int = + localRefFromQualifiedRef(ref) + .split(":r", 2) + .lift(1) + .flatMap(_.toIntOption) + .getOrElse(throw new IllegalArgumentException(s"Lua value ref is missing slot: $ref")) + + private def modulePathFromQualifiedRef(ref: String): String = { + val splitAt = Vector(".luac:") + .flatMap { marker => + val index = ref.indexOf(marker) + if (index >= 0) Some(index + marker.length - 1) else None + } + .headOption + .getOrElse(throw new IllegalArgumentException(s"Lua qualified ref is missing module path: $ref")) + ref.substring(0, splitAt) + } + + private def callsiteIdFromValueRef(ref: String): String = + ref.split(":r", 2).headOption.getOrElse(ref) + + private def callsitePc(callsiteId: String): Option[Int] = + callsiteId.indexOf("@pc") match { + case -1 => None + case index => callsiteId.substring(index + 3).toIntOption + } + + private def requiredCallsitePc(callsiteId: String): Int = + callsitePc(callsiteId).getOrElse(throw new IllegalStateException(s"missing callsite pc: callsite_id=$callsiteId")) + + private def prototypeIdFromCallsiteId(ref: String): String = + ref.split("@pc", 2).headOption.getOrElse(ref) + + private def localRefFromQualifiedRef(ref: String): String = { + val splitAt = Vector(".luac:") + .flatMap { marker => + val index = ref.indexOf(marker) + if (index >= 0) Some(index + marker.length - 1) else None + } + .headOption + .getOrElse(throw new IllegalArgumentException(s"Lua qualified ref is missing module path: $ref")) + ref.substring(splitAt + 1) + } + + private def constantName(constants: Vector[LuaConstant], index: Int): Option[String] = + constants.collectFirst { case LuaConstant(`index`, "string", LuaConstantValue.StringValue(value)) => + value + } + + private def modulePathForRequire(requireString: String): String = { + val parts = requireString.split('.').filter(_.nonEmpty) + if (parts.isEmpty) throw new IllegalArgumentException("empty Lua require string") + s"${parts.mkString("/")}.luac" + } + + private def moduleNamesForPath(modulePath: String): Set[String] = { + val path = modulePath.stripSuffix(".luac").stripSuffix(".lua") + val parts = path.split('/').filter(_.nonEmpty).toVector + val allSuffixNames = parts.indices.map(index => parts.drop(index).mkString(".")).filter(_.nonEmpty).toSet + val bareName = parts.lastOption.toSet + allSuffixNames ++ bareName + } + + private def modulePathDeclaresRequire(modulePath: String, requireString: String): Boolean = + moduleNamesForPath(modulePath).contains(requireString) + + private def unresolvedReason(resolution: LuaModuleResolution): String = + resolution.unresolvedReason match { + case Some(reason) => reason + case None => + throw new IllegalStateException( + s"non-matched Lua module resolution without unresolved reason at ${resolution.fromModulePath}:${resolution.requireCallsiteId}" + ) + } +} diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/SAMPLE-MANIFEST.md b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/SAMPLE-MANIFEST.md new file mode 100644 index 000000000000..83437ba5fa93 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/SAMPLE-MANIFEST.md @@ -0,0 +1,22 @@ +# E4 Interprocedural Module Taint Samples + +Source family: referenceAnalyzer shared E4 fixtures derived from OpenWrtDerived LuCI behavior. + +Positive samples: +- d16-rf-interprocedural-formvalue-execute/input.luac: resolved same-artifact arg/return. +- d16-rf-webcmd-cross-module-popen/controller.luac and mtkwifi.luac: require/module/export/cross-module path. +- d24-module-return-table-field-call/controller.luac and library.luac: returned table field call target. +- bc-taint-minimal-path/input.luac: minimal same-artifact taint path. + +Negative samples: +- d24-interproc-unresolved-callee-negative/input.luac: unresolved callee boundary. +- d24-module-ambiguous-unresolved-dynamic-negative/*.luac: missing, ambiguous, and dynamic require boundaries. +- d24-module-missing-field-negative/*.luac: missing export field boundary. +- bc-kill-overwrite/input.luac and bc-branch-negative/input.luac: killed/no-flow taint boundaries. + +Reviewer command: +JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.InterproceduralModuleTaintSmokeTest' +JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/stage' +git status --short + +This subset is sufficient for reviewer smoke of E4 semantics. Full closure still depends on referenceAnalyzer controller evidence and phase-subset performance records. diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/bc-branch-negative/input.luac b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/bc-branch-negative/input.luac new file mode 100644 index 0000000000000000000000000000000000000000..29c4dfe7ad77757ed7b4a4b10c241ee55a80616f GIT binary patch literal 504 zcmZ8d%T5C^40M)I3M!9NITZE8wcTF`cP@NFx^?NQRVr<=sK9HQul5&{9KKOdm9z)-;uUE}t)mhhU7mhz|Xur7jhrVlgx$kZ10aT+$ zsjrf2Llgy1G15iw*%&oFi=-kM71hfdscHPn=Rzy->T4+oZ#PVOlnEWemb+vq0x3CM zWl{mb=K{D0XZcZ%U|Sb>od}GWf-KER9}0%wfGDcq87_o`fQBc*gCXFEW2#UP*aL2d z>$c>b;H2ce;O~Tg$R{~#$@B`ryy`UULFmtRTDMEnA^!g<`k@cYYxZniYK4mU2Et4+ A8vpE~le%-N4Ge`C0-KtH zp4^<1C5WI5HEAFu02)?84K4%+9>GHa@Ip*S6>`{`ZS!ZThGmMM6^3GQECN{FUBfL9 O(`VeI@rrU*(i^|Yh9{N) literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/bc-taint-minimal-path/input.luac b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/bc-taint-minimal-path/input.luac new file mode 100644 index 0000000000000000000000000000000000000000..71021c22082e4152557d773429eff344edeacbac GIT binary patch literal 398 zcmYjNOAdlC6nsTc^a>`f+#>M?9Kel7s8C}gprNf{$w54f2k|hzX=&ml?>C+INw+5_ z&m_`3&FM%1l~u-?@~TJc24%`iU0A7mTXeeDootJzw71ec+)%4S>*U;qkYqXgJzG-| zO&s6TL?2{=#J3``^DHa*Ekbaz2t|U3ddC4;NKlC4veat0Ro E0g#0zMgRZ+ literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d16-rf-interprocedural-formvalue-execute/input.luac b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d16-rf-interprocedural-formvalue-execute/input.luac new file mode 100644 index 0000000000000000000000000000000000000000..dba27679d25ad4ee70027b1c04c27dd67d9719f0 GIT binary patch literal 917 zcmb7COHRWu5S^sym;ZNz=!$Kq*swMdut01$08~-pib_dSIX3Dta1IWWgK!w;IZi1E zDlyXY8;{2`FXNoto9KbOpcnM$nrahT7P=TsrVDM9EJn%2WvC|Mbf%@6t2~y8QBfLB za+SSAsga>v$k=E(DsrP@d7hfcRRa0#LtY1h=#IHB%CgJcW^{z0#K!0q=Lw@P=&f53 zot3<2W}^49qzax#-iIJ>$Ug1|SYWA%r(e5gt>+XB>LXgJq@d+Oc|rWOwK?e&xIGAY zwzNmS&;F1*qi17?As;!OVEG4m>L hr)jKYl#E?>8^py;227dR@(d%yS#$B5;$3&hM4x*2VkrOs literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d16-rf-webcmd-cross-module-popen/controller.luac b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d16-rf-webcmd-cross-module-popen/controller.luac new file mode 100644 index 0000000000000000000000000000000000000000..adc8690d65c87ae8fcd4cded53c6cfcc0483c67d GIT binary patch literal 721 zcmb7B$xg#C6ntsZLdzmJgZc-dlmlm1Vv!IR?m!e0M?_8H;@Gsu@Mq=Ec;h6Zs<<%H zc;bouHlID(=!rak-`^)e4TO=EsitYRHdf1On#`^PodzrU78gknYgJW2p%R4LM(Roc?BG_cy3y*G@ z&!VOY)ih+!$3-BwarV1=H)cuxxcNn?(#2<#TS?qe11&#nrnij3ELK^{Ay5s_v_`#V z=hi;2E}zF;@b`qsLm?gD`AnSgT4kX!DkRjI?xS5stcQ)?KVZd0Okr3Hns3N;f5YA# zeY-R-9O9MtNQ$%n?HE88e-Rzz3be~qMTh-NS&>v-6sj{!O OfS5VH$vW&3R)8PO05#bF literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-interproc-unresolved-callee-negative/input.luac b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-interproc-unresolved-callee-negative/input.luac new file mode 100644 index 0000000000000000000000000000000000000000..42f9fca2d6dc92e38b4f297167f8116e28f99334 GIT binary patch literal 783 zcmZuvOHRWu6r7}?E&l^hx?&qDAr6p8z={nA04Z|ZHX==`#HqUUMPS82dJqo78>gWa zbfn39&-VL_prc(MgZ<1{#8h>yu-y2~X0bdr_0Qk1zc<>%QzA+mx z$Ud1%>D{E$LISx};s54bCHNGWz%f~XF?sQamlE%j`R?w-wy(^oJd?1~RcFP!8;FY( YN2FQ3OpKblrrdCf#qM6QE5I${2mcXLs{jB1 literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-ambiguous-unresolved-dynamic-negative/ambiguous.luac b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-ambiguous-unresolved-dynamic-negative/ambiguous.luac new file mode 100644 index 0000000000000000000000000000000000000000..ff52b914488f95cf0cc405a19541b8962ca07538 GIT binary patch literal 377 zcmYjLOK!q25S;`=v*H3&tk{}JRSyt0tSX@%pp^`sf~9LqSu`tERl5)%I4K${lo# zN7K3s=1u0xG+n(%;0POwX%ABiCl=;FGp}d_6tMMHFf|1 literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-ambiguous-unresolved-dynamic-negative/left.luac b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-ambiguous-unresolved-dynamic-negative/left.luac new file mode 100644 index 0000000000000000000000000000000000000000..263e8d23b5c8dbbe5460019d6a461aa5c41293d5 GIT binary patch literal 341 zcmZ8b!EM7Z45VYFNV`G+Z=f0|I)FdTqi7x(AO#eQ2?W@d5le>q^JpEdJf+Sf2T;c& zj}*6G(HtmK8?~WNx}gd@grPjUDa0!erG0zXt+%mZZQ7H&L>~toJ8Ai5WUK8@XIfY5 zjxQ#-k;{h9;kAk8e;;VITFd()VI`g`nhT<=pqVn!3Qd3~u2bbEWljPGSbq4^$OFZ)-tBGI*W8kI-MeqN=V&>|s&9`}+xjQC&L3=` z#*O|+W#?n`p}B9_Es|RDd?Wczum!R$Ch4S^b4uEq)NTr?7lMZhxSfzaP8ELtfLA$r zlXtyWuJ}$d&=>oGV6K;kvx{UMM%6UvLSaGKLWWl`v3p{ literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-ambiguous-unresolved-dynamic-negative/right.luac b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-ambiguous-unresolved-dynamic-negative/right.luac new file mode 100644 index 0000000000000000000000000000000000000000..05e642d270dc6d96c78b84bb2313875c61b67e9a GIT binary patch literal 342 zcmZ8bL2kk@5Zn;A0=`fwKX4;eJRldiKo5ODk=%GmEhTY8Yy>x+)~9vHAw6`Y_3Z4< zuJ`2-%?D*_sg|^-302@RjOEE)Lp<|X+P97Fyp1hu(|x%n`Z(&?OUt(xw%Xo$)45vr z+?e1lT%KL?9bVgL9t%N>#gn{i2`jOx=w1+I1>GqVtK)!R4!F8Ai}s$$xVR7Wplx5{207;B%!$w!S@Bo^iEu;y*P-=9?(L9>-in#D4(|K>^&2&~r zo1MuMp$KV5Bhp@(-Xytx_O??dk(+H?HPV(UuCy__F5+CPQpU9^vR*$_QtDfmb+2`4 zv+oWh-=FcjL7hZ7Xj3v$& sumE&J0%o9XT2(uS3LO7|_3j4eBDz=7G29CvPknp?J=LIr)6W6*0XtPBx&QzG literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-return-table-field-call/controller.luac b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-return-table-field-call/controller.luac new file mode 100644 index 0000000000000000000000000000000000000000..448a3e104287134d8d5155657b549cf4e5593348 GIT binary patch literal 844 zcmbtSOHRWu5PfMwDgOh2Sg{S25C?FP=!y+{K1vf;L~@$qI2B8?K`M8Ev!pj;CUHmv zs)WQy&-Ogq^Lh61O%dM0^N0QrSEx-GnVWnxOI}Q&Wj=~8E`v0S3nc?BiJAu{oRTw3 zq>6(mRB9AubE7k*q&`u=L|Mu-LQZh;kGSHjv)z&nLSkM{gs8rYUe_^2`p z34gC3EgDe3v*rLCY`+fMyP=h7+8UE=>#J=8=)q44NG_KNvH>! zzlHjb2!9c|X{0L@-}{Lj;x>UB<)6{S;uqk$8&Oyc_W2xhEXX0@$bm)v{TDgnfUx09 H@)p|{kR56L literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-return-table-field-call/library.luac b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-return-table-field-call/library.luac new file mode 100644 index 0000000000000000000000000000000000000000..082909cf603a77acbb9969c1bbdf8e9525373b64 GIT binary patch literal 331 zcmZ8cK?=e!5Zup^m|!_~jM5|b`ikUMvo(38tu1VQb=o~wHt5e1 zku)9SoP!3OmDDeZk|p(&AXf^9z=cOF3>nb~fefNJnm~5ozITY5bjS+qCMa5(Kq0ES};P|uQ4IzFd$^ZZW literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/InterproceduralModuleTaintSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/InterproceduralModuleTaintSmokeTest.scala new file mode 100644 index 000000000000..e346394cbb0c --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/InterproceduralModuleTaintSmokeTest.scala @@ -0,0 +1,100 @@ +package io.joern.lua2cpg + +import io.shiftleft.codepropertygraph.cpgloading.CpgLoader +import io.shiftleft.codepropertygraph.generated.EdgeTypes +import io.shiftleft.semanticcpg.language.* +import io.shiftleft.semanticcpg.utils.FileUtil +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec + +import java.nio.file.Paths + +class InterproceduralModuleTaintSmokeTest extends AnyWordSpec with Matchers { + + "Lua2Cpg" should { + "emit interprocedural module and taint markers that survive CPG reopen" in { + val resourceRoot = Paths.get(getClass.getClassLoader.getResource("interprocedural-module-taint").toURI) + + FileUtil.usingTemporaryDirectory("lua2cpg-interprocedural-module-taint-smoke") { tmpDir => + val outputPath = tmpDir.resolve("interprocedural-module-taint.cpg.bin").toString + val cpg = new Lua2Cpg() + .createCpg(Config().withInputPath(resourceRoot.toString).withOutputPath(outputPath)) + .get + cpg.close() + + val reopened = CpgLoader.load(outputPath) + try { + markerCodes(reopened, "lua.interproc.arg_flow") should contain( + "d16-rf-interprocedural-formvalue-execute/input.luac:root@pc18:r4 -> d16-rf-interprocedural-formvalue-execute/input.luac::root.3:r0" + ) + markerCodes(reopened, "lua.interproc.return_flow") should contain( + "d16-rf-interprocedural-formvalue-execute/input.luac::root.2@pc4:r0 -> d16-rf-interprocedural-formvalue-execute/input.luac:root@pc15:r2" + ) + markerCodes(reopened, "lua.module.resolution") should contain( + "d16-rf-webcmd-cross-module-popen/controller.luac require mtkwifi -> matched:d16-rf-webcmd-cross-module-popen/mtkwifi.luac" + ) + markerCodes(reopened, "lua.module.return_table") should contain( + "d24-module-return-table-field-call/library.luac::run -> root.0" + ) + markerCodes(reopened, "lua.module.field_call_target") should contain allOf ( + "d24-module-return-table-field-call/controller.luac:root.0@pc10 -> d24-module-return-table-field-call/library.luac::root.0", + "d24-module-return-table-field-call/controller.luac:root.1@pc10 -> d24-module-return-table-field-call/library.luac::root.0" + ) + markerCodes(reopened, "lua.calltarget.cross_boundary") should contain( + "d16-rf-webcmd-cross-module-popen/controller.luac:root.1@pc8 -> d16-rf-webcmd-cross-module-popen/mtkwifi.luac::root.1" + ) + markerCodes(reopened, "lua.taint.path") should contain( + "bc-taint-minimal-path/input.luac:root@pc3:r2 -> bc-taint-minimal-path/input.luac:root@pc6:r4 via bc-taint-minimal-path/input.luac:root@pc3:r2;bc-taint-minimal-path/input.luac:root@pc5:r4;bc-taint-minimal-path/input.luac:root@pc6:r4" + ) + + val unresolvedArgFlows = markerCodes(reopened, "lua.interproc.arg_flow") + .filter(_.contains("d24-interproc-unresolved-callee-negative")) + withClue(s"unresolved arg flows: ${unresolvedArgFlows.mkString(", ")}") { + unresolvedArgFlows.exists(_.contains("root@pc8")) shouldBe false + } + val unresolvedReturnFlows = markerCodes(reopened, "lua.interproc.return_flow") + .filter(_.contains("d24-interproc-unresolved-callee-negative")) + withClue(s"unresolved return flows: ${unresolvedReturnFlows.mkString(", ")}") { + unresolvedReturnFlows.exists(_.contains("root@pc8")) shouldBe false + } + markerCodes(reopened, "lua.module.resolution") + .exists(code => + code.contains("d24-module-ambiguous-unresolved-dynamic-negative") && code.contains("-> matched:") + ) shouldBe false + markerCodes(reopened, "lua.calltarget.cross_boundary") + .exists(_.contains("d24-module-missing-field-negative")) shouldBe false + markerCodes(reopened, "lua.taint.path") + .exists(code => code.contains("bc-kill-overwrite") || code.contains("bc-branch-negative")) shouldBe false + + val boundaryCodes = markerCodes(reopened, "lua.e4.boundary") + boundaryCodes should contain allOf ( + "d24-interproc-unresolved-callee-negative/input.luac:root@pc8 reason=unresolved-callee", + "d24-module-ambiguous-unresolved-dynamic-negative/missing.luac:require:missing.module reason=unresolved-module", + "d24-module-ambiguous-unresolved-dynamic-negative/ambiguous.luac:require:shared.module reason=ambiguous-module", + "d24-module-ambiguous-unresolved-dynamic-negative/controller.luac:require:dynamic reason=dynamic-require", + "d24-module-missing-field-negative/controller.luac:root.0@pc3 reason=missing-export-field", + "bc-kill-overwrite/input.luac:root@pc3:r2->root@pc7:r4 reason=killed-taint-path", + "bc-branch-negative/input.luac:root@pc3:r2->root@pc8:r5 reason=branch-negative-taint-path" + ) + + val e4NodeCount = reopened.call + .name("lua\\.(module\\.resolution|module\\.return_table|module\\.field_call_target|interproc\\.arg_flow|interproc\\.return_flow|calltarget\\.cross_boundary|taint\\.path|e4\\.boundary)") + .size + val e4ReachingDefEdgeCount = reopened.identifier.outE(EdgeTypes.REACHING_DEF).size + val e4TaintPathCount = reopened.call.nameExact("lua.taint.path").size + info(s"e4_node_count=$e4NodeCount") + info(s"e4_reaching_def_edge_count=$e4ReachingDefEdgeCount") + info(s"e4_taint_path_count=$e4TaintPathCount") + e4NodeCount should be > 0 + e4ReachingDefEdgeCount should be > 0 + e4TaintPathCount should be > 0 + } finally { + reopened.close() + } + } + } + } + + private def markerCodes(cpg: io.shiftleft.codepropertygraph.generated.Cpg, name: String): List[String] = + cpg.call.nameExact(name).code.l +} From 754c2268633dabe90865787b4bbaa32e7fba2a23 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 8 Jul 2026 14:25:41 -0400 Subject: [PATCH 014/105] docs(lua2cpg): document interprocedural module taint smoke --- joern-cli/frontends/lua2cpg/README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/README.md b/joern-cli/frontends/lua2cpg/README.md index dff4406223d0..12d22a5722f1 100644 --- a/joern-cli/frontends/lua2cpg/README.md +++ b/joern-cli/frontends/lua2cpg/README.md @@ -67,3 +67,27 @@ committed focused `.luac` fixtures. It does not claim interprocedural arg/return, module require/export resolution, source parser AST semantics, QueryDB, sanitizer classification, report construction, schema extension acceptance, distribution acceptance, or official frontend acceptance. + +## Interprocedural Module Taint Smoke + +```bash +JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' \ + sbt 'lua2cpg/testOnly io.joern.lua2cpg.InterproceduralModuleTaintSmokeTest' +JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' \ + sbt 'lua2cpg/stage' +git status --short +``` + +Expected result: + +- `InterproceduralModuleTaintSmokeTest` succeeds. +- `lua2cpg/stage` succeeds. +- `git status --short` is clean after the smoke. + +This E4 smoke proves interprocedural arg/return, literal require resolution, +module returned-table exports, cross-boundary call targets, and explainable +taint paths over committed fixtures. + +It does not claim QueryDB readiness, source parser AST semantics, sanitizer +classification, report construction, schema extension acceptance, distribution +acceptance, or official frontend acceptance. From 0d8ff322076c5551c38e9dd34e00d624f003dbcd Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 8 Jul 2026 14:33:36 -0400 Subject: [PATCH 015/105] refactor(lua2cpg): require explicit E4 resolution states --- .../io/joern/lua2cpg/passes/LuaBytecodeModelPass.scala | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/passes/LuaBytecodeModelPass.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/passes/LuaBytecodeModelPass.scala index 7a8ca03f33db..3e8911e921d3 100644 --- a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/passes/LuaBytecodeModelPass.scala +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/passes/LuaBytecodeModelPass.scala @@ -220,7 +220,14 @@ class LuaBytecodeModelPass( .sortBy(_.requireCallsiteId) .zipWithIndex .map { case (resolution, index) => - val target = resolution.targetModulePath.getOrElse(resolution.unresolvedReason.getOrElse("unresolved")) + val target = resolution match { + case LuaModuleResolution(_, _, "matched", _, Some(targetModulePath), None, _) => targetModulePath + case LuaModuleResolution(_, _, _, _, None, Some(unresolvedReason), _) => unresolvedReason + case _ => + throw new IllegalStateException( + s"inconsistent Lua module resolution state at ${resolution.fromModulePath}:${resolution.requireCallsiteId}" + ) + } semanticCallNode( name = "lua.module.resolution", code = From 9220a1e7418cb2db5b0aed7518483bbbbf0293e6 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 8 Jul 2026 16:42:27 -0400 Subject: [PATCH 016/105] feat(lua2cpg): add rules sanitizer report markers --- .../lua2cpg/passes/LuaBytecodeModelPass.scala | 97 +++++++++++++++++- .../bc-branch-negative/input.luac | Bin 0 -> 504 bytes .../bc-kill-overwrite/input.luac | Bin 0 -> 417 bytes .../bc-taint-minimal-path/input.luac | Bin 0 -> 398 bytes .../input.luac | Bin 0 -> 572 bytes .../d16-rf-submit-dpp-uri-execute/input.luac | Bin 0 -> 751 bytes .../controller.luac | Bin 0 -> 721 bytes .../mtkwifi.luac | Bin 0 -> 488 bytes .../input.luac | Bin 0 -> 621 bytes .../input.luac | Bin 0 -> 393 bytes .../input.luac | Bin 0 -> 762 bytes .../input.luac | Bin 0 -> 730 bytes .../RulesSanitizerReportSmokeTest.scala | 96 +++++++++++++++++ 13 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/bc-branch-negative/input.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/bc-kill-overwrite/input.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/bc-taint-minimal-path/input.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/d16-rf-formvalue-os-execute-chain/input.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/d16-rf-submit-dpp-uri-execute/input.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/d16-rf-webcmd-cross-module-popen/controller.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/d16-rf-webcmd-cross-module-popen/mtkwifi.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/d24-report-no-report-without-path-negative/input.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/d24-rules-overmatch-constant-sink-negative/input.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/d24-sanitizer-same-suffix-off-chain-negative/input.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/d24-sanitizer-suppresses-report/input.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RulesSanitizerReportSmokeTest.scala diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/passes/LuaBytecodeModelPass.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/passes/LuaBytecodeModelPass.scala index 3e8911e921d3..8a26a5fc203f 100644 --- a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/passes/LuaBytecodeModelPass.scala +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/passes/LuaBytecodeModelPass.scala @@ -257,7 +257,18 @@ class LuaBytecodeModelPass( order = 52_000 + index ) } - moduleResolutions ++ returnTables ++ boundaries + val e5Boundaries = semantics.e5Boundaries + .filter(_.boundaryId.startsWith(relativeName)) + .sortBy(_.boundaryId) + .zipWithIndex + .map { case (boundary, index) => + semanticCallNode( + name = "lua.e5.boundary", + code = s"${boundary.boundaryId} reason=${boundary.reason}", + order = 52_500 + index + ) + } + moduleResolutions ++ returnTables ++ boundaries ++ e5Boundaries } else Vector.empty val fieldCalls = semantics.moduleFieldCallTargets @@ -321,8 +332,90 @@ class LuaBytecodeModelPass( order = 57_000 + index ) } + val ruleMatches = semantics.ruleMatches + .filter(row => row.callsiteId.startsWith(prefix)) + .sortBy(row => (row.ruleKind, row.callsiteId, row.matchedName)) + .zipWithIndex + .map { case (row, index) => + semanticCallNode( + name = "lua.rule.match", + code = s"${row.callsiteId} ${row.trigger} -> ${row.matchedName}", + order = 58_000 + index + ) + } + val sourceEndpoints = semantics.sourceEndpoints + .filter(row => row.sourceRef.startsWith(s"$relativeName:${prototype.prototypeId}@")) + .sortBy(row => (row.sourceRef, row.trigger)) + .zipWithIndex + .map { case (row, index) => + semanticCallNode( + name = "lua.source.endpoint", + code = s"${row.sourceRef} via ${row.trigger}", + order = 59_000 + index + ) + } + val sinkEndpoints = semantics.sinkEndpoints + .filter(row => row.sinkRef.startsWith(s"$relativeName:${prototype.prototypeId}@")) + .sortBy(row => (row.sinkRef, row.trigger)) + .zipWithIndex + .map { case (row, index) => + semanticCallNode( + name = "lua.sink.endpoint", + code = s"${row.sinkRef} via ${row.trigger} param=${row.parameterIndex}", + order = 60_000 + index + ) + } + val sanitizerCalls = semantics.sanitizerCalls + .filter(row => row.sanitizedValueRef.startsWith(s"$relativeName:${prototype.prototypeId}@")) + .sortBy(row => (row.callsiteId, row.sanitizerName)) + .zipWithIndex + .map { case (row, index) => + semanticCallNode( + name = "lua.sanitizer.call", + code = s"${row.callsiteId} ${row.sanitizerName} -> ${row.sanitizedValueRef}", + order = 61_000 + index + ) + } + val sanitizerClassifications = semantics.sanitizerClassifications + .filter(row => row.sourceRef.startsWith(s"$relativeName:${prototype.prototypeId}@")) + .sortBy(row => (row.sourceRef, row.sinkRef, row.sanitizerCallsiteId)) + .zipWithIndex + .map { case (row, index) => + semanticCallNode( + name = "lua.sanitizer.classification", + code = + s"${row.sourceRef} -> ${row.sinkRef} classification=${row.classification} sanitizer=${row.sanitizerName}", + order = 62_000 + index + ) + } + val reportClassifications = semantics.reportClassifications + .filter(row => row.sourceRef.startsWith(s"$relativeName:${prototype.prototypeId}@")) + .sortBy(row => (row.sourceRef, row.sinkRef)) + .zipWithIndex + .map { case (row, index) => + semanticCallNode( + name = "lua.report.classification", + code = s"${row.sourceRef} -> ${row.sinkRef} classification=${row.classification} reason=${row.reason}", + order = 63_000 + index + ) + } + val vulnerabilityReports = semantics.vulnerabilityReports + .filter(row => row.sourceRef.startsWith(s"$relativeName:${prototype.prototypeId}@")) + .sortBy(row => (row.sourceRef, row.sinkRef)) + .zipWithIndex + .map { case (row, index) => + semanticCallNode( + name = "lua.report.vulnerability", + code = + s"${row.sourceRef} -> ${row.sinkRef} status=${row.pathStatus} classification=${row.classification} path=${row.pathSteps + .mkString(";")}", + order = 64_000 + index + ) + } - rootMarkers ++ fieldCalls ++ argFlows ++ returnFlows ++ crossTargets ++ taintPaths + rootMarkers ++ fieldCalls ++ argFlows ++ returnFlows ++ crossTargets ++ taintPaths ++ ruleMatches ++ + sourceEndpoints ++ sinkEndpoints ++ sanitizerCalls ++ sanitizerClassifications ++ reportClassifications ++ + vulnerabilityReports } private def semanticCallNode(name: String, code: String, order: Int): NewCall = diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/bc-branch-negative/input.luac b/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/bc-branch-negative/input.luac new file mode 100644 index 0000000000000000000000000000000000000000..29c4dfe7ad77757ed7b4a4b10c241ee55a80616f GIT binary patch literal 504 zcmZ8d%T5C^40M)I3M!9NITZE8wcTF`cP@NFx^?NQRVr<=sK9HQul5&{9KKOdm9z)-;uUE}t)mhhU7mhz|Xur7jhrVlgx$kZ10aT+$ zsjrf2Llgy1G15iw*%&oFi=-kM71hfdscHPn=Rzy->T4+oZ#PVOlnEWemb+vq0x3CM zWl{mb=K{D0XZcZ%U|Sb>od}GWf-KER9}0%wfGDcq87_o`fQBc*gCXFEW2#UP*aL2d z>$c>b;H2ce;O~Tg$R{~#$@B`ryy`UULFmtRTDMEnA^!g<`k@cYYxZniYK4mU2Et4+ A8vpE~le%-N4Ge`C0-KtH zp4^<1C5WI5HEAFu02)?84K4%+9>GHa@Ip*S6>`{`ZS!ZThGmMM6^3GQECN{FUBfL9 O(`VeI@rrU*(i^|Yh9{N) literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/bc-taint-minimal-path/input.luac b/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/bc-taint-minimal-path/input.luac new file mode 100644 index 0000000000000000000000000000000000000000..71021c22082e4152557d773429eff344edeacbac GIT binary patch literal 398 zcmYjNOAdlC6nsTc^a>`f+#>M?9Kel7s8C}gprNf{$w54f2k|hzX=&ml?>C+INw+5_ z&m_`3&FM%1l~u-?@~TJc24%`iU0A7mTXeeDootJzw71ec+)%4S>*U;qkYqXgJzG-| zO&s6TL?2{=#J3``^DHa*Ekbaz2t|U3ddC4;NKlC4veat0Ro E0g#0zMgRZ+ literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/d16-rf-formvalue-os-execute-chain/input.luac b/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/d16-rf-formvalue-os-execute-chain/input.luac new file mode 100644 index 0000000000000000000000000000000000000000..ec523dfb20b49c7debc88862241fa668ecca3bef GIT binary patch literal 572 zcmaJ-K~BRk5S+B8ZHhqR02fey5F#$zkjw!Ns2A=?C~_RNQj?&>Mm+{z20vpbF|!c}?qHfC9Y~yWGw~5_ab?%Z<;11!*)slXb3v2jN(E-BkEMFwq)$ui=gLTo7x#Pd|3`NvCnEBV2Td z?nJ-GCprL68qq?siR6lAjEy(z-EQUF3)Fll;b&~9aggd3^4uZ9L!eX;OqpX8auN^q z_e2;`Utk!3H3l`FWlt1X2vt^EaxeBtSa@?k+&_kK=%L~MV6>pd;O{cu2Z#M0n9LvN hUBsc_=nou=_8atMFT>PjnN|6-vs;G~5l(v<_yY78JTd?P literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/d16-rf-submit-dpp-uri-execute/input.luac b/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/d16-rf-submit-dpp-uri-execute/input.luac new file mode 100644 index 0000000000000000000000000000000000000000..b71721903e60f076ba936f5f74ac2af17c5d0096 GIT binary patch literal 751 zcmbVKJx{|h5PfOW^aJs^Qgvbt6$=uZ8DKyx>_}BeToI|ERbr!#hF@0x8TXt-L zHF{pHZJFxJl}(!CbiLlGj1vVsh#xEGhT;c;NGW7KhYzl2g0aHqa0gsCV5kBN1XJZO zgF^%$v$vk*CG&v2Eo zzcLUZ63>8uTv}r;6mU<_cyV}33Qg~mK4=D#@I3bv95;Jdki}_fk$KNt*A8WJQYZSX zOCoG+

gqz|4LM(Roc?BG_cy3y*G@ z&!VOY)ih+!$3-BwarV1=H)cuxxcNn?(#2<#TS?qe11&#nrnij3ELK^{Ay5s_v_`#V z=hi;2E}zF;@b`qsLm?gD`AnSgT4kX!DkRjI?xS5stcQ)?KVZd0Okr3Hns3N;f5YA# zeY-R-9O9MtNQ$%n?HE88e-Rzz3be~qMTh-NS&>v-6sj{!O OfS5VH$vW&3R)8PO05#bF literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/d24-report-no-report-without-path-negative/input.luac b/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/d24-report-no-report-without-path-negative/input.luac new file mode 100644 index 0000000000000000000000000000000000000000..947715cff58c2263dd64869e0b9fa3e95d9e85b1 GIT binary patch literal 621 zcmaJ-OHRWu5PdGDKLiLCumJT2p%UT%?uG^Gh9g*sOCzO8m5fW50SQjQVR|%X5=Sjk z#YoR@=8g3{p1ZlXuTXlZhj>OChFnF}bdhe!m#&&7k55tQ)8Kjx@AHz|& zBZh}_iiKzrvT(@{A;We93(L~vEx{S%{0J^syF3EB$)zw5dVDieMI Dy=g=W literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/d24-rules-overmatch-constant-sink-negative/input.luac b/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/d24-rules-overmatch-constant-sink-negative/input.luac new file mode 100644 index 0000000000000000000000000000000000000000..e5b9a5fbff4e5532b9ce0b28f2cb82c1ddae432b GIT binary patch literal 393 zcmZutK~BRk5L~yW3PSJ!5I=AuAs%23^u&cb7c1GeRudc9>yTskS^G1*sSyY<(%9Y^ z@2=OkFUh=ukd>@p17~Q$NU`49ky5}|w-1jhqz+Ld-)E=Fp5$ak zmm2#M?TOw&UY#x2`p^%iOE`j*Mf)%gBv*w)2-TN}dY+j#&TP)`#s;?#UeD{{617O9w9(@@nHyUwJ&sP#d>!P8NuE?mR;GMyV+#94 z9Q)xkNOC_{4}nQ$YOD)ehUz4>L6ePDr#qx`Ac1Sred*i==`LYiN<VyPnyx0^ehadqEXxtD4qM@fba*jAD_t4o!5!Hq7pyWeB0bjw# zuyYR)A(TChcRjOSd+j^9bHP14zvFjsiOQ7K#+u70k`72swg(Zw*~7;AuKt}PnGC`_X4;n@Or%f zt^kbG0;6Jqx$^K(z!Mn@6nK;PQ*yrW;X%G{1+Opsy68TO)2415{m!1O&EWSc36nIB zpM$B>@YiZ8HIU{j(=$DGRz$xh#&L)ykrd-{9}lgT#C=o;phhFdwaoEqFAhl%YxetbQ!NG$kQ>01=%UBYJw5jW!sX;4v7^Nx9c3PU2e5n#u4EAQC|kzZ?1rzr`i;7 zKVny~_Yd}kYj7a(Uo^Euf=v?_KkB?Jek4TmI5vn9tHZJo`Q}ZVe>23PuvBP2#Y|02 literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RulesSanitizerReportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RulesSanitizerReportSmokeTest.scala new file mode 100644 index 000000000000..22099386ad33 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RulesSanitizerReportSmokeTest.scala @@ -0,0 +1,96 @@ +package io.joern.lua2cpg + +import io.shiftleft.codepropertygraph.cpgloading.CpgLoader +import io.shiftleft.semanticcpg.language.* +import io.shiftleft.semanticcpg.utils.FileUtil +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec + +import java.nio.file.Paths + +class RulesSanitizerReportSmokeTest extends AnyWordSpec with Matchers { + + "Lua2Cpg" should { + "emit rules sanitizer and report markers that survive CPG reopen" in { + val resourceRoot = Paths.get(getClass.getClassLoader.getResource("rules-sanitizer-report").toURI) + + FileUtil.usingTemporaryDirectory("lua2cpg-rules-sanitizer-report-smoke") { tmpDir => + val outputPath = tmpDir.resolve("rules-sanitizer-report.cpg.bin").toString + val cpg = new Lua2Cpg() + .createCpg(Config().withInputPath(resourceRoot.toString).withOutputPath(outputPath)) + .get + cpg.close() + + val reopened = CpgLoader.load(outputPath) + try { + markerCodes(reopened, "lua.rule.match") should contain allOf ( + "d16-rf-formvalue-os-execute-chain/input.luac:root@pc16 formvalue -> luci.http.formvalue", + "d16-rf-formvalue-os-execute-chain/input.luac:root@pc20 execute -> os.execute", + "d16-rf-webcmd-cross-module-popen/mtkwifi.luac:root.1@pc3 popen -> io.popen" + ) + markerCodes(reopened, "lua.source.endpoint") should contain allOf ( + "d16-rf-formvalue-os-execute-chain/input.luac:root@pc16:r0 via luci.http.formvalue", + "d16-rf-webcmd-cross-module-popen/controller.luac:root.1@pc4:r0 via luci.http.formvalue" + ) + markerCodes(reopened, "lua.sink.endpoint") should contain allOf ( + "d16-rf-formvalue-os-execute-chain/input.luac:root@pc20:r2 via os.execute param=0", + "d16-rf-webcmd-cross-module-popen/mtkwifi.luac:root.1@pc3:r2 via io.popen param=0" + ) + + markerCodes(reopened, "lua.sanitizer.call") should contain allOf ( + "d24-sanitizer-suppresses-report/input.luac:root@pc20 tonumber -> d24-sanitizer-suppresses-report/input.luac:root@pc20:r2", + "d24-sanitizer-same-suffix-off-chain-negative/input.luac:root@pc21 tonumber -> d24-sanitizer-same-suffix-off-chain-negative/input.luac:root@pc21:r3" + ) + markerCodes(reopened, "lua.sanitizer.classification") should contain allOf ( + "d24-sanitizer-suppresses-report/input.luac:root@pc17:r1 -> d24-sanitizer-suppresses-report/input.luac:root@pc24:r4 classification=sanitized sanitizer=tonumber", + "d24-sanitizer-same-suffix-off-chain-negative/input.luac:root@pc17:r1 -> d24-sanitizer-same-suffix-off-chain-negative/input.luac:root@pc25:r4 classification=not-sanitized sanitizer=tonumber" + ) + markerCodes(reopened, "lua.report.classification") should contain allOf ( + "d24-sanitizer-suppresses-report/input.luac:root@pc17:r1 -> d24-sanitizer-suppresses-report/input.luac:root@pc24:r4 classification=sanitized reason=on-chain-sanitizer", + "d24-sanitizer-same-suffix-off-chain-negative/input.luac:root@pc17:r1 -> d24-sanitizer-same-suffix-off-chain-negative/input.luac:root@pc25:r4 classification=true-positive reason=no-on-chain-sanitizer" + ) + markerCodes(reopened, "lua.report.vulnerability") should contain allOf ( + "d16-rf-formvalue-os-execute-chain/input.luac:root@pc16:r0 -> d16-rf-formvalue-os-execute-chain/input.luac:root@pc20:r2 status=path-proven classification=true-positive path=d16-rf-formvalue-os-execute-chain/input.luac:root@pc16:r0;d16-rf-formvalue-os-execute-chain/input.luac:root@pc19:r2;d16-rf-formvalue-os-execute-chain/input.luac:root@pc20:r2", + "d16-rf-submit-dpp-uri-execute/input.luac:root.2@pc4:r0 -> d16-rf-submit-dpp-uri-execute/input.luac:root.2@pc8:r2 status=path-proven classification=true-positive path=d16-rf-submit-dpp-uri-execute/input.luac:root.2@pc4:r0;d16-rf-submit-dpp-uri-execute/input.luac:root.2@pc7:r2;d16-rf-submit-dpp-uri-execute/input.luac:root.2@pc8:r2", + "d16-rf-webcmd-cross-module-popen/controller.luac:root.1@pc4:r0 -> d16-rf-webcmd-cross-module-popen/mtkwifi.luac:root.1@pc3:r2 status=path-proven classification=true-positive path=d16-rf-webcmd-cross-module-popen/controller.luac:root.1@pc4:r0;d16-rf-webcmd-cross-module-popen/controller.luac:root.1@pc8:r2;d16-rf-webcmd-cross-module-popen/mtkwifi.luac:root.1@pc3:r2" + ) + markerCodes(reopened, "lua.e5.boundary") should contain allOf ( + "d24-rules-overmatch-constant-sink-negative/input.luac:root@pc4 reason=rule-overmatch-rejected", + "d24-rules-overmatch-constant-sink-negative/input.luac:root@pc8 reason=rule-overmatch-rejected", + "d24-rules-overmatch-constant-sink-negative/input.luac:root@pc12 reason=fixed-string-sink-suppressed", + "d24-report-no-report-without-path-negative/input.luac:source-to-sink reason=endpoint-only-no-path", + "bc-kill-overwrite/input.luac:root@pc3:r2->root@pc7:r4 reason=killed-taint-path", + "bc-branch-negative/input.luac:root@pc3:r2->root@pc8:r5 reason=branch-negative-taint-path" + ) + + val ruleCodes = markerCodes(reopened, "lua.rule.match") + ruleCodes.exists(_.contains("formvaluex")) shouldBe false + ruleCodes.exists(_.contains("executex")) shouldBe false + markerCodes(reopened, "lua.sink.endpoint") + .exists(_.contains("d24-rules-overmatch-constant-sink-negative")) shouldBe false + + markerCodes(reopened, "lua.report.vulnerability") + .exists(_.contains("d24-sanitizer-suppresses-report")) shouldBe false + markerCodes(reopened, "lua.report.vulnerability") + .exists(_.contains("d24-report-no-report-without-path-negative")) shouldBe false + markerCodes(reopened, "lua.report.vulnerability") + .exists(code => code.contains("bc-kill-overwrite") || code.contains("bc-branch-negative")) shouldBe false + + val e5NodeCount = reopened.call + .name("lua\\.(rule\\.match|source\\.endpoint|sink\\.endpoint|sanitizer\\.call|sanitizer\\.classification|report\\.classification|report\\.vulnerability|e5\\.boundary)") + .size + val reportCount = reopened.call.nameExact("lua.report.vulnerability").size + info(s"e5_node_count=$e5NodeCount") + info(s"e5_report_count=$reportCount") + e5NodeCount should be > 0 + reportCount should be > 0 + } finally { + reopened.close() + } + } + } + } + + private def markerCodes(cpg: io.shiftleft.codepropertygraph.generated.Cpg, name: String): List[String] = + cpg.call.nameExact(name).code.l +} From 372747606f43d89f7fbc811cda4161339f553eb7 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 8 Jul 2026 16:42:45 -0400 Subject: [PATCH 017/105] docs(lua2cpg): document rules sanitizer report smoke --- joern-cli/frontends/lua2cpg/README.md | 35 +++++++++++++++++++ .../rules-sanitizer-report/SAMPLE-MANIFEST.md | 19 ++++++++++ 2 files changed, 54 insertions(+) create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/SAMPLE-MANIFEST.md diff --git a/joern-cli/frontends/lua2cpg/README.md b/joern-cli/frontends/lua2cpg/README.md index 12d22a5722f1..8d4db8233fb8 100644 --- a/joern-cli/frontends/lua2cpg/README.md +++ b/joern-cli/frontends/lua2cpg/README.md @@ -91,3 +91,38 @@ taint paths over committed fixtures. It does not claim QueryDB readiness, source parser AST semantics, sanitizer classification, report construction, schema extension acceptance, distribution acceptance, or official frontend acceptance. + +## Rules, Sanitizer, And Report Smoke + +```bash +JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' \ + sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest' +git status --short +``` + +Expected result: + +- `RulesSanitizerReportSmokeTest` succeeds. +- `git status --short` is clean after the smoke. + +This E5 smoke proves source/sink rule matches, sanitizer calls and +classification, report classification, vulnerability report construction, and +explainable E5 boundaries through schema-safe CPG `CALL` markers over committed +focused `.luac` fixtures. + +Reviewer traversal surface: + +```scala +cpg.call.nameExact("lua.rule.match").code.l +cpg.call.nameExact("lua.source.endpoint").code.l +cpg.call.nameExact("lua.sink.endpoint").code.l +cpg.call.nameExact("lua.sanitizer.call").code.l +cpg.call.nameExact("lua.sanitizer.classification").code.l +cpg.call.nameExact("lua.report.classification").code.l +cpg.call.nameExact("lua.report.vulnerability").code.l +cpg.call.nameExact("lua.e5.boundary").code.l +``` + +This phase does not claim QueryDB inclusion, schema extension acceptance, +distribution acceptance, source parser AST support, production security-query +readiness, or official frontend acceptance. diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/SAMPLE-MANIFEST.md b/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/SAMPLE-MANIFEST.md new file mode 100644 index 000000000000..386a63356e9a --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/SAMPLE-MANIFEST.md @@ -0,0 +1,19 @@ +# Lua2Cpg Rules, Sanitizer, And Report Samples + +These committed bytecode samples support the `RulesSanitizerReportSmokeTest` +reviewer smoke. They are copied from the referenceAnalyzer oracle fixture set and are +consumed entirely inside the `lua2cpg` test resources. + +| Fixture | Source fixture path | Capability reason | Consuming reviewer command | +| --- | --- | --- | --- | +| `bc-taint-minimal-path/input.luac` | `referenceAnalyzer tests/fixtures/bc-taint-minimal-path/input.luac` | Minimal source-to-sink path for rule, endpoint, report, and E5 boundary smoke coverage. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | +| `d16-rf-formvalue-os-execute-chain/input.luac` | `referenceAnalyzer tests/fixtures/d16-rf-formvalue-os-execute-chain/input.luac` | Final-segment `*.formvalue` source and `*.execute` sink positive with a constructed report. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | +| `d16-rf-submit-dpp-uri-execute/input.luac` | `referenceAnalyzer tests/fixtures/d16-rf-submit-dpp-uri-execute/input.luac` | Independent same-module formvalue-to-execute report positive. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | +| `d16-rf-webcmd-cross-module-popen/controller.luac` | `referenceAnalyzer tests/fixtures/d16-rf-webcmd-cross-module-popen/controller.luac` | Cross-module source side for final-segment source/sink and report construction. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | +| `d16-rf-webcmd-cross-module-popen/mtkwifi.luac` | `referenceAnalyzer tests/fixtures/d16-rf-webcmd-cross-module-popen/mtkwifi.luac` | Cross-module `*.popen` sink side for final-segment sink and report construction. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | +| `d24-sanitizer-suppresses-report/input.luac` | `referenceAnalyzer tests/fixtures/d24-sanitizer-suppresses-report/input.luac` | On-chain sanitizer positive; emits sanitized classification and suppresses true-positive vulnerability reporting. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | +| `d24-rules-overmatch-constant-sink-negative/input.luac` | `referenceAnalyzer tests/fixtures/d24-rules-overmatch-constant-sink-negative/input.luac` | Rejects `formvaluex`, `executex`, and fixed-string sink arguments. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | +| `d24-sanitizer-same-suffix-off-chain-negative/input.luac` | `referenceAnalyzer tests/fixtures/d24-sanitizer-same-suffix-off-chain-negative/input.luac` | Same-suffix sanitizer call not on the path remains `not-sanitized` and does not suppress the report. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | +| `d24-report-no-report-without-path-negative/input.luac` | `referenceAnalyzer tests/fixtures/d24-report-no-report-without-path-negative/input.luac` | Endpoint-only source/sink evidence does not create a vulnerability report. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | +| `bc-kill-overwrite/input.luac` | `referenceAnalyzer tests/fixtures/bc-kill-overwrite/input.luac` | Killed taint path negative boundary; no E5 report should be emitted. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | +| `bc-branch-negative/input.luac` | `referenceAnalyzer tests/fixtures/bc-branch-negative/input.luac` | Branch-negative no-path boundary; no E5 report should be emitted. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | From abfe1b37509ed252f5cca57d68941ce2d113e19e Mon Sep 17 00:00:00 2001 From: prankster009 Date: Thu, 9 Jul 2026 00:06:01 -0400 Subject: [PATCH 018/105] docs(lua2cpg): add benchmark adapter runbook --- joern-cli/frontends/lua2cpg/README.md | 69 +++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/README.md b/joern-cli/frontends/lua2cpg/README.md index 8d4db8233fb8..dd89850e759d 100644 --- a/joern-cli/frontends/lua2cpg/README.md +++ b/joern-cli/frontends/lua2cpg/README.md @@ -126,3 +126,72 @@ cpg.call.nameExact("lua.e5.boundary").code.l This phase does not claim QueryDB inclusion, schema extension acceptance, distribution acceptance, source parser AST support, production security-query readiness, or official frontend acceptance. + +## Controller Benchmark Runbook + +Quick local capability smoke from the Joern clone: + +```bash +JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' \ + sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest' +JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' \ + sbt 'lua2cpg/stage' +joern-cli/frontends/lua2cpg/target/universal/stage/bin/lua2cpg --help +git status --short +``` + +Expected result: + +- `RulesSanitizerReportSmokeTest` succeeds and prints E5 node/report counts. +- `lua2cpg/stage` succeeds. +- The staged `lua2cpg --help` command prints usage. +- `git status --short` is clean after the smoke. + +referenceAnalyzer-managed benchmark adapter smoke: + +```bash +referenceAnalyzer=/path/to/referenceAnalyzer +JOERN_CLONE=$(pwd) +JOERN_COMMAND="$JOERN_CLONE/joern-cli/frontends/lua2cpg/target/universal/stage/bin/lua2cpg" +RUN_ID=joern-upstream-smoke +RUN_ROOT=/tmp/referenceAnalyzer-upstream-joern-smoke-runs +FIRMWARE_ROOT=/tmp/referenceAnalyzer-focused-luac + +cd "$referenceAnalyzer" +python3 -m tools.real_firmware.OpenWrtDerived_compare init \ + --run-id "$RUN_ID" \ + --run-root "$RUN_ROOT" \ + --firmware-profile custom \ + --firmware-root "$FIRMWARE_ROOT" \ + --luabyte-result-dir "$RUN_ROOT/$RUN_ID-luabyte-placeholder" \ + --upstream-joern-clone "$JOERN_CLONE" + +python3 -m tools.real_firmware.OpenWrtDerived_compare run-joern \ + --run-dir "$RUN_ROOT/$RUN_ID" \ + --upstream-joern-clone "$JOERN_CLONE" \ + --joern-command "$JOERN_COMMAND" + +python3 - <<'PY' +from pathlib import Path +from tools.real_firmware.normalize_joern import normalize_joern_run + +run_dir = Path("/tmp/referenceAnalyzer-upstream-joern-smoke-runs/joern-upstream-smoke") +normalize_joern_run(run_dir=run_dir, run_id=run_dir.name) +PY +``` + +Expected output: + +- `raw/joern/run-errors.json` contains `{"errors": []}` when the staged command + can process the selected material. +- `raw/joern/command-record.json` records `target_kind=upstream-clone` for this + clone. +- `normalized/joern.jsonl` is present. + +The benchmark adapter smoke is controlled by referenceAnalyzer because referenceAnalyzer owns the +oracle, firmware selection, normalization, comparison, and performance +judgment. It proves that this Joern clone can produce upstream-clone output for +the controller. It is not a standalone full-corpus benchmark and does not claim +QueryDB inclusion, schema extension acceptance, distribution acceptance, source +parser AST support, production security-query readiness, or official frontend +acceptance. From c1c2537c9a79f2a2afeb06942df5c1dbf03119c6 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Thu, 9 Jul 2026 00:52:57 -0400 Subject: [PATCH 019/105] feat(lua2cpg): export real-firmware benchmark evidence --- joern-cli/frontends/lua2cpg/build.sbt | 1 + .../main/scala/io/joern/lua2cpg/Lua2Cpg.scala | 6 +- .../main/scala/io/joern/lua2cpg/Main.scala | 16 +- .../LuaRealFirmwareEvidenceExporter.scala | 654 ++++++++++++++++++ .../RealFirmwareEvidenceExportSmokeTest.scala | 61 ++ 5 files changed, 734 insertions(+), 4 deletions(-) create mode 100644 joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaRealFirmwareEvidenceExporter.scala create mode 100644 joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala diff --git a/joern-cli/frontends/lua2cpg/build.sbt b/joern-cli/frontends/lua2cpg/build.sbt index 1c390146efdc..714cba0cb07c 100644 --- a/joern-cli/frontends/lua2cpg/build.sbt +++ b/joern-cli/frontends/lua2cpg/build.sbt @@ -6,6 +6,7 @@ dependsOn( ) libraryDependencies ++= Seq( + "com.lihaoyi" %% "ujson" % Versions.upickle, "org.scalatest" %% "scalatest" % Versions.scalatest % Test ) diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/Lua2Cpg.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/Lua2Cpg.scala index a93f2d19b623..6a491c504e0d 100644 --- a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/Lua2Cpg.scala +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/Lua2Cpg.scala @@ -1,5 +1,6 @@ package io.joern.lua2cpg +import io.joern.lua2cpg.bytecode.{LuaProgramSemantics, LuaRealFirmwareEvidenceExporter} import io.joern.lua2cpg.passes.{LuaBytecodeModelPass, LuaFileInventoryPass} import io.joern.x2cpg.X2Cpg.withNewEmptyCpg import io.joern.x2cpg.X2CpgFrontend @@ -13,10 +14,13 @@ class Lua2Cpg extends X2CpgFrontend { override val defaultConfig: Config = Config() override def createCpg(config: Config): Try[Cpg] = { + val decoded = LuaBytecodeModelPass.decodeInputs(config) + val programSemantics = LuaProgramSemantics.normalize(decoded.map(item => item.relativeName -> item.result)) withNewEmptyCpg(config.outputPath, config) { (cpg, config) => new MetaDataPass(cpg, "LUA", config.inputPath).createAndApply() new LuaFileInventoryPass(cpg, config).createAndApply() - new LuaBytecodeModelPass(cpg, config).createAndApply() + new LuaBytecodeModelPass(cpg, config, Some(decoded)).createAndApply() + LuaRealFirmwareEvidenceExporter.write(config, decoded, programSemantics) } } } diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/Main.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/Main.scala index e7fd342ff0c7..4a8ac6fa59c6 100644 --- a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/Main.scala +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/Main.scala @@ -4,17 +4,27 @@ import io.joern.lua2cpg.Frontend.cmdLineParser import io.joern.x2cpg.{X2CpgConfig, X2CpgMain} import scopt.OParser -final case class Config(override val genericConfig: X2CpgConfig.GenericConfig = X2CpgConfig.GenericConfig()) - extends X2CpgConfig[Config] { +final case class Config( + realFirmwareOutputDir: Option[String] = None, + override val genericConfig: X2CpgConfig.GenericConfig = X2CpgConfig.GenericConfig() +) extends X2CpgConfig[Config] { override def withGenericConfig(value: X2CpgConfig.GenericConfig): Config = copy(genericConfig = value) + + def withRealFirmwareOutputDir(value: String): Config = + copy(realFirmwareOutputDir = Some(value)) } private object Frontend { val cmdLineParser: OParser[Unit, Config] = { val builder = OParser.builder[Config] import builder.* - OParser.sequence(programName("lua2cpg")) + OParser.sequence( + programName("lua2cpg"), + opt[String]("lua-real-firmware-output-dir") + .text("write Lua real-firmware benchmark evidence JSON to the given directory") + .action((value, config) => config.withRealFirmwareOutputDir(value)) + ) } } diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaRealFirmwareEvidenceExporter.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaRealFirmwareEvidenceExporter.scala new file mode 100644 index 000000000000..fd95c200c81a --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaRealFirmwareEvidenceExporter.scala @@ -0,0 +1,654 @@ +package io.joern.lua2cpg.bytecode + +import io.joern.lua2cpg.Config +import io.joern.lua2cpg.passes.LuaBytecodeModelPass.DecodedBytecode + +import java.nio.charset.StandardCharsets +import java.nio.file.{Files, Path, Paths} +import scala.jdk.CollectionConverters.* + +object LuaRealFirmwareEvidenceExporter { + def write(config: Config, decoded: Vector[DecodedBytecode], semantics: LuaProgramSemantics): Unit = + config.realFirmwareOutputDir.foreach { outputDir => + validateTaintPathEndpoints(semantics) + val root = Paths.get(outputDir) + val stagingDir = root.resolve("staging") + Files.createDirectories(stagingDir) + + val artifacts = decoded.map { item => + val staging = stagingFor(item, semantics) + writeJson(stagingDir.resolve(s"${safeArtifactName(artifactIdFor(item.relativeName))}.json"), staging) + artifactSummary(item) + } + + writeJson( + root.resolve("decoder-summary.json"), + ujson.Obj( + "run_id" -> "lua2cpg-upstream-export", + "totals" -> ujson.Obj( + "input_count" -> decoded.size, + "decoded_count" -> decoded.count(_.result.artifact.accepted), + "diagnostic_count" -> decoded.count(!_.result.artifact.accepted), + "prototype_count" -> decoded.flatMap(_.result.root.toVector.flatMap(allPrototypes)).size, + "instruction_count" -> decoded + .flatMap(_.result.root.toVector.flatMap(allPrototypes)) + .map(_.instructions.size) + .sum, + "callsite_count" -> decoded.flatMap(_.result.root.toVector.flatMap(allPrototypes)).map(countCallsites).sum, + "flow_edge_count" -> decoded.map(item => localSemantics(item.result).localFlows.size).sum + ), + "artifacts" -> artifacts + ) + ) + writeJson(root.resolve("path-search-profile.json"), pathSearchProfile(semantics)) + writeJson( + root.resolve("run-summary.json"), + ujson.Obj( + "run_id" -> "lua2cpg-upstream-export", + "status" -> "completed", + "native_status" -> "cpg-written", + "d19_path_parity_status" -> "not-run", + "native_d19_path_parity_status" -> "not-run", + "totals" -> ujson.Obj( + "input_count" -> decoded.size, + "decoded_count" -> decoded.count(_.result.artifact.accepted), + "diagnostic_count" -> decoded.count(!_.result.artifact.accepted) + ) + ) + ) + writeJson(root.resolve("run-errors.json"), ujson.Obj("errors" -> ujson.Arr())) + } + + private def stagingFor(item: DecodedBytecode, semantics: LuaProgramSemantics): ujson.Obj = { + val relativeName = item.relativeName + val result = item.result + val artifactId = artifactIdFor(relativeName) + val accepted = result.artifact.accepted + val prototypes = result.root.toVector.flatMap(allPrototypes) + val local = localSemantics(result) + + ujson.Obj( + "artifact_id" -> artifactId, + "relative_path" -> relativeName, + "decoder_status" -> (if (accepted) "accepted" else result.artifact.diagnostic.kind), + "input_kind" -> result.artifact.inputKind, + "profile" -> result.profile.map(profileJson).getOrElse(ujson.Null), + "provenance" -> "upstream-lua2cpg,bytecode-only", + "nodes" -> nodesFor(prototypes), + "edges" -> local.localFlows.map(localFlowJson), + "call_name_resolution" -> callNameResolutionRows(relativeName, result, semantics), + "unresolved_values" -> ujson.Arr(), + "upvalue_flows" -> local.upvalueFlows.map(upvalueFlowJson), + "defuse_paths" -> local.localFlows.map(defusePathJson(relativeName)), + "function_identity" -> prototypes.map(functionIdentityJson(artifactId, relativeName)), + "module_resolution" -> semantics.moduleResolutions + .filter(_.fromModulePath == relativeName) + .map(moduleResolutionJson), + "module_return_table" -> semantics.moduleReturnTables + .filter(_.modulePath == relativeName) + .map(moduleReturnTableJson), + "module_linkage" -> moduleLinkageRows(relativeName, semantics), + "call_target_candidate" -> callTargetCandidateRows(relativeName, local, semantics), + "interproc_arg_flow" -> semantics.interproceduralArgFlows + .filter(_.fromArgumentRef.startsWith(s"$relativeName:")) + .map(interproceduralArgFlowJson), + "interproc_return_flow" -> semantics.interproceduralReturnFlows + .filter(_.callerResultRef.startsWith(s"$relativeName:")) + .map(interproceduralReturnFlowJson), + "source_endpoints" -> semantics.sourceEndpoints + .filter(_.sourceRef.startsWith(s"$relativeName:")) + .map(sourceEndpointJson), + "sink_endpoints" -> semantics.sinkEndpoints.filter(_.sinkRef.startsWith(s"$relativeName:")).map(sinkEndpointJson), + "path_evidence" -> pathEvidenceRows(relativeName, semantics) + ) + } + + private def artifactSummary(item: DecodedBytecode): ujson.Obj = { + val prototypes = item.result.root.toVector.flatMap(allPrototypes) + ujson.Obj( + "artifact_id" -> artifactIdFor(item.relativeName), + "relative_path" -> item.relativeName, + "status" -> (if (item.result.artifact.accepted) "accepted" else item.result.artifact.diagnostic.kind), + "profile_id" -> item.result.profile.map(_.profileId).getOrElse("unavailable"), + "profile" -> item.result.profile.map(profileJson).getOrElse(ujson.Null), + "diagnostic_kind" -> item.result.artifact.diagnostic.kind, + "diagnostic_message" -> item.result.artifact.diagnostic.message, + "prototype_count" -> prototypes.size, + "instruction_count" -> prototypes.map(_.instructions.size).sum, + "callsite_count" -> prototypes.map(countCallsites).sum, + "flow_edge_count" -> localSemantics(item.result).localFlows.size, + "staging_graph" -> s"staging/${safeArtifactName(artifactIdFor(item.relativeName))}.json", + "path_evidence_count" -> 0 + ) + } + + private def localSemantics(result: LuaBytecodeDecodeResult): LuaPrototypeSemantics = + result.root + .map(LuaInstructionSemantics.normalize) + .getOrElse( + LuaPrototypeSemantics( + Vector.empty, + Vector.empty, + Vector.empty, + Vector.empty, + Vector.empty, + Vector.empty, + Vector.empty, + Vector.empty, + Vector.empty, + Vector.empty, + Vector.empty, + Vector.empty + ) + ) + + private def callNameResolutionRows( + relativeName: String, + result: LuaBytecodeDecodeResult, + semantics: LuaProgramSemantics + ): Vector[ujson.Obj] = { + val decodedRows = result.root.toVector.flatMap(allPrototypes).flatMap { prototype => + val callsByPrototype = + LuaInstructionSemantics.normalizePrototype(prototype).callSites.map(row => row.callsiteId -> row).toMap + resolvedCalls(prototype).map { call => + val callsite = callsByPrototype.get(call.callsiteId) + val targetValueRef = callsite + .map(_.targetValueRef) + .getOrElse( + throw new IllegalStateException(s"missing callsite semantics for ${relativeName}::${call.callsiteId}") + ) + ujson.Obj( + "resolution_id" -> s"${relativeName}:${call.callsiteId}:${call.resolvedName.getOrElse("unresolved")}", + "artifact_id" -> artifactIdFor(relativeName), + "artifact_role" -> "main", + "module_path" -> relativeName, + "prototype_id" -> call.prototypeId, + "pc" -> call.pc, + "callsite_id" -> scopedCallsite(relativeName, call.callsiteId), + "target_value_ref" -> targetValueRef, + "bytecode_pattern" -> call.resolvedName.getOrElse("unresolved-target"), + "resolved_name" -> call.resolvedName.getOrElse(""), + "name_components" -> call.resolvedName.map(_.split('.').toVector).getOrElse(Vector.empty), + "resolution_kind" -> call.resolvedName.map(_ => "global-member-chain").getOrElse("unresolved"), + "confidence" -> call.resolvedName.map(_ => "bytecode-derived").getOrElse("unresolved"), + "provenance" -> "upstream-lua2cpg,bytecode-only", + "unresolved_reason" -> call.resolvedName + .map(_ => "none") + .getOrElse("target-register-has-no-resolved-name-chain"), + "argument_value_refs" -> call.argumentRefs, + "return_value_refs" -> call.returnRefs, + "argument_constants" -> ujson.Arr(), + "direct_target_prototype_ids" -> ujson.Arr() + ) + } + } + val sanitizerRows = semantics.sanitizerCalls.flatMap { row => + val (modulePath, callsiteId) = splitQualifiedRef(row.callsiteId) + if (modulePath == relativeName) { + Vector( + ujson.Obj( + "resolution_id" -> s"${row.callsiteId}:${row.sanitizerName}", + "artifact_id" -> artifactIdFor(relativeName), + "artifact_role" -> "main", + "module_path" -> modulePath, + "prototype_id" -> prototypeIdFromCallsiteId(callsiteId), + "pc" -> pcFromCallsiteId(callsiteId), + "callsite_id" -> toScopedStepRef(row.callsiteId), + "target_value_ref" -> splitQualifiedRef(row.sanitizedValueRef)._2, + "bytecode_pattern" -> s"sanitizer:${row.sanitizerName}", + "resolved_name" -> row.sanitizerName, + "name_components" -> row.sanitizerName.split('.').toVector, + "resolution_kind" -> "sanitizer-call", + "confidence" -> "bytecode-derived", + "provenance" -> row.provenance, + "unresolved_reason" -> "none", + "argument_value_refs" -> Vector(splitQualifiedRef(row.sanitizedValueRef)._2), + "return_value_refs" -> Vector(splitQualifiedRef(row.sanitizedValueRef)._2), + "argument_constants" -> ujson.Arr(), + "direct_target_prototype_ids" -> ujson.Arr() + ) + ) + } else Vector.empty + } + decodedRows ++ sanitizerRows + } + + private def resolvedCalls(prototype: LuaPrototype): Vector[ResolvedCall] = + prototype.instructions + .filter(instruction => instruction.opcode == LuaOpcode.Call || instruction.opcode == LuaOpcode.TailCall) + .map { instruction => + ResolvedCall( + callsiteId = s"${prototype.prototypeId}@pc${instruction.pc}", + prototypeId = prototype.prototypeId, + pc = instruction.pc, + resolvedName = callTargetNameAt(prototype, instruction.pc, instruction.a), + argumentRefs = callArgumentRefs(prototype, instruction), + returnRefs = callReturnRefs(prototype.prototypeId, instruction) + ) + } + + private final case class ResolvedCall( + callsiteId: String, + prototypeId: String, + pc: Int, + resolvedName: Option[String], + argumentRefs: Vector[String], + returnRefs: Vector[String] + ) + + private def callTargetNameAt(prototype: LuaPrototype, pc: Int, slot: Int): Option[String] = + prototype.instructions + .filter(instruction => instruction.pc < pc && instruction.a == slot) + .sortBy(_.pc) + .lastOption + .flatMap { + case instruction if instruction.opcode == LuaOpcode.GetGlobal => + constantName(prototype.constants, instruction.b) + case instruction if instruction.opcode == LuaOpcode.GetTable => + for { + baseName <- callTargetNameAt(prototype, instruction.pc, instruction.b) + field <- instruction.c.filter(_ >= 256).flatMap(value => constantName(prototype.constants, value - 256)) + } yield s"$baseName.$field" + case _ => None + } + + private def callArgumentRefs(prototype: LuaPrototype, instruction: LuaInstruction): Vector[String] = { + val slots = instruction.b match { + case 0 => ((instruction.a + 1) until prototype.maxStack).toVector + case 1 => Vector.empty + case n => ((instruction.a + 1) until (instruction.a + n)).toVector + } + slots.map(slot => s"${prototype.prototypeId}@pc${instruction.pc}:r$slot") + } + + private def callReturnRefs(prototypeId: String, instruction: LuaInstruction): Vector[String] = { + val slots = instruction.c match { + case Some(0) => Vector(instruction.a) + case Some(1) => Vector.empty + case Some(n) => (instruction.a until (instruction.a + n - 1)).toVector + case None => Vector.empty + } + slots.map(slot => s"$prototypeId@pc${instruction.pc}:r$slot") + } + + private def nodesFor(prototypes: Vector[LuaPrototype]): Vector[ujson.Obj] = + prototypes.flatMap { prototype => + prototype.instructions.map { instruction => + ujson.Obj( + "type" -> "LUA_BYTECODE_INSTRUCTION", + "prototype_id" -> prototype.prototypeId, + "pc" -> instruction.pc, + "opcode" -> instruction.opcode.mnemonic, + "callsite_id" -> s"${prototype.prototypeId}@pc${instruction.pc}", + "reads" -> readRefs(prototype.prototypeId, instruction), + "writes" -> writeRefs(prototype.prototypeId, instruction) + ) + } + } + + private def readRefs(prototypeId: String, instruction: LuaInstruction): Vector[String] = + instruction.opcode match { + case LuaOpcode.Call | LuaOpcode.TailCall => + (Vector(instruction.a) ++ callArgumentSlots(instruction)).map(slot => + s"$prototypeId@pc${instruction.pc}:r$slot" + ) + case LuaOpcode.Return => + returnSlots(instruction).map(slot => s"$prototypeId@pc${instruction.pc}:r$slot").toVector + case _ => + (Vector(instruction.b) ++ instruction.c.toVector.filter(_ < 256)).map(slot => + s"$prototypeId@pc${instruction.pc}:r$slot" + ) + } + + private def writeRefs(prototypeId: String, instruction: LuaInstruction): Vector[String] = + instruction.opcode match { + case LuaOpcode.Return => Vector.empty + case _ => Vector(s"$prototypeId@pc${instruction.pc}:r${instruction.a}") + } + + private def localFlowJson(row: LuaLocalFlow): ujson.Obj = + ujson.Obj("type" -> "REACHING_DEF", "src" -> row.sourceRef, "dst" -> row.sinkRef, "kind" -> row.edgeKind) + + private def upvalueFlowJson(row: LuaUpvalueFlow): ujson.Obj = + ujson.Obj( + "flow_id" -> s"upvalue:${row.upvalueId}:${row.captureRef}:${row.writeRef}", + "upvalue_id" -> row.upvalueId, + "capture_ref" -> row.captureRef, + "read_ref" -> row.readRef, + "write_ref" -> row.writeRef, + "provenance" -> row.provenance + ) + + private def defusePathJson(relativeName: String)(row: LuaLocalFlow): ujson.Obj = + ujson.Obj( + "path_id" -> s"$relativeName:${row.sourceRef}->${row.sinkRef}", + "source_ref" -> row.sourceRef, + "sink_ref" -> row.sinkRef, + "path_steps" -> Vector(qualify(relativeName, row.sourceRef), qualify(relativeName, row.sinkRef)), + "first_missing_edge" -> "none", + "provenance" -> row.provenance + ) + + private def functionIdentityJson(artifactId: String, relativeName: String)(prototype: LuaPrototype): ujson.Obj = + ujson.Obj( + "identity_id" -> s"$relativeName:${prototype.prototypeId}", + "artifact_id" -> artifactId, + "artifact_role" -> "main", + "module_path" -> relativeName, + "prototype_id" -> prototype.prototypeId, + "display_name" -> prototype.prototypeId, + "identity_kind" -> "bytecode-prototype-id", + "provenance" -> "upstream-lua2cpg,bytecode-only,prototype-identity" + ) + + private def moduleResolutionJson(row: LuaModuleResolution): ujson.Obj = + ujson.Obj( + "resolution_id" -> s"${row.fromModulePath}:${row.requireCallsiteId}:${row.requireString}", + "callsite_id" -> scopedCallsite(row.fromModulePath, row.requireCallsiteId), + "module_path" -> row.fromModulePath, + "require_string" -> row.requireString, + "resolution_status" -> row.resolutionStatus, + "target_module_path" -> row.targetModulePath.getOrElse(""), + "unresolved_reason" -> row.unresolvedReason.getOrElse("none"), + "provenance" -> row.provenance + ) + + private def moduleReturnTableJson(row: LuaModuleReturnTable): ujson.Obj = + ujson.Obj( + "return_table_id" -> s"${row.modulePath}:${row.tableRef}:${row.fieldName}", + "module_path" -> row.modulePath, + "table_ref" -> row.tableRef, + "field_name" -> row.fieldName, + "target_prototype_id" -> row.targetPrototypeId, + "provenance" -> row.provenance + ) + + private def moduleLinkageRows(relativeName: String, semantics: LuaProgramSemantics): Vector[ujson.Obj] = + semantics.moduleFieldCallTargets.filter(_.fromModulePath == relativeName).map { row => + ujson.Obj( + "linkage_id" -> s"${row.fromModulePath}:${row.callsiteId}:${row.targetModulePath}:${row.targetPrototypeId}", + "callsite_id" -> scopedCallsite(row.fromModulePath, row.callsiteId), + "module_path" -> row.fromModulePath, + "target_module_path" -> row.targetModulePath, + "target_prototype_id" -> row.targetPrototypeId, + "field_name" -> row.fieldName, + "resolution_status" -> "matched", + "provenance" -> row.provenance + ) + } + + private def callTargetCandidateRows( + relativeName: String, + local: LuaPrototypeSemantics, + semantics: LuaProgramSemantics + ): Vector[ujson.Obj] = { + val localRows = local.callTargetCandidates.map { row => + ujson.Obj( + "candidate_id" -> s"$relativeName:${row.callsiteId}:${row.targetRef}", + "callsite_id" -> scopedCallsite(relativeName, row.callsiteId), + "module_path" -> relativeName, + "target_ref" -> s"$relativeName::${row.targetRef}", + "confidence" -> row.confidence, + "resolution_status" -> "matched", + "unresolved_reason" -> "none", + "provenance" -> row.provenance + ) + } + val crossRows = semantics.crossBoundaryCallTargets.filter(_.fromModulePath == relativeName).map { row => + ujson.Obj( + "candidate_id" -> s"${row.fromModulePath}:${row.callsiteId}:${row.targetModulePath}:${row.targetPrototypeId}", + "callsite_id" -> scopedCallsite(row.fromModulePath, row.callsiteId), + "module_path" -> row.fromModulePath, + "target_module_path" -> row.targetModulePath, + "target_prototype_id" -> row.targetPrototypeId, + "target_ref" -> s"${row.targetModulePath}::${row.targetPrototypeId}", + "confidence" -> row.confidence, + "resolution_status" -> "matched", + "unresolved_reason" -> "none", + "provenance" -> row.provenance + ) + } + localRows ++ crossRows + } + + private def interproceduralArgFlowJson(row: LuaInterproceduralArgFlow): ujson.Obj = + ujson.Obj( + "flow_id" -> s"arg:${qualifiedCallsite(row.callsiteId, row.fromArgumentRef)}:${row.fromArgumentRef}:${row.toParameterRef}", + "callsite_id" -> qualifiedCallsite(row.callsiteId, row.fromArgumentRef), + "from_argument_ref" -> row.fromArgumentRef, + "argument_index" -> row.argumentIndex, + "to_parameter_ref" -> row.toParameterRef, + "target_module_path" -> row.targetModulePath, + "target_prototype_id" -> row.targetPrototypeId, + "provenance" -> row.provenance + ) + + private def interproceduralReturnFlowJson(row: LuaInterproceduralReturnFlow): ujson.Obj = + ujson.Obj( + "flow_id" -> s"return:${qualifiedCallsite(row.callsiteId, row.callerResultRef)}:${row.calleeReturnRef}:${row.callerResultRef}", + "callsite_id" -> qualifiedCallsite(row.callsiteId, row.callerResultRef), + "callee_return_ref" -> s"${row.targetModulePath}:${row.calleeReturnRef}", + "caller_result_ref" -> row.callerResultRef, + "target_module_path" -> row.targetModulePath, + "target_prototype_id" -> row.targetPrototypeId, + "provenance" -> row.provenance + ) + + private def sourceEndpointJson(row: LuaSourceEndpoint): ujson.Obj = { + val (modulePath, valueRef) = splitQualifiedRef(row.sourceRef) + ujson.Obj( + "module_path" -> modulePath, + "value_ref" -> valueRef, + "callsite_id" -> toScopedStepRef(row.callsiteId), + "trigger" -> row.trigger, + "provenance" -> row.provenance + ) + } + + private def sinkEndpointJson(row: LuaSinkEndpoint): ujson.Obj = { + val (modulePath, valueRef) = splitQualifiedRef(row.sinkRef) + ujson.Obj( + "module_path" -> modulePath, + "value_ref" -> valueRef, + "callsite_id" -> toScopedStepRef(row.callsiteId), + "trigger" -> row.trigger, + "param_idx" -> row.parameterIndex, + "provenance" -> row.provenance + ) + } + + private def pathEvidenceRows(relativeName: String, semantics: LuaProgramSemantics): Vector[ujson.Obj] = { + val exportedNamesByPrototype = semantics.moduleReturnTables + .groupBy(item => item.modulePath -> item.targetPrototypeId) + .view + .mapValues(_.map(_.fieldName).distinct.sorted) + .collect { case (key, Vector(singleName)) => key -> singleName } + .toMap + semantics.taintPaths.filter(_.sourceRef.startsWith(s"$relativeName:")).map { row => + val report = + semantics.reportClassifications.find(item => item.sourceRef == row.sourceRef && item.sinkRef == row.sinkRef) + val (sourceModule, sourceValue) = splitQualifiedRef(row.sourceRef) + val (sinkModule, sinkValue) = splitQualifiedRef(row.sinkRef) + val sourceEndpoint = semantics.sourceEndpoints + .find(_.sourceRef == row.sourceRef) + .getOrElse( + throw new IllegalStateException(s"taint path lacks source endpoint: ${row.sourceRef}->${row.sinkRef}") + ) + val sinkEndpoint = semantics.sinkEndpoints + .find(_.sinkRef == row.sinkRef) + .getOrElse(throw new IllegalStateException(s"taint path lacks sink endpoint: ${row.sourceRef}->${row.sinkRef}")) + val sourceCallsite = unqualifyCallsite(sourceEndpoint.callsiteId) + val sinkCallsite = unqualifyCallsite(sinkEndpoint.callsiteId) + val sourcePrototype = prototypeIdFromCallsiteId(sourceCallsite) + val sinkPrototype = prototypeIdFromCallsiteId(sinkCallsite) + val sourceFunctionName = displayFunctionName(exportedNamesByPrototype, sourceModule, sourcePrototype) + val sinkFunctionName = displayFunctionName(exportedNamesByPrototype, sinkModule, sinkPrototype) + ujson.Obj( + "path_id" -> s"${row.sourceRef}->${row.sinkRef}", + "baseline_report_id" -> "", + "source_module_path" -> sourceModule, + "source_function_name" -> sourceFunctionName, + "source_pc" -> pcFromCallsiteId(sourceCallsite), + "source_trigger" -> sourceEndpoint.trigger, + "sink_module_path" -> sinkModule, + "sink_function_name" -> sinkFunctionName, + "sink_pc" -> pcFromCallsiteId(sinkCallsite), + "sink_trigger" -> sinkEndpoint.trigger, + "sink_param_idx" -> sinkEndpoint.parameterIndex, + "source" -> ujson.Obj("module_path" -> sourceModule, "value_ref" -> sourceValue), + "sink" -> ujson.Obj("module_path" -> sinkModule, "value_ref" -> sinkValue), + "path_status" -> "matched", + "path_step_count" -> row.pathSteps.size, + "equivalence_level" -> "upstream-lua2cpg-native-path", + "path_steps" -> row.pathSteps.map(toScopedStepRef), + "sanitizer_hits" -> sanitizerHitsFor(row, semantics), + "classification" -> report.map(_.classification).getOrElse(row.classification), + "provenance" -> row.provenance + ) + } + } + + private def validateTaintPathEndpoints(semantics: LuaProgramSemantics): Unit = { + val sourceRefs = semantics.sourceEndpoints.map(_.sourceRef).toSet + val sinkRefs = semantics.sinkEndpoints.map(_.sinkRef).toSet + semantics.taintPaths.foreach { path => + if (!sourceRefs(path.sourceRef)) { + throw new IllegalStateException(s"taint path lacks source endpoint: ${path.sourceRef}->${path.sinkRef}") + } + if (!sinkRefs(path.sinkRef)) { + throw new IllegalStateException(s"taint path lacks sink endpoint: ${path.sourceRef}->${path.sinkRef}") + } + } + } + + private def displayFunctionName( + exportedNamesByPrototype: Map[(String, String), String], + modulePath: String, + prototypeId: String + ): String = + exportedNamesByPrototype.getOrElse(modulePath -> prototypeId, prototypeId) + + private def sanitizerHitsFor(path: LuaTaintPath, semantics: LuaProgramSemantics): ujson.Arr = + ujson.Arr.from( + semantics.sanitizerClassifications + .filter(row => row.sourceRef == path.sourceRef && row.sinkRef == path.sinkRef) + .map { row => + ujson.Obj( + "callsite_id" -> toScopedStepRef(row.sanitizerCallsiteId), + "sanitizer_name" -> row.sanitizerName, + "applies_to_sink" -> row.appliesToSink, + "on_dataflow_chain" -> row.onDataflowChain + ) + } + ) + + private def pathSearchProfile(semantics: LuaProgramSemantics): ujson.Obj = + ujson.Obj( + "status" -> "completed", + "source_endpoint_count" -> semantics.sourceEndpoints.size, + "sink_endpoint_count" -> semantics.sinkEndpoints.size, + "taint_path_count" -> semantics.taintPaths.size, + "sanitizer_classification_count" -> semantics.sanitizerClassifications.size, + "report_count" -> semantics.vulnerabilityReports.size, + "local_path_graph_module_count" -> semantics.pathSearchStats.localPathGraphModuleCount, + "local_path_graph_build_count" -> semantics.pathSearchStats.localPathGraphBuildCount, + "local_path_search_count" -> semantics.pathSearchStats.localPathSearchCount, + "distinct_local_path_query_count" -> semantics.pathSearchStats.distinctLocalPathQueryCount, + "source_sink_pair_count" -> semantics.pathSearchStats.sourceSinkPairCount, + "qualified_source_sink_pair_count" -> semantics.pathSearchStats.qualifiedSourceSinkPairCount, + "prototype_pruned_source_sink_pair_count" -> semantics.pathSearchStats.prototypePrunedSourceSinkPairCount + ) + + private def profileJson(profile: LuaBytecodeProfile): ujson.Obj = + ujson.Obj( + "lua_version" -> profile.luaVersion, + "bytecode_version" -> profile.bytecodeVersion, + "format" -> profile.format, + "endianness" -> profile.endianness, + "int_size" -> profile.intSize, + "size_t_size" -> profile.sizeTSize, + "instruction_size" -> profile.instructionSize, + "lua_number_size" -> profile.luaNumberSize, + "number_mode" -> profile.numberMode, + "profile_id" -> profile.profileId + ) + + private def allPrototypes(prototype: LuaPrototype): Vector[LuaPrototype] = + prototype +: prototype.nested.flatMap(allPrototypes) + + private def countCallsites(prototype: LuaPrototype): Int = + prototype.instructions.count(instruction => + instruction.opcode == LuaOpcode.Call || instruction.opcode == LuaOpcode.TailCall + ) + + private def callArgumentSlots(instruction: LuaInstruction): Vector[Int] = + instruction.b match { + case 0 => Vector(instruction.a + 1) + case 1 => Vector.empty + case n => ((instruction.a + 1) until (instruction.a + n)).toVector + } + + private def returnSlots(instruction: LuaInstruction): Seq[Int] = + instruction.b match { + case 0 => Seq.empty + case 1 => Seq.empty + case n => instruction.a until (instruction.a + n - 1) + } + + private def constantName(constants: Vector[LuaConstant], index: Int): Option[String] = + constants.collectFirst { case LuaConstant(`index`, "string", LuaConstantValue.StringValue(value)) => + value + } + + private def toScopedStepRef(valueRef: String): String = { + val (modulePath, localRef) = splitQualifiedRef(valueRef) + s"$modulePath::$localRef" + } + + private def qualify(modulePath: String, ref: String): String = s"$modulePath:$ref" + + private def qualifiedCallsite(callsiteId: String, valueRef: String): String = + s"${splitQualifiedRef(valueRef)._1}::$callsiteId" + + private def scopedCallsite(modulePath: String, callsiteId: String): String = + s"$modulePath::$callsiteId" + + private def unqualifyCallsite(ref: String): String = + ref.split(':').lastOption.getOrElse(ref) + + private def splitQualifiedRef(ref: String): (String, String) = { + val splitAt = Vector(".luac:") + .flatMap(marker => { + val index = ref.indexOf(marker) + if (index >= 0) Some(index + marker.length - 1) else None + }) + .headOption + .getOrElse(throw new IllegalArgumentException(s"Lua qualified ref is missing module path: $ref")) + (ref.substring(0, splitAt), ref.substring(splitAt + 1)) + } + + private def prototypeIdFromValueRef(ref: String): String = + ref.split("@pc", 2).headOption.getOrElse(ref) + + private def pcFromValueRef(ref: String): Int = + ref.split("@pc", 2).lift(1).flatMap(_.split(":r", 2).headOption).flatMap(_.toIntOption).getOrElse(0) + + private def prototypeIdFromCallsiteId(ref: String): String = + ref.split("@pc", 2).headOption.getOrElse(ref) + + private def pcFromCallsiteId(ref: String): Int = + ref.split("@pc", 2).lift(1).flatMap(_.toIntOption).getOrElse(0) + + private def artifactIdFor(relativeName: String): String = s"sha256:${relativeName}" + + private def safeArtifactName(artifactId: String): String = + artifactId.map { + case character if character.isLetterOrDigit => character + case _ => '_' + } + + private def writeJson(path: Path, value: ujson.Value): Unit = { + Files.createDirectories(path.getParent) + Files.write(path, (ujson.write(value, indent = 2) + "\n").getBytes(StandardCharsets.UTF_8)) + } +} diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala new file mode 100644 index 000000000000..7b943b5f3955 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -0,0 +1,61 @@ +package io.joern.lua2cpg + +import io.shiftleft.semanticcpg.utils.FileUtil +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec + +import java.nio.file.{Files, Paths} +import scala.jdk.CollectionConverters.* + +class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { + + "Lua2Cpg" should { + "export Lua real-firmware evidence with scoped callsite rows" in { + val resourceRoot = Paths.get(getClass.getClassLoader.getResource("rules-sanitizer-report").toURI) + + FileUtil.usingTemporaryDirectory("lua2cpg-real-firmware-export-smoke") { tmpDir => + val outputPath = tmpDir.resolve("rules-sanitizer-report.cpg.bin").toString + val exportDir = tmpDir.resolve("real-firmware-export") + val cpg = new Lua2Cpg() + .createCpg( + Config(realFirmwareOutputDir = Some(exportDir.toString)) + .withInputPath(resourceRoot.toString) + .withOutputPath(outputPath) + ) + .get + cpg.close() + + Files.isRegularFile(exportDir.resolve("decoder-summary.json")) shouldBe true + Files.isRegularFile(exportDir.resolve("run-summary.json")) shouldBe true + Files.isRegularFile(exportDir.resolve("run-errors.json")) shouldBe true + Files.isRegularFile(exportDir.resolve("path-search-profile.json")) shouldBe true + + val stagingDir = exportDir.resolve("staging") + val stagingStream = Files.list(stagingDir) + val stagingFiles = stagingStream.iterator.asScala.toVector + try { + stagingFiles.size should be > 0 + + val staging = stagingFiles + .map(path => ujson.read(Files.readString(path)).obj) + .find(_("relative_path").str.endsWith("d24-sanitizer-suppresses-report/input.luac")) + .getOrElse(fail("missing sanitizer fixture staging evidence")) + + val callRows = staging("call_name_resolution").arr.map(_.obj) + callRows.exists(row => + row("module_path").str.endsWith("d24-sanitizer-suppresses-report/input.luac") && + row("callsite_id").str == "root@pc20" && + row("resolved_name").str == "tonumber" + ) shouldBe true + + val pathRows = staging("path_evidence").arr.map(_.obj) + pathRows.exists(row => + row("path_steps").arr.exists(_.str.endsWith("d24-sanitizer-suppresses-report/input.luac::root@pc20:r2")) + ) shouldBe true + } finally { + stagingStream.close() + } + } + } + } +} From aec716c92800cba9adef998a6238c9ab4dbe4c57 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Thu, 9 Jul 2026 01:56:29 -0400 Subject: [PATCH 020/105] fix(lua2cpg): emit OpenWrt-derived real-firmware path reports --- .../RealFirmwareEvidenceExportSmokeTest.scala | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 7b943b5f3955..cd71bc6ed849 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -57,5 +57,68 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } } + + "export OpenWrtDerived real-firmware source-to-sink path evidence without fixture-id fallback" in { + val resourceRoot = Paths.get(getClass.getClassLoader.getResource("OpenWrtDerived-real-firmware-path-report").toURI) + + FileUtil.usingTemporaryDirectory("lua2cpg-OpenWrtDerived-real-firmware-path-report") { tmpDir => + val outputPath = tmpDir.resolve("OpenWrtDerived-real-firmware-path-report.cpg.bin").toString + val exportDir = tmpDir.resolve("real-firmware-export") + val cpg = new Lua2Cpg() + .createCpg( + Config(realFirmwareOutputDir = Some(exportDir.toString)) + .withInputPath(resourceRoot.toString) + .withOutputPath(outputPath) + ) + .get + cpg.close() + + val stagingDir = exportDir.resolve("staging") + val stagingStream = Files.list(stagingDir) + val stagingFiles = stagingStream.iterator.asScala.toVector + try { + stagingFiles.size should be > 0 + + val stagingRows = stagingFiles.map(path => ujson.read(Files.readString(path)).obj) + val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) + val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + sourceRows.exists(row => + row("module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && + row("callsite_id").str == "root.110@pc3" && + row("trigger").str == "luci.http.formvalue" + ) shouldBe true + + sinkRows.exists(row => + row("module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && + row("callsite_id").str == "root.110@pc12" && + row("trigger").str == "os.execute" + ) shouldBe true + + pathRows.size should be > 0 + pathRows.exists(row => + row("source_module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && + row("sink_module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && + row("source_pc").num.toInt == 3 && + row("sink_pc").num.toInt == 12 && + row("path_steps").arr.exists(_.str.contains("::")) && + row("path_steps").arr.exists(_.str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac::root.110@pc3:r0")) && + row("path_steps").arr.exists(_.str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac::root.110@pc12:r1")) + ) shouldBe true + + pathRows.foreach { row => + row("source_module_path").str should not be empty + row("sink_module_path").str should not be empty + row("path_steps").arr.foreach { step => + step.str should include("::") + } + } + pathRows.exists(row => row.obj.contains("callsite_id")) shouldBe false + } finally { + stagingStream.close() + } + } + } } } From 0cce403d5998e1c5be3b697c852e9273babb23aa Mon Sep 17 00:00:00 2001 From: prankster009 Date: Thu, 9 Jul 2026 02:43:32 -0400 Subject: [PATCH 021/105] fix(lua2cpg): cover OpenWrt-derived real-firmware report families --- .../RealFirmwareEvidenceExportSmokeTest.scala | 172 +++++++++++++----- 1 file changed, 129 insertions(+), 43 deletions(-) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index cd71bc6ed849..b8b0d2766bfb 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -59,65 +59,151 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } "export OpenWrtDerived real-firmware source-to-sink path evidence without fixture-id fallback" in { - val resourceRoot = Paths.get(getClass.getClassLoader.getResource("OpenWrtDerived-real-firmware-path-report").toURI) - - FileUtil.usingTemporaryDirectory("lua2cpg-OpenWrtDerived-real-firmware-path-report") { tmpDir => - val outputPath = tmpDir.resolve("OpenWrtDerived-real-firmware-path-report.cpg.bin").toString - val exportDir = tmpDir.resolve("real-firmware-export") - val cpg = new Lua2Cpg() - .createCpg( - Config(realFirmwareOutputDir = Some(exportDir.toString)) - .withInputPath(resourceRoot.toString) - .withOutputPath(outputPath) - ) - .get - cpg.close() - - val stagingDir = exportDir.resolve("staging") - val stagingStream = Files.list(stagingDir) - val stagingFiles = stagingStream.iterator.asScala.toVector - try { - stagingFiles.size should be > 0 - - val stagingRows = stagingFiles.map(path => ujson.read(Files.readString(path)).obj) - val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) - val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + withDLinkStagingRows { stagingRows => + val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) + val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + sourceRows.exists(row => + row("module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && + row("callsite_id").str == "root.110@pc3" && + row("trigger").str == "luci.http.formvalue" + ) shouldBe true + + sinkRows.exists(row => + row("module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && + row("callsite_id").str == "root.110@pc12" && + row("trigger").str == "os.execute" + ) shouldBe true + + pathRows.size should be > 0 + pathRows.exists(row => + row("source_module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && + row("sink_module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && + row("source_pc").num.toInt == 3 && + row("sink_pc").num.toInt == 12 && + row("path_steps").arr.exists(_.str.contains("::")) && + row("path_steps").arr.exists(_.str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac::root.110@pc3:r0")) && + row("path_steps").arr.exists(_.str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac::root.110@pc12:r1")) + ) shouldBe true + + pathRows.foreach { row => + row("source_module_path").str should not be empty + row("sink_module_path").str should not be empty + row("path_steps").arr.foreach { step => + step.str should include("::") + } + } + pathRows.exists(row => row.obj.contains("callsite_id")) shouldBe false + } + } + "export OpenWrtDerived root.61 fan-out source-to-sink path evidence" in { + withDLinkStagingRows { stagingRows => + val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) + val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + sourceRows.exists(row => + row("module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && + row("callsite_id").str == "root.61@pc63" && + row("trigger").str == "luci.http.formvalue" + ) shouldBe true + + val sinkCallsites = Vector( + "root.61@pc180", + "root.61@pc188", + "root.61@pc196", + "root.61@pc204", + "root.61@pc212", + "root.61@pc220", + "root.61@pc228" + ) + sinkCallsites.foreach { callsiteId => sourceRows.exists(row => row("module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && - row("callsite_id").str == "root.110@pc3" && + row("callsite_id").str == "root.61@pc63" && row("trigger").str == "luci.http.formvalue" ) shouldBe true sinkRows.exists(row => row("module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && - row("callsite_id").str == "root.110@pc12" && + row("callsite_id").str == callsiteId && row("trigger").str == "os.execute" ) shouldBe true + } - pathRows.size should be > 0 + sinkCallsites.foreach { callsiteId => + val sinkPc = callsiteId.split("@pc", 2)(1).toInt pathRows.exists(row => row("source_module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && row("sink_module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && - row("source_pc").num.toInt == 3 && - row("sink_pc").num.toInt == 12 && - row("path_steps").arr.exists(_.str.contains("::")) && - row("path_steps").arr.exists(_.str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac::root.110@pc3:r0")) && - row("path_steps").arr.exists(_.str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac::root.110@pc12:r1")) + row("source_pc").num.toInt == 63 && + row("sink_pc").num.toInt == sinkPc && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_trigger").str == "os.execute" && + row("path_steps").arr.forall(_.str.contains("::")) ) shouldBe true - - pathRows.foreach { row => - row("source_module_path").str should not be empty - row("sink_module_path").str should not be empty - row("path_steps").arr.foreach { step => - step.str should include("::") - } - } - pathRows.exists(row => row.obj.contains("callsite_id")) shouldBe false - } finally { - stagingStream.close() } + pathRows.exists(row => row.obj.contains("callsite_id")) shouldBe false + } + } + + "export OpenWrtDerived cross-module webcmd to mtkwifi popen path evidence" in { + withDLinkStagingRows { stagingRows => + val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) + val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + sourceRows.exists(row => + row("module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && + row("callsite_id").str == "root.2@pc4" && + row("trigger").str == "luci.http.formvalue" + ) shouldBe true + + sinkRows.exists(row => + row("module_path").str.endsWith("usr/lib/lua/mtkwifi.luac") && + row("callsite_id").str == "root.12@pc5" && + row("trigger").str == "io.popen" + ) shouldBe true + + pathRows.exists(row => + row("source_module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && + row("sink_module_path").str.endsWith("usr/lib/lua/mtkwifi.luac") && + row("source_pc").num.toInt == 4 && + row("sink_pc").num.toInt == 5 && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_trigger").str == "io.popen" && + row("path_steps").arr.forall(_.str.contains("::")) + ) shouldBe true + pathRows.exists(row => row.obj.contains("callsite_id")) shouldBe false + } + } + } + + private def withDLinkStagingRows(test: Vector[ujson.Obj] => Unit): Unit = { + val resourceRoot = Paths.get(getClass.getClassLoader.getResource("OpenWrtDerived-real-firmware-path-report").toURI) + + FileUtil.usingTemporaryDirectory("lua2cpg-OpenWrtDerived-real-firmware-path-report") { tmpDir => + val outputPath = tmpDir.resolve("OpenWrtDerived-real-firmware-path-report.cpg.bin").toString + val exportDir = tmpDir.resolve("real-firmware-export") + val cpg = new Lua2Cpg() + .createCpg( + Config(realFirmwareOutputDir = Some(exportDir.toString)) + .withInputPath(resourceRoot.toString) + .withOutputPath(outputPath) + ) + .get + cpg.close() + + val stagingDir = exportDir.resolve("staging") + val stagingStream = Files.list(stagingDir) + val stagingFiles = stagingStream.iterator.asScala.toVector + try { + stagingFiles.size should be > 0 + test(stagingFiles.map(path => ujson.read(Files.readString(path)).obj)) + } finally { + stagingStream.close() } } } From afdd31c170cc3ca9f2784196c34027eb7f4f3f82 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Thu, 9 Jul 2026 04:34:48 -0400 Subject: [PATCH 022/105] test(lua2cpg): add cross-platform path report red coverage --- .../RealFirmwareEvidenceExportSmokeTest.scala | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index b8b0d2766bfb..6fa3e044ed2a 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -179,6 +179,93 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { pathRows.exists(row => row.obj.contains("callsite_id")) shouldBe false } } + + "export CrossPlatform real-firmware source and sink endpoints before path repair" in { + withXiaomiStagingRows { stagingRows => + val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) + val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) + val callRows = stagingRows.flatMap(_("call_name_resolution").arr.map(_.obj)) + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + sourceRows.exists(row => + row("module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && + row("callsite_id").str == "root.39@pc15" && + row("trigger").str == "luci.http.formvalue" + ) shouldBe true + + sinkRows.exists(row => + row("module_path").str.endsWith("usr/lib/lua/xiaoqiang/util/XQQoSUtil.luac") && + row("callsite_id").str == "root.24@pc82" && + row("trigger").str == "os.execute" + ) shouldBe true + + sinkRows.exists(row => + row("module_path").str.endsWith("usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac") && + row("callsite_id").str == "root.0@pc25" && + row("trigger").str == "os.execute" + ) shouldBe true + + sinkRows.exists(row => + row("module_path").str.endsWith("usr/lib/lua/luci/util.luac") && + row("callsite_id").str == "root@pc3" && + row("trigger").str == "io.popen" + ) shouldBe true + + callRows.exists(row => + row("module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && + row("callsite_id").str == "root.145@pc48" && + row("resolved_name").str == "luci.util.exec" + ) shouldBe true + + pathRows.exists(row => row.obj.contains("callsite_id")) shouldBe false + } + } + + "export CrossPlatform representative source-to-sink path evidence" in { + withXiaomiStagingRows { stagingRows => + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + pathRows.exists(row => + row("source_module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && + row("sink_module_path").str.endsWith("usr/lib/lua/xiaoqiang/util/XQQoSUtil.luac") && + row("source_pc").num.toInt == 15 && + row("sink_pc").num.toInt == 82 && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_trigger").str == "os.execute" && + row("path_steps").arr.forall(_.str.contains("::")) + ) shouldBe true + + pathRows.exists(row => + row("source_module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && + row("sink_module_path").str.endsWith("usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac") && + row("source_pc").num.toInt == 15 && + row("sink_pc").num.toInt == 25 && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_trigger").str == "os.execute" && + row("path_steps").arr.forall(_.str.contains("::")) + ) shouldBe true + + pathRows.exists(row => + row("source_module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && + row("sink_module_path").str.endsWith("usr/lib/lua/luci/util.luac") && + Set(15, 19).contains(row("source_pc").num.toInt) && + row("sink_pc").num.toInt == 3 && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_trigger").str == "io.popen" && + row("path_steps").arr.forall(_.str.contains("::")) + ) shouldBe true + + pathRows.exists(row => + row("source_module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && + row("sink_module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && + row("source_pc").num.toInt == 15 && + row("sink_pc").num.toInt == 38 && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_trigger").str == "luci.util.exec" && + row("path_steps").arr.forall(_.str.contains("::")) + ) shouldBe true + } + } } private def withDLinkStagingRows(test: Vector[ujson.Obj] => Unit): Unit = { @@ -207,4 +294,31 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } } + + private def withXiaomiStagingRows(test: Vector[ujson.Obj] => Unit): Unit = { + val resourceRoot = Paths.get(getClass.getClassLoader.getResource("CrossPlatform-real-firmware-path-report").toURI) + + FileUtil.usingTemporaryDirectory("lua2cpg-CrossPlatform-real-firmware-path-report") { tmpDir => + val outputPath = tmpDir.resolve("CrossPlatform-real-firmware-path-report.cpg.bin").toString + val exportDir = tmpDir.resolve("real-firmware-export") + val cpg = new Lua2Cpg() + .createCpg( + Config(realFirmwareOutputDir = Some(exportDir.toString)) + .withInputPath(resourceRoot.toString) + .withOutputPath(outputPath) + ) + .get + cpg.close() + + val stagingDir = exportDir.resolve("staging") + val stagingStream = Files.list(stagingDir) + val stagingFiles = stagingStream.iterator.asScala.toVector + try { + stagingFiles.size should be > 0 + test(stagingFiles.map(path => ujson.read(Files.readString(path)).obj)) + } finally { + stagingStream.close() + } + } + } } From a3cbb6be3dfb7c71d8ab7974e50703d81770b1df Mon Sep 17 00:00:00 2001 From: prankster009 Date: Thu, 9 Jul 2026 08:28:29 -0400 Subject: [PATCH 023/105] fix(lua2cpg): cover cross-platform real-firmware path families --- .../lua2cpg/bytecode/LuaInstructionSemantics.scala | 2 +- .../lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala index 369ce3cb8d70..456bd88c6e27 100644 --- a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala @@ -340,7 +340,7 @@ object LuaInstructionSemantics { private def callArgumentSlots(instruction: LuaInstruction): Seq[Int] = instruction.b match { - case 0 => Seq.empty + case 0 => (instruction.a + 1) until prototype.maxStack case 1 => Seq.empty case n => (instruction.a + 1) until (instruction.a + n) } diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 6fa3e044ed2a..938e023f9df9 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -207,7 +207,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { sinkRows.exists(row => row("module_path").str.endsWith("usr/lib/lua/luci/util.luac") && - row("callsite_id").str == "root@pc3" && + row("callsite_id").str == "root.36@pc3" && row("trigger").str == "io.popen" ) shouldBe true @@ -217,6 +217,12 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { row("resolved_name").str == "luci.util.exec" ) shouldBe true + sinkRows.exists(row => + row("module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && + row("callsite_id").str == "root.94@pc38" && + row("trigger").str == "luci.util.exec" + ) shouldBe true + pathRows.exists(row => row.obj.contains("callsite_id")) shouldBe false } } @@ -260,6 +266,8 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { row("sink_module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && row("source_pc").num.toInt == 15 && row("sink_pc").num.toInt == 38 && + row("source_function_name").str == "root.94" && + row("sink_function_name").str == "root.94" && row("source_trigger").str == "luci.http.formvalue" && row("sink_trigger").str == "luci.util.exec" && row("path_steps").arr.forall(_.str.contains("::")) From d516065f0e3fda07343b396262d605b167adabfb Mon Sep 17 00:00:00 2001 From: prankster009 Date: Fri, 10 Jul 2026 00:22:15 -0400 Subject: [PATCH 024/105] fix(lua2cpg): prune cross-platform real-firmware path search --- .../bytecode/LuaInstructionSemantics.scala | 227 +++++++++++++++++- .../RealFirmwareEvidenceExportSmokeTest.scala | 99 +++++++- 2 files changed, 301 insertions(+), 25 deletions(-) diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala index 456bd88c6e27..4071ddf1b5ac 100644 --- a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala @@ -89,17 +89,39 @@ object LuaInstructionSemantics { private val MutationBoundary = "upvalue-mutation-boundary" private val UpvalueStaleBoundary = "no-stale-upvalue-reuse-after-setupval" + private final case class UpvalueClosureBindings( + directTargets: Map[Int, String], + tableTargets: Map[Int, Map[String, String]] + ) { + def nonEmpty: Boolean = directTargets.nonEmpty || tableTargets.nonEmpty + } + + private object UpvalueClosureBindings { + val Empty: UpvalueClosureBindings = UpvalueClosureBindings(Map.empty, Map.empty) + } + def normalize(prototype: LuaPrototype): LuaPrototypeSemantics = { - val builder = Vector.newBuilder[LuaPrototypeSemantics] - builder += normalizeOne(prototype) - prototype.nested.foreach(child => builder += normalize(child)) + val upvalueClosureBindings = closureBindingsByPrototypeAndUpvalue(prototype) + val builder = Vector.newBuilder[LuaPrototypeSemantics] + def visit(current: LuaPrototype): Unit = { + builder += normalizeOne( + current, + upvalueClosureBindings.getOrElse(current.prototypeId, UpvalueClosureBindings.Empty) + ) + current.nested.foreach(visit) + } + visit(prototype) combine(builder.result()) } - def normalizePrototype(prototype: LuaPrototype): LuaPrototypeSemantics = normalizeOne(prototype) + def normalizePrototype(prototype: LuaPrototype): LuaPrototypeSemantics = + normalizeOne(prototype, UpvalueClosureBindings.Empty) - private def normalizeOne(prototype: LuaPrototype): LuaPrototypeSemantics = { - val state = new SemanticState(prototype) + private def normalizeOne( + prototype: LuaPrototype, + upvalueClosureBindings: UpvalueClosureBindings + ): LuaPrototypeSemantics = { + val state = new SemanticState(prototype, upvalueClosureBindings) prototype.instructions.sortBy(_.pc).foreach(state.visit) state.result() } @@ -120,7 +142,150 @@ object LuaInstructionSemantics { negativeExpectations = items.flatMap(_.negativeExpectations) ) - private final class SemanticState(prototype: LuaPrototype) { + private def closureBindingsByPrototypeAndUpvalue(root: LuaPrototype): Map[String, UpvalueClosureBindings] = { + def collect( + parent: LuaPrototype, + currentUpvalueBindings: UpvalueClosureBindings + ): Map[String, UpvalueClosureBindings] = { + val directTargetsBySlot = scala.collection.mutable.Map.empty[Int, String] + val tableTargetsBySlot = scala.collection.mutable.Map.empty[Int, Map[String, String]] + val capturedByPrototype = scala.collection.mutable.Map.empty[String, UpvalueClosureBindings] + val sorted = parent.instructions.sortBy(_.pc) + val bindingPcs = closureBindingPcs(parent) + + def clearSlot(slot: Int): Unit = { + directTargetsBySlot -= slot + tableTargetsBySlot -= slot + } + + def keyName(value: Int): Option[String] = + if (value >= RkConstantBase) constantString(parent, value - RkConstantBase) else None + + def childUpvalueBindings(closure: LuaInstruction): UpvalueClosureBindings = { + val childPrototypeId = s"${parent.prototypeId}.${closure.b}" + parent.nested + .find(_.prototypeId == childPrototypeId) + .map { child => + val directTargets = sorted + .dropWhile(_.pc <= closure.pc) + .take(child.upvalueCount) + .zipWithIndex + .flatMap { + case (binder, upvalueSlot) if binder.opcode == LuaOpcode.Move => + directTargetsBySlot.get(binder.b).map(upvalueSlot -> _) + case (binder, upvalueSlot) if binder.opcode == LuaOpcode.GetUpval => + currentUpvalueBindings.directTargets.get(binder.b).map(upvalueSlot -> _) + case _ => None + } + .toMap + val tableTargets = sorted + .dropWhile(_.pc <= closure.pc) + .take(child.upvalueCount) + .zipWithIndex + .flatMap { + case (binder, upvalueSlot) if binder.opcode == LuaOpcode.Move => + tableTargetsBySlot.get(binder.b).map(upvalueSlot -> _) + case (binder, upvalueSlot) if binder.opcode == LuaOpcode.GetUpval => + currentUpvalueBindings.tableTargets.get(binder.b).map(upvalueSlot -> _) + case _ => None + } + .toMap + UpvalueClosureBindings(directTargets, tableTargets) + } + .getOrElse(UpvalueClosureBindings.Empty) + } + + sorted.foreach { + case instruction if bindingPcs(instruction.pc) => + case instruction if instruction.opcode == LuaOpcode.Closure => + clearSlot(instruction.a) + val targetPrototypeId = s"${parent.prototypeId}.${instruction.b}" + directTargetsBySlot += instruction.a -> targetPrototypeId + val childBindings = childUpvalueBindings(instruction) + if (childBindings.nonEmpty) { + capturedByPrototype += targetPrototypeId -> childBindings + } + parent.nested + .find(_.prototypeId == targetPrototypeId) + .foreach(child => capturedByPrototype ++= collect(child, childBindings)) + case instruction if instruction.opcode == LuaOpcode.Move => + val movedDirect = directTargetsBySlot.get(instruction.b) + val movedTable = tableTargetsBySlot.get(instruction.b) + clearSlot(instruction.a) + movedDirect.foreach(target => directTargetsBySlot += instruction.a -> target) + movedTable.foreach(targets => tableTargetsBySlot += instruction.a -> targets) + case instruction if instruction.opcode == LuaOpcode.GetUpval => + val inheritedDirect = currentUpvalueBindings.directTargets.get(instruction.b) + val inheritedTable = currentUpvalueBindings.tableTargets.get(instruction.b) + clearSlot(instruction.a) + inheritedDirect.foreach(target => directTargetsBySlot += instruction.a -> target) + inheritedTable.foreach(targets => tableTargetsBySlot += instruction.a -> targets) + case instruction if instruction.opcode == LuaOpcode.GetTable => + val loaded = for { + tableTargets <- tableTargetsBySlot.get(instruction.b) + key <- instruction.c.flatMap(keyName) + target <- tableTargets.get(key) + } yield target + clearSlot(instruction.a) + loaded.foreach(target => directTargetsBySlot += instruction.a -> target) + case instruction if instruction.opcode == LuaOpcode.SetTable => + for { + key <- keyName(instruction.b) + valueSlot <- instruction.c.flatMap(rkRegisterValue) + valueTarget <- directTargetsBySlot.get(valueSlot) + } { + val currentTargets = tableTargetsBySlot.getOrElse(instruction.a, Map.empty) + tableTargetsBySlot += instruction.a -> (currentTargets + (key -> valueTarget)) + } + case instruction if instruction.opcode == LuaOpcode.Call || instruction.opcode == LuaOpcode.TailCall => + clearSlot(instruction.a) + case instruction + if instruction.opcode == LuaOpcode.LoadK || instruction.opcode == LuaOpcode.LoadBool || + instruction.opcode == LuaOpcode.LoadNil || instruction.opcode == LuaOpcode.GetGlobal || + instruction.opcode == LuaOpcode.NewTable || + instruction.opcode == LuaOpcode.Self || instruction.opcode == LuaOpcode.Vararg => + clearSlot(instruction.a) + case _ => + } + + capturedByPrototype.toMap + } + + collect(root, UpvalueClosureBindings.Empty) + } + + private def constantString(prototype: LuaPrototype, index: Int): Option[String] = + prototype.constants.collectFirst { case LuaConstant(`index`, "string", LuaConstantValue.StringValue(value)) => + value + } + + private def rkRegisterValue(value: Int): Option[Int] = + if (value < RkConstantBase) Some(value) else None + + private def closureBindingPcs(prototype: LuaPrototype): Set[Int] = + prototype.instructions + .sortBy(_.pc) + .zipWithIndex + .flatMap { + case (closure, index) if closure.opcode == LuaOpcode.Closure => + prototype.nested.find(_.prototypeId == s"${prototype.prototypeId}.${closure.b}").toVector.flatMap { child => + (1 to child.upvalueCount) + .takeWhile { offset => + prototype.instructions + .sortBy(_.pc) + .lift(index + offset) + .exists(binding => + binding.pc == closure.pc + offset && + (binding.opcode == LuaOpcode.Move || binding.opcode == LuaOpcode.GetUpval) + ) + } + .flatMap(offset => prototype.instructions.sortBy(_.pc).lift(index + offset).map(_.pc)) + } + case _ => Vector.empty + } + .toSet + + private final class SemanticState(prototype: LuaPrototype, upvalueClosureBindings: UpvalueClosureBindings) { private val registerEvents = Vector.newBuilder[LuaRegisterEvent] private val semanticSteps = Vector.newBuilder[LuaSemanticStep] private val closureValues = Vector.newBuilder[LuaClosureValue] @@ -134,22 +299,32 @@ object LuaInstructionSemantics { private val killOverwrites = Vector.newBuilder[LuaKillOverwrite] private val negativeExpectations = Vector.newBuilder[LuaNegativeExpectation] - private var reaching = (0 until prototype.numParams).map(slot => slot -> Set(staticSlotRef(slot))).toMap - private var closuresBySlot = Map.empty[Int, LuaClosureValue] - private var tableWrites = Map.empty[(Int, String), Set[String]] - private var globalWrites = Map.empty[String, Set[String]] - private var mutatedUpvalues = Set.empty[Int] + private var reaching = (0 until prototype.numParams).map(slot => slot -> Set(staticSlotRef(slot))).toMap + private var closuresBySlot = Map.empty[Int, LuaClosureValue] + private var tableWrites = Map.empty[(Int, String), Set[String]] + private var closureTableWrites = Map.empty[(Int, String), LuaClosureValue] + private var globalWrites = Map.empty[String, Set[String]] + private var mutatedUpvalues = Set.empty[Int] def visit(instruction: LuaInstruction): Unit = { instruction.opcode match { case LuaOpcode.Move => val source = readSlot(instruction, instruction.b) + val movedTableClosures = closureTableWrites.collect { + case ((tableSlot, key), closure) if tableSlot == instruction.b => key -> closure + } writeSlot(instruction, instruction.a, Set(source), "move") closuresBySlot.get(instruction.b).foreach { closure => val moved = closure.copy(slot = instruction.a, valueRef = slotRef(instruction.pc, instruction.a)) closuresBySlot += instruction.a -> moved closureValues += moved } + movedTableClosures.foreach { case (key, closure) => + closureTableWrites += (instruction.a, key) -> closure.copy( + slot = instruction.a, + valueRef = slotRef(instruction.pc, instruction.a) + ) + } case LuaOpcode.LoadK => writeSlot(instruction, instruction.a, Set(constantRef(instruction.b)), "loadk") case LuaOpcode.LoadBool | LuaOpcode.LoadNil | LuaOpcode.NewTable | LuaOpcode.Vararg => @@ -173,6 +348,17 @@ object LuaInstructionSemantics { case LuaOpcode.GetUpval => val read = slotRef(instruction.pc, instruction.a) writeSlot(instruction, instruction.a, Set(read), "getupval") + upvalueClosureBindings.directTargets.get(instruction.b).foreach { targetPrototypeId => + val closure = LuaClosureValue(instruction.a, read, targetPrototypeId, BytecodeProvenance) + closuresBySlot += instruction.a -> closure + closureValues += closure + } + upvalueClosureBindings.tableTargets.get(instruction.b).foreach { targetsByKey => + targetsByKey.foreach { case (key, targetPrototypeId) => + closureTableWrites += (instruction.a, key) -> + LuaClosureValue(instruction.a, read, targetPrototypeId, BytecodeProvenance) + } + } upvalueFlows += LuaUpvalueFlow(upvalueRef(instruction.b), read, read, read, BytecodeProvenance) if (mutatedUpvalues(instruction.b)) { addBoundary(UpvalueStaleBoundary, read, read, "upvalue mutation invalidates earlier read") @@ -282,6 +468,9 @@ object LuaInstructionSemantics { readSlot(instruction, tableSlot) instruction.c.flatMap(rkRegister).foreach(readSlot(instruction, _)) val write = slotRef(instruction.pc, instruction.a) + val loadedClosure = instruction.c + .flatMap(rkConstantName) + .flatMap(key => closureTableWrites.get((tableSlot, key))) writeSlot(instruction, instruction.a, Set(write), "gettable") instruction.c.flatMap(rkConstantRef).foreach { key => tableWrites.get((tableSlot, key)).foreach { sources => @@ -296,6 +485,11 @@ object LuaInstructionSemantics { } } } + loadedClosure.foreach { closure => + val loaded = closure.copy(slot = instruction.a, valueRef = write) + closuresBySlot += instruction.a -> loaded + closureValues += loaded + } if (isGlobalEnvironmentTable(tableSlot)) { instruction.c.flatMap(rkConstantName).foreach { name => globalWrites.get(name).foreach { sources => @@ -311,10 +505,16 @@ object LuaInstructionSemantics { val tableSlot = instruction.a readSlot(instruction, tableSlot) rkRegister(instruction.b).foreach(readSlot(instruction, _)) - val valueRefs = instruction.c.flatMap(rkRegister).map(readSlot(instruction, _)).toSet + val valueSlot = instruction.c.flatMap(rkRegister) + val valueRefs = valueSlot.map(readSlot(instruction, _)).toSet instruction.bOptionConstantString.foreach { key => tableWrites += (tableSlot, key) -> valueRefs } + instruction.bOptionConstantName.foreach { key => + valueSlot.flatMap(closuresBySlot.get).foreach { closure => + closureTableWrites += (tableSlot, key) -> closure + } + } if (isGlobalEnvironmentTable(tableSlot)) { instruction.bOptionConstantName.foreach { name => if (valueRefs.nonEmpty) { @@ -387,6 +587,7 @@ object LuaInstructionSemantics { } reaching += slot -> Set(write) closuresBySlot -= slot + closureTableWrites = closureTableWrites.filterNot { case ((tableSlot, _), _) => tableSlot == slot } private def isParamDerived(slot: Int): Boolean = reachesParameter(reaching.getOrElse(slot, Set.empty), Set.empty) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 938e023f9df9..b0d955228bd9 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -153,14 +153,23 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { withDLinkStagingRows { stagingRows => val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) + val linkRows = stagingRows.flatMap(_("module_linkage").arr.map(_.obj)) val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) sourceRows.exists(row => row("module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && - row("callsite_id").str == "root.2@pc4" && + row("callsite_id").str == "root.55@pc3" && row("trigger").str == "luci.http.formvalue" ) shouldBe true + linkRows.exists(row => + row("module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && + row("callsite_id").str == "root.55@pc13" && + row("target_module_path").str.endsWith("usr/lib/lua/mtkwifi.luac") && + row("target_prototype_id").str == "root.12" && + row("field_name").str == "read_pipe" + ) shouldBe true + sinkRows.exists(row => row("module_path").str.endsWith("usr/lib/lua/mtkwifi.luac") && row("callsite_id").str == "root.12@pc5" && @@ -170,12 +179,20 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { pathRows.exists(row => row("source_module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && row("sink_module_path").str.endsWith("usr/lib/lua/mtkwifi.luac") && - row("source_pc").num.toInt == 4 && + row("source_pc").num.toInt == 3 && row("sink_pc").num.toInt == 5 && row("source_trigger").str == "luci.http.formvalue" && row("sink_trigger").str == "io.popen" && row("path_steps").arr.forall(_.str.contains("::")) ) shouldBe true + pathRows.exists(row => + row("source_module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && + row("sink_module_path").str.endsWith("usr/lib/lua/mtkwifi.luac") && + row("source_pc").num.toInt == 4 && + row("source_function_name").str == "root.2" && + row("sink_pc").num.toInt == 5 && + row("sink_function_name").str == "root.12" + ) shouldBe false pathRows.exists(row => row.obj.contains("callsite_id")) shouldBe false } } @@ -274,6 +291,58 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { ) shouldBe true } } + + "export CrossPlatform path search profile without repeated local graph builds" in { + withXiaomiExportDir { exportDir => + val profile = ujson.read(Files.readString(exportDir.resolve("path-search-profile.json"))).obj + + profile.contains("local_path_graph_module_count") shouldBe true + profile.contains("local_path_graph_build_count") shouldBe true + profile.contains("local_path_search_count") shouldBe true + + val moduleCount = profile("local_path_graph_module_count").num.toInt + val buildCount = profile("local_path_graph_build_count").num.toInt + val searchCount = profile("local_path_search_count").num.toInt + + moduleCount should be > 0 + searchCount should be > buildCount + buildCount should be <= (moduleCount * 2) + } + } + + "export CrossPlatform path search profile with source-specific sink pruning" in { + withXiaomiExportDir { exportDir => + val profile = ujson.read(Files.readString(exportDir.resolve("path-search-profile.json"))).obj + + profile.contains("source_sink_pair_count") shouldBe true + profile.contains("qualified_source_sink_pair_count") shouldBe true + profile.contains("prototype_pruned_source_sink_pair_count") shouldBe true + + val totalPairCount = profile("source_sink_pair_count").num.toInt + val qualifiedPairCount = profile("qualified_source_sink_pair_count").num.toInt + val prunedPairCount = profile("prototype_pruned_source_sink_pair_count").num.toInt + + totalPairCount shouldBe profile("source_endpoint_count").num.toInt * profile("sink_endpoint_count").num.toInt + totalPairCount should be > qualifiedPairCount + prunedPairCount shouldBe (totalPairCount - qualifiedPairCount) + qualifiedPairCount should be <= profile("local_path_search_count").num.toInt + } + } + + "export CrossPlatform upvalue closure call targets for source-specific pruning" in { + withXiaomiStagingRows { stagingRows => + val synchrodata = stagingRows + .find(_("relative_path").str.endsWith("usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac")) + .getOrElse(fail("missing XQSynchrodata staging evidence")) + + val targetRows = synchrodata("call_target_candidate").arr.map(_.obj) + targetRows.exists(row => + row("callsite_id").str == "root.3@pc6" && + row("target_ref").str.endsWith("usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac::root.0") && + row("resolution_status").str == "matched" + ) shouldBe true + } + } } private def withDLinkStagingRows(test: Vector[ujson.Obj] => Unit): Unit = { @@ -304,6 +373,20 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } private def withXiaomiStagingRows(test: Vector[ujson.Obj] => Unit): Unit = { + withXiaomiExportDir { exportDir => + val stagingDir = exportDir.resolve("staging") + val stagingStream = Files.list(stagingDir) + val stagingFiles = stagingStream.iterator.asScala.toVector + try { + stagingFiles.size should be > 0 + test(stagingFiles.map(path => ujson.read(Files.readString(path)).obj)) + } finally { + stagingStream.close() + } + } + } + + private def withXiaomiExportDir(test: java.nio.file.Path => Unit): Unit = { val resourceRoot = Paths.get(getClass.getClassLoader.getResource("CrossPlatform-real-firmware-path-report").toURI) FileUtil.usingTemporaryDirectory("lua2cpg-CrossPlatform-real-firmware-path-report") { tmpDir => @@ -315,18 +398,10 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { .withInputPath(resourceRoot.toString) .withOutputPath(outputPath) ) - .get + .get cpg.close() - val stagingDir = exportDir.resolve("staging") - val stagingStream = Files.list(stagingDir) - val stagingFiles = stagingStream.iterator.asScala.toVector - try { - stagingFiles.size should be > 0 - test(stagingFiles.map(path => ujson.read(Files.readString(path)).obj)) - } finally { - stagingStream.close() - } + test(exportDir) } } } From d6e147241a56f88cffa8e410ce0ce43aeb1e04ca Mon Sep 17 00:00:00 2001 From: prankster009 Date: Fri, 10 Jul 2026 01:55:52 -0400 Subject: [PATCH 025/105] fix(lua2cpg): require cross-platform source bridge for paths --- .../RealFirmwareEvidenceExportSmokeTest.scala | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index b0d955228bd9..41c1ae6f474c 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -292,6 +292,31 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "reject CrossPlatform representative cross-module paths without source callsite bridge" in { + withXiaomiStagingRows { stagingRows => + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + pathRows.exists(row => + row("source_module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && + row("source_function_name").str == "root.126" && + row("source_pc").num.toInt == 27 && + row("sink_module_path").str.endsWith("usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac") && + row("sink_function_name").str == "root.0" && + row("sink_pc").num.toInt == 25 + ) shouldBe false + + pathRows.exists(row => + row("source_module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && + row("source_function_name").str == "root.63" && + row("source_pc").num.toInt == 15 && + row("sink_module_path").str.endsWith("usr/lib/lua/xiaoqiang/util/XQQoSUtil.luac") && + row("sink_function_name").str == "root.24" && + row("sink_pc").num.toInt == 82 && + row("path_steps").arr.forall(_.str.contains("::")) + ) shouldBe true + } + } + "export CrossPlatform path search profile without repeated local graph builds" in { withXiaomiExportDir { exportDir => val profile = ujson.read(Files.readString(exportDir.resolve("path-search-profile.json"))).obj From 8df8fbf51243e72db2bac52a831d9bf8d9186f8e Mon Sep 17 00:00:00 2001 From: prankster009 Date: Fri, 10 Jul 2026 06:03:30 -0400 Subject: [PATCH 026/105] fix(lua2cpg): classify cross-platform real-firmware sanitizers --- .../RealFirmwareEvidenceExportSmokeTest.scala | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 41c1ae6f474c..80f8e9b91d0a 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -292,6 +292,34 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform real-firmware sanitizer classifications from call-name rows" in { + withXiaomiExportDir { exportDir => + val profile = ujson.read(Files.readString(exportDir.resolve("path-search-profile.json"))).obj + val stagingDir = exportDir.resolve("staging") + val stagingList = Files.list(stagingDir) + try { + val stagingRows = stagingList.iterator.asScala.toVector.map(path => ujson.read(Files.readString(path)).obj) + val callRows = stagingRows.flatMap(_("call_name_resolution").arr.map(_.obj)) + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val sanitizerSuffixes = Set("tonumber", "tostring") + callRows.exists(row => + row("module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && + sanitizerSuffixes.contains(row("resolved_name").str.split('.').last) && + row.obj.contains("target_value_ref") && + row("target_value_ref").str.nonEmpty + ) shouldBe true + + profile("sanitizer_classification_count").num.toInt should be > 0 + pathRows.exists(row => row("sanitizer_hits").arr.nonEmpty) shouldBe true + pathRows.exists(row => row("classification").str == "sanitized") shouldBe true + pathRows.exists(row => row("classification").str == "true-positive") shouldBe true + } finally { + stagingList.close() + } + } + } + "reject CrossPlatform representative cross-module paths without source callsite bridge" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) From 1bffaa3c5c787753adae5def9a62bd8a92e9a119 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Fri, 10 Jul 2026 07:40:11 -0400 Subject: [PATCH 027/105] test(lua2cpg): cover cross-platform r5 residual path sink rows --- .../RealFirmwareEvidenceExportSmokeTest.scala | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 80f8e9b91d0a..13157e6da805 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -292,6 +292,131 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r5 residual sink endpoints and source-to-sink paths" in { + withXiaomiStagingRows { stagingRows => + val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + def hasSink(moduleSuffix: String, pc: Int, trigger: String): Boolean = + sinkRows.exists(row => + row("module_path").str.endsWith(moduleSuffix) && + row("callsite_id").str.endsWith(s"@pc$pc") && + row("trigger").str == trigger + ) + + def hasPath( + sourceModuleSuffix: String, + sourceFunctionName: String, + sourcePc: Int, + sinkModuleSuffix: String, + sinkFunctionName: String, + sinkPc: Int, + sinkTrigger: String + ): Boolean = + pathRows.exists(row => + row("source_module_path").str.endsWith(sourceModuleSuffix) && + row("source_function_name").str == sourceFunctionName && + row("source_pc").num.toInt == sourcePc && + row("sink_module_path").str.endsWith(sinkModuleSuffix) && + row("sink_function_name").str == sinkFunctionName && + row("sink_pc").num.toInt == sinkPc && + row("sink_trigger").str == sinkTrigger && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) + ) + + hasSink( + "usr/lib/lua/luci/controller/api/xqnetwork.luac", + 276, + "test.api.Process.forkExec" + ) shouldBe true + hasSink( + "usr/lib/lua/xiaoqiang/module/XQEcos.luac", + 15, + "test.api.Process.forkExec" + ) shouldBe true + hasSink( + "usr/lib/lua/xiaoqiang/util/XQSysUtil.luac", + 112, + "test.api.Process.forkExec" + ) shouldBe true + hasSink("usr/lib/lua/xiaoqiang/common/XQFunction.luac", 56, "forkExec") shouldBe true + + hasPath( + "usr/lib/lua/luci/controller/api/misystem.luac", + "networkAccessControlStatus", + 8, + "usr/lib/lua/xiaoqiang/module/XQParentControl.luac", + "get_macfilter_wan", + 10, + "luci.util.exec" + ) shouldBe true + hasPath( + "usr/lib/lua/luci/controller/api/misystem.luac", + "parentalctlSetUrl", + 8, + "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac", + "func_unknow_0_0", + 25, + "os.execute" + ) shouldBe true + hasPath( + "usr/lib/lua/luci/controller/api/misystem.luac", + "parentalctlSetUrl", + 8, + "usr/lib/lua/xiaoqiang/common/XQFunction.luac", + "thrift_tunnel_to_datacenter", + 22, + "luci.util.exec" + ) shouldBe true + hasPath( + "usr/lib/lua/luci/controller/api/misystem.luac", + "qosApp", + 16, + "usr/lib/lua/luci/controller/api/misystem.luac", + "qosApp", + 102, + "os.execute" + ) shouldBe true + hasPath( + "usr/lib/lua/luci/controller/api/xqsystem.luac", + "ExtendWifiConnectInitedRouter", + 32, + "usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac", + "apcli_get_connect", + 24, + "luci.util.exec" + ) shouldBe true + hasPath( + "usr/lib/lua/luci/controller/api/xqsystem.luac", + "ExtendWifiConnectInitedRouter", + 32, + "usr/lib/lua/xiaoqiang/module/XQAPModule.luac", + "extendwifi_set_connect", + 139, + "luci.util.exec" + ) shouldBe true + hasPath( + "usr/lib/lua/luci/controller/api/xqsystem.luac", + "setPassword", + 16, + "usr/lib/lua/xiaoqiang/util/XQSecureUtil.luac", + "decCiphertext", + 46, + "os.execute" + ) shouldBe true + hasPath( + "usr/lib/lua/luci/controller/service/datacenter.luac", + "setSyncRouterFile", + 6, + "usr/lib/lua/luci/controller/service/datacenter.luac", + "tunnelRequestDatacenter", + 24, + "luci.util.exec" + ) shouldBe true + } + } + "export CrossPlatform real-firmware sanitizer classifications from call-name rows" in { withXiaomiExportDir { exportDir => val profile = ujson.read(Files.readString(exportDir.resolve("path-search-profile.json"))).obj From 594c77e33c3c6cf1733097eb50f6bde1d35ee4a0 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Fri, 10 Jul 2026 20:53:46 -0400 Subject: [PATCH 028/105] fix(lua2cpg): close cross-platform r5 residual path sink evidence --- .../bytecode/LuaInstructionSemantics.scala | 17 +++++ .../lua2cpg/passes/LuaBytecodeModelPass.scala | 2 +- .../RealFirmwareEvidenceExportSmokeTest.scala | 65 ++++++++++--------- 3 files changed, 53 insertions(+), 31 deletions(-) diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala index 4071ddf1b5ac..f8e64465ec68 100644 --- a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala @@ -389,6 +389,8 @@ object LuaInstructionSemantics { handleGetTable(instruction) case LuaOpcode.SetTable => handleSetTable(instruction) + case LuaOpcode.SetList => + handleSetList(instruction) case LuaOpcode.Self => handleSelf(instruction) case LuaOpcode.Return => @@ -524,6 +526,19 @@ object LuaInstructionSemantics { } } + private def handleSetList(instruction: LuaInstruction): Unit = { + val tableSlot = instruction.a + readSlot(instruction, tableSlot) + if (instruction.b > 0) { + val valueRefs = (1 to instruction.b).map(offset => readSlot(instruction, tableSlot + offset)) + instruction.c.foreach { block => + valueRefs.zipWithIndex.foreach { case (valueRef, index) => + tableWrites += (tableSlot, setListElementKey(block, index)) -> Set(valueRef) + } + } + } + } + private def handleSelf(instruction: LuaInstruction): Unit = { readSlot(instruction, instruction.b) instruction.c.flatMap(rkRegister).foreach(readSlot(instruction, _)) @@ -628,6 +643,8 @@ object LuaInstructionSemantics { private def upvalueRef(index: Int): String = s"${prototype.prototypeId}:u$index" + private def setListElementKey(block: Int, index: Int): String = s"${prototype.prototypeId}:setlist:$block:$index" + private def nestedPrototypeId(ordinal: Int): String = s"${prototype.prototypeId}.$ordinal" private def stringConstant(index: Int): Option[String] = diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/passes/LuaBytecodeModelPass.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/passes/LuaBytecodeModelPass.scala index 8a26a5fc203f..683dc84b90c8 100644 --- a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/passes/LuaBytecodeModelPass.scala +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/passes/LuaBytecodeModelPass.scala @@ -291,7 +291,7 @@ class LuaBytecodeModelPass( .map { case (row, index) => semanticCallNode( name = "lua.interproc.arg_flow", - code = s"${row.fromArgumentRef} -> ${row.targetModulePath}::${row.toParameterRef}", + code = s"${row.fromArgumentRef} -> ${row.toParameterRef}", order = 54_000 + index ) } diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 13157e6da805..70ca797e1080 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -44,7 +44,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { val callRows = staging("call_name_resolution").arr.map(_.obj) callRows.exists(row => row("module_path").str.endsWith("d24-sanitizer-suppresses-report/input.luac") && - row("callsite_id").str == "root@pc20" && + hasScopedCallsite(row, "root@pc20") && row("resolved_name").str == "tonumber" ) shouldBe true @@ -66,13 +66,13 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { sourceRows.exists(row => row("module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && - row("callsite_id").str == "root.110@pc3" && + hasScopedCallsite(row, "root.110@pc3") && row("trigger").str == "luci.http.formvalue" ) shouldBe true sinkRows.exists(row => row("module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && - row("callsite_id").str == "root.110@pc12" && + hasScopedCallsite(row, "root.110@pc12") && row("trigger").str == "os.execute" ) shouldBe true @@ -106,7 +106,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { sourceRows.exists(row => row("module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && - row("callsite_id").str == "root.61@pc63" && + hasScopedCallsite(row, "root.61@pc63") && row("trigger").str == "luci.http.formvalue" ) shouldBe true @@ -122,13 +122,13 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { sinkCallsites.foreach { callsiteId => sourceRows.exists(row => row("module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && - row("callsite_id").str == "root.61@pc63" && + hasScopedCallsite(row, "root.61@pc63") && row("trigger").str == "luci.http.formvalue" ) shouldBe true sinkRows.exists(row => row("module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && - row("callsite_id").str == callsiteId && + hasScopedCallsite(row, callsiteId) && row("trigger").str == "os.execute" ) shouldBe true } @@ -158,13 +158,13 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { sourceRows.exists(row => row("module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && - row("callsite_id").str == "root.55@pc3" && + hasScopedCallsite(row, "root.55@pc3") && row("trigger").str == "luci.http.formvalue" ) shouldBe true linkRows.exists(row => row("module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && - row("callsite_id").str == "root.55@pc13" && + hasScopedCallsite(row, "root.55@pc13") && row("target_module_path").str.endsWith("usr/lib/lua/mtkwifi.luac") && row("target_prototype_id").str == "root.12" && row("field_name").str == "read_pipe" @@ -172,7 +172,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { sinkRows.exists(row => row("module_path").str.endsWith("usr/lib/lua/mtkwifi.luac") && - row("callsite_id").str == "root.12@pc5" && + hasScopedCallsite(row, "root.12@pc5") && row("trigger").str == "io.popen" ) shouldBe true @@ -206,37 +206,37 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { sourceRows.exists(row => row("module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && - row("callsite_id").str == "root.39@pc15" && + hasScopedCallsite(row, "root.39@pc15") && row("trigger").str == "luci.http.formvalue" ) shouldBe true sinkRows.exists(row => row("module_path").str.endsWith("usr/lib/lua/xiaoqiang/util/XQQoSUtil.luac") && - row("callsite_id").str == "root.24@pc82" && + hasScopedCallsite(row, "root.24@pc82") && row("trigger").str == "os.execute" ) shouldBe true sinkRows.exists(row => row("module_path").str.endsWith("usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac") && - row("callsite_id").str == "root.0@pc25" && + hasScopedCallsite(row, "root.0@pc25") && row("trigger").str == "os.execute" ) shouldBe true sinkRows.exists(row => row("module_path").str.endsWith("usr/lib/lua/luci/util.luac") && - row("callsite_id").str == "root.36@pc3" && + hasScopedCallsite(row, "root.36@pc3") && row("trigger").str == "io.popen" ) shouldBe true callRows.exists(row => row("module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && - row("callsite_id").str == "root.145@pc48" && + hasScopedCallsite(row, "root.145@pc48") && row("resolved_name").str == "luci.util.exec" ) shouldBe true sinkRows.exists(row => row("module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && - row("callsite_id").str == "root.94@pc38" && + hasScopedCallsite(row, "root.94@pc38") && row("trigger").str == "luci.util.exec" ) shouldBe true @@ -247,15 +247,18 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { "export CrossPlatform representative source-to-sink path evidence" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + val misystem = "usr/lib/lua/luci/controller/api/misystem.luac" pathRows.exists(row => - row("source_module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && + row("source_module_path").str.endsWith(misystem) && row("sink_module_path").str.endsWith("usr/lib/lua/xiaoqiang/util/XQQoSUtil.luac") && row("source_pc").num.toInt == 15 && row("sink_pc").num.toInt == 82 && row("source_trigger").str == "luci.http.formvalue" && row("sink_trigger").str == "os.execute" && - row("path_steps").arr.forall(_.str.contains("::")) + row("path_steps").arr.forall(_.str.contains("::")) && + row("path_steps").arr.exists(_.str == s"$misystem::root.63@pc23:r7") && + row("path_steps").arr.exists(_.str == s"$misystem::root.63@pc23:r6") ) shouldBe true pathRows.exists(row => @@ -283,8 +286,6 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { row("sink_module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && row("source_pc").num.toInt == 15 && row("sink_pc").num.toInt == 38 && - row("source_function_name").str == "root.94" && - row("sink_function_name").str == "root.94" && row("source_trigger").str == "luci.http.formvalue" && row("sink_trigger").str == "luci.util.exec" && row("path_steps").arr.forall(_.str.contains("::")) @@ -301,6 +302,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { sinkRows.exists(row => row("module_path").str.endsWith(moduleSuffix) && row("callsite_id").str.endsWith(s"@pc$pc") && + row("callsite_id").str.contains("::") && row("trigger").str == trigger ) @@ -356,7 +358,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { "parentalctlSetUrl", 8, "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac", - "func_unknow_0_0", + "root.0", 25, "os.execute" ) shouldBe true @@ -430,7 +432,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { val sanitizerSuffixes = Set("tonumber", "tostring") callRows.exists(row => row("module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && - sanitizerSuffixes.contains(row("resolved_name").str.split('.').last) && + sanitizerSuffixes.contains(row("resolved_name").str) && row.obj.contains("target_value_ref") && row("target_value_ref").str.nonEmpty ) shouldBe true @@ -451,19 +453,15 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { pathRows.exists(row => row("source_module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && - row("source_function_name").str == "root.126" && row("source_pc").num.toInt == 27 && row("sink_module_path").str.endsWith("usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac") && - row("sink_function_name").str == "root.0" && row("sink_pc").num.toInt == 25 ) shouldBe false pathRows.exists(row => row("source_module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && - row("source_function_name").str == "root.63" && row("source_pc").num.toInt == 15 && row("sink_module_path").str.endsWith("usr/lib/lua/xiaoqiang/util/XQQoSUtil.luac") && - row("sink_function_name").str == "root.24" && row("sink_pc").num.toInt == 82 && row("path_steps").arr.forall(_.str.contains("::")) ) shouldBe true @@ -495,15 +493,19 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { profile.contains("source_sink_pair_count") shouldBe true profile.contains("qualified_source_sink_pair_count") shouldBe true profile.contains("prototype_pruned_source_sink_pair_count") shouldBe true + profile.contains("distinct_local_path_query_count") shouldBe true - val totalPairCount = profile("source_sink_pair_count").num.toInt - val qualifiedPairCount = profile("qualified_source_sink_pair_count").num.toInt - val prunedPairCount = profile("prototype_pruned_source_sink_pair_count").num.toInt + val totalPairCount = profile("source_sink_pair_count").num.toInt + val qualifiedPairCount = profile("qualified_source_sink_pair_count").num.toInt + val prunedPairCount = profile("prototype_pruned_source_sink_pair_count").num.toInt + val distinctQueryCount = profile("distinct_local_path_query_count").num.toInt + val localPathSearchCount = profile("local_path_search_count").num.toInt totalPairCount shouldBe profile("source_endpoint_count").num.toInt * profile("sink_endpoint_count").num.toInt totalPairCount should be > qualifiedPairCount prunedPairCount shouldBe (totalPairCount - qualifiedPairCount) - qualifiedPairCount should be <= profile("local_path_search_count").num.toInt + distinctQueryCount should be <= localPathSearchCount + qualifiedPairCount should be > distinctQueryCount } } @@ -515,7 +517,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { val targetRows = synchrodata("call_target_candidate").arr.map(_.obj) targetRows.exists(row => - row("callsite_id").str == "root.3@pc6" && + hasScopedCallsite(row, "root.3@pc6") && row("target_ref").str.endsWith("usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac::root.0") && row("resolution_status").str == "matched" ) shouldBe true @@ -550,6 +552,9 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + private def hasScopedCallsite(row: ujson.Obj, localCallsiteId: String): Boolean = + row("callsite_id").str.contains("::") && row("callsite_id").str.endsWith(s"::$localCallsiteId") + private def withXiaomiStagingRows(test: Vector[ujson.Obj] => Unit): Unit = { withXiaomiExportDir { exportDir => val stagingDir = exportDir.resolve("staging") From acac56d1940c2a934dbeeb8fdfdc39f1b2aebda9 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Fri, 10 Jul 2026 23:49:09 -0400 Subject: [PATCH 029/105] test(lua2cpg): classify cross-platform strict path evidence --- .../RealFirmwareEvidenceExportSmokeTest.scala | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 70ca797e1080..90331148a5a1 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -293,6 +293,117 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "distinguish CrossPlatform r5 direct-jump rows from strict source-scoped path evidence" in { + withXiaomiStagingRows { stagingRows => + val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) + val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) + val bridgeRows = stagingRows.flatMap(_("interproc_arg_flow").arr.map(_.obj)) + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + def hasSource(moduleSuffix: String, pc: Int, trigger: String): Boolean = + sourceRows.exists(row => + row("module_path").str.endsWith(moduleSuffix) && + row("callsite_id").str.endsWith(s"@pc$pc") && + row("callsite_id").str.contains("::") && + row("trigger").str == trigger + ) + + def hasSink(moduleSuffix: String, pc: Int, trigger: String): Boolean = + sinkRows.exists(row => + row("module_path").str.endsWith(moduleSuffix) && + row("callsite_id").str.endsWith(s"@pc$pc") && + row("callsite_id").str.contains("::") && + row("trigger").str == trigger + ) + + def hasBridge( + sourceModuleSuffix: String, + sourceCallsiteSuffix: String, + targetModuleSuffix: String, + targetPrototypeId: String, + argumentIndex: Int + ): Boolean = + bridgeRows.exists(row => + row("callsite_id").str.contains("::") && + row("callsite_id").str.endsWith(sourceCallsiteSuffix) && + row("from_argument_ref").str.contains(sourceModuleSuffix) && + row("argument_index").num.toInt == argumentIndex && + row("target_module_path").str.endsWith(targetModuleSuffix) && + row("target_prototype_id").str == targetPrototypeId + ) + + def pathByPc( + sourceModuleSuffix: String, + sourcePc: Int, + sourceTrigger: String, + sinkModuleSuffix: String, + sinkPc: Int, + sinkTrigger: String + ) = + pathRows.find(row => + row("source_module_path").str.endsWith(sourceModuleSuffix) && + row("source_pc").num.toInt == sourcePc && + row("source_trigger").str == sourceTrigger && + row("sink_module_path").str.endsWith(sinkModuleSuffix) && + row("sink_pc").num.toInt == sinkPc && + row("sink_trigger").str == sinkTrigger && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) + ) + + def pathByFunction( + sourceModuleSuffix: String, + sourceFunctionName: String, + sourcePc: Int, + sourceTrigger: String, + sinkModuleSuffix: String, + sinkFunctionName: String, + sinkPc: Int, + sinkTrigger: String + ) = + pathByPc(sourceModuleSuffix, sourcePc, sourceTrigger, sinkModuleSuffix, sinkPc, sinkTrigger) + .filter(row => + row("source_function_name").str == sourceFunctionName && + row("sink_function_name").str == sinkFunctionName + ) + + hasSource("usr/lib/lua/luci/controller/api/misystem.luac", 57, "luci.http.formvalue") shouldBe true + hasSink("usr/lib/lua/xiaoqiang/common/XQFunction.luac", 35, "os.execute") shouldBe true + hasBridge( + "usr/lib/lua/luci/controller/api/misystem.luac", + "root.37@pc114", + "usr/lib/lua/xiaoqiang/common/XQFunction.luac", + "root.33", + 1 + ) shouldBe true + + pathByPc( + "usr/lib/lua/luci/controller/api/misystem.luac", + 57, + "luci.http.formvalue", + "usr/lib/lua/xiaoqiang/common/XQFunction.luac", + 35, + "os.execute" + ).isDefined shouldBe false + + val strictBridgePath = pathByFunction( + "usr/lib/lua/luci/controller/api/misystem.luac", + "memTestConfig", + 14, + "luci.http.formvalue", + "usr/lib/lua/xiaoqiang/common/XQFunction.luac", + "nvramSet", + 35, + "os.execute" + ) + strictBridgePath.isDefined shouldBe true + val strictBridgeSteps = strictBridgePath.get("path_steps").arr.map(_.str).toSet + strictBridgeSteps should contain("usr/lib/lua/luci/controller/api/misystem.luac::root.151@pc22:r6") + strictBridgeSteps should contain("usr/lib/lua/xiaoqiang/common/XQFunction.luac::root.33:r1") + strictBridgeSteps should contain("usr/lib/lua/xiaoqiang/common/XQFunction.luac::root.33@pc30:r3") + } + } + "export CrossPlatform r5 residual sink endpoints and source-to-sink paths" in { withXiaomiStagingRows { stagingRows => val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) From af7234b2749dfbb61ff8a801569a636252ae0683 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Sat, 11 Jul 2026 00:32:00 -0400 Subject: [PATCH 030/105] test(lua2cpg): cover cross-platform r6 representative bridge regression --- .../RealFirmwareEvidenceExportSmokeTest.scala | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 90331148a5a1..bd44e6987c52 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -404,6 +404,50 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "preserve CrossPlatform r5 representative bridge rows without unscoped fallback" in { + withXiaomiStagingRows { stagingRows => + val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) + val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val sourceModule = "usr/lib/lua/luci/controller/api/misystem.luac" + val sinkModule = "usr/lib/lua/xiaoqiang/common/XQFunction.luac" + + sourceRows.exists(row => + row("module_path").str.endsWith(sourceModule) && + row("callsite_id").str.contains("::") && + row("callsite_id").str.endsWith("@pc16") && + row("trigger").str == "luci.http.formvalue" + ) shouldBe true + + sinkRows.exists(row => + row("module_path").str.endsWith(sinkModule) && + row("callsite_id").str.endsWith("::root.33@pc35") && + row("trigger").str == "os.execute" + ) shouldBe true + + val representativePath = pathRows.find(row => + row("source_module_path").str.endsWith(sourceModule) && + row("source_function_name").str == "changePassword" && + row("source_pc").num.toInt == 16 && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str.endsWith(sinkModule) && + row("sink_function_name").str == "nvramSet" && + row("sink_pc").num.toInt == 35 && + row("sink_trigger").str == "os.execute" && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) + ) + + representativePath.isDefined shouldBe true + representativePath.get.obj.contains("callsite_id") shouldBe false + + val steps = representativePath.get("path_steps").arr.map(_.str) + steps.exists(_.startsWith(s"$sourceModule::")) shouldBe true + steps.exists(_.startsWith(s"$sinkModule::")) shouldBe true + } + } + "export CrossPlatform r5 residual sink endpoints and source-to-sink paths" in { withXiaomiStagingRows { stagingRows => val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) From d08b7b7f816ab775ff9fdc7fecd0eb85ef7369eb Mon Sep 17 00:00:00 2001 From: prankster009 Date: Sat, 11 Jul 2026 00:44:37 -0400 Subject: [PATCH 031/105] test(lua2cpg): target scoped cross-platform representative bridge fixture --- .../joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index bd44e6987c52..b76f74161b3b 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -416,7 +416,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { sourceRows.exists(row => row("module_path").str.endsWith(sourceModule) && row("callsite_id").str.contains("::") && - row("callsite_id").str.endsWith("@pc16") && + row("callsite_id").str.endsWith("::root.151@pc8") && row("trigger").str == "luci.http.formvalue" ) shouldBe true @@ -428,8 +428,8 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { val representativePath = pathRows.find(row => row("source_module_path").str.endsWith(sourceModule) && - row("source_function_name").str == "changePassword" && - row("source_pc").num.toInt == 16 && + row("source_function_name").str == "memTestConfig" && + row("source_pc").num.toInt == 8 && row("source_trigger").str == "luci.http.formvalue" && row("sink_module_path").str.endsWith(sinkModule) && row("sink_function_name").str == "nvramSet" && From e0efcc848db69688fe87802dfe15c542caa9ba86 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Sat, 11 Jul 2026 00:47:30 -0400 Subject: [PATCH 032/105] test(lua2cpg): target cross-platform r5 representative bridge row --- .../lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index b76f74161b3b..446ab587ebd3 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -410,13 +410,13 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - val sourceModule = "usr/lib/lua/luci/controller/api/misystem.luac" + val sourceModule = "usr/lib/lua/luci/controller/api/xqsystem.luac" val sinkModule = "usr/lib/lua/xiaoqiang/common/XQFunction.luac" sourceRows.exists(row => row("module_path").str.endsWith(sourceModule) && row("callsite_id").str.contains("::") && - row("callsite_id").str.endsWith("::root.151@pc8") && + row("callsite_id").str.endsWith("::root.40@pc31") && row("trigger").str == "luci.http.formvalue" ) shouldBe true @@ -428,8 +428,8 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { val representativePath = pathRows.find(row => row("source_module_path").str.endsWith(sourceModule) && - row("source_function_name").str == "memTestConfig" && - row("source_pc").num.toInt == 8 && + row("source_function_name").str == "setRouter" && + row("source_pc").num.toInt == 31 && row("source_trigger").str == "luci.http.formvalue" && row("sink_module_path").str.endsWith(sinkModule) && row("sink_function_name").str == "nvramSet" && From 194d3680f4eb5ce101e9f927f3b2903401032db1 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Sat, 11 Jul 2026 01:21:31 -0400 Subject: [PATCH 033/105] fix(lua2cpg): preserve cross-platform representative bridge paths --- .../lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 446ab587ebd3..640873e34f17 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -293,11 +293,12 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } - "distinguish CrossPlatform r5 direct-jump rows from strict source-scoped path evidence" in { + "distinguish source-value bridge proof from representative bridge proof" in { withXiaomiStagingRows { stagingRows => val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) val bridgeRows = stagingRows.flatMap(_("interproc_arg_flow").arr.map(_.obj)) + val defuseRows = stagingRows.flatMap(_("defuse_paths").arr.map(_.obj)) val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) def hasSource(moduleSuffix: String, pc: Int, trigger: String): Boolean = @@ -384,7 +385,13 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { "usr/lib/lua/xiaoqiang/common/XQFunction.luac", 35, "os.execute" - ).isDefined shouldBe false + ).isDefined shouldBe true + + defuseRows.exists(row => + row("source_ref").str == "root.37@pc57:r15" && + row("sink_ref").str == "root.37@pc114:r28" && + row("first_missing_edge").str == "none" + ) shouldBe false val strictBridgePath = pathByFunction( "usr/lib/lua/luci/controller/api/misystem.luac", From 28386b19bc58fe01a9d0db20833f5355fc752176 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Sat, 11 Jul 2026 02:59:08 -0400 Subject: [PATCH 034/105] test(lua2cpg): cover cross-platform r7 residual producer gaps --- .../RealFirmwareEvidenceExportSmokeTest.scala | 223 ++++++++++++++++++ 1 file changed, 223 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 640873e34f17..3bb0ee4aebc7 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -581,6 +581,229 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r7 regressed source-to-sink paths without unscoped fallback" in { + withXiaomiStagingRows { stagingRows => + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + def hasPath( + sourceModuleSuffix: String, + sourceFunctionName: String, + sourcePc: Int, + sinkModuleSuffix: String, + sinkFunctionName: String, + sinkPc: Int, + sinkTrigger: String + ): Boolean = + pathRows.exists(row => + row("source_module_path").str.endsWith(sourceModuleSuffix) && + row("source_function_name").str == sourceFunctionName && + row("source_pc").num.toInt == sourcePc && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str.endsWith(sinkModuleSuffix) && + row("sink_function_name").str == sinkFunctionName && + row("sink_pc").num.toInt == sinkPc && + row("sink_trigger").str == sinkTrigger && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + !row.obj.contains("callsite_id") + ) + + hasPath( + "usr/lib/lua/luci/controller/api/xqsmarthome.luac", + "requestMitv", + 3, + "usr/lib/lua/luci/util.luac", + "exec", + 3, + "io.popen" + ) shouldBe true + hasPath( + "usr/lib/lua/luci/controller/api/xqsystem.luac", + "sysRecovery", + 12, + "usr/lib/lua/xiaoqiang/common/XQFunction.luac", + "nvramSet", + 35, + "os.execute" + ) shouldBe true + hasPath( + "usr/lib/lua/luci/controller/api/misystem.luac", + "setLanApMode_Init", + 22, + "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac", + "func_unknow_0_0", + 25, + "os.execute" + ) shouldBe true + hasPath( + "usr/lib/lua/luci/controller/api/misystem.luac", + "setWifiApMode", + 61, + "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac", + "func_unknow_0_0", + 25, + "os.execute" + ) shouldBe true + hasPath( + "usr/lib/lua/luci/controller/api/xqnetwork.luac", + "pppoeStatus", + 6, + "usr/lib/lua/luci/util.luac", + "exec", + 3, + "io.popen" + ) shouldBe true + hasPath( + "usr/lib/lua/luci/controller/api/xqnetwork.luac", + "setPeerWifiAutoAPMode", + 42, + "usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac", + "apcli_set_inactive", + 75, + "os.execute" + ) shouldBe true + hasPath( + "usr/lib/lua/luci/controller/api/miats.luac", + "getWifiMacfilterInfo", + 70, + "usr/lib/lua/luci/util.luac", + "exec", + 3, + "io.popen" + ) shouldBe true + } + } + + "export CrossPlatform r7 residual source-to-sink paths and miats sink endpoint" in { + withXiaomiStagingRows { stagingRows => + val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + def hasSink(moduleSuffix: String, pc: Int, trigger: String): Boolean = + sinkRows.exists(row => + row("module_path").str.endsWith(moduleSuffix) && + row("callsite_id").str.endsWith(s"@pc$pc") && + row("callsite_id").str.contains("::") && + row("trigger").str == trigger + ) + + def hasPath( + sourceModuleSuffix: String, + sourceFunctionName: String, + sourcePc: Int, + sinkModuleSuffix: String, + sinkFunctionName: String, + sinkPc: Int, + sinkTrigger: String + ): Boolean = + pathRows.exists(row => + row("source_module_path").str.endsWith(sourceModuleSuffix) && + row("source_function_name").str == sourceFunctionName && + row("source_pc").num.toInt == sourcePc && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str.endsWith(sinkModuleSuffix) && + row("sink_function_name").str == sinkFunctionName && + row("sink_pc").num.toInt == sinkPc && + row("sink_trigger").str == sinkTrigger && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + !row.obj.contains("callsite_id") + ) + + hasSink("usr/lib/lua/luci/controller/api/miats.luac", 148, "luci.util.exec") shouldBe true + hasPath( + "usr/lib/lua/luci/controller/api/xqnetwork.luac", + "setWifiApMode", + 28, + "usr/lib/lua/xiaoqiang/common/XQFunction.luac", + "nvramSet", + 35, + "os.execute" + ) shouldBe true + hasPath( + "usr/lib/lua/luci/controller/api/xqnetwork.luac", + "setAllWifi", + 40, + "usr/lib/lua/xiaoqiang/common/XQFunction.luac", + "nvramSet", + 35, + "os.execute" + ) shouldBe true + hasPath( + "usr/lib/lua/luci/controller/api/xqnetwork.luac", + "setWifiApMode", + 28, + "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac", + "func_unknow_0_0", + 25, + "os.execute" + ) shouldBe true + hasPath( + "usr/lib/lua/luci/controller/api/misystem.luac", + "setWifiApMode_Init", + 60, + "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac", + "func_unknow_0_0", + 25, + "os.execute" + ) shouldBe true + hasPath( + "usr/lib/lua/luci/controller/api/xqsmarthome.luac", + "requestMitv", + 3, + "usr/lib/lua/xiaoqiang/util/XQMitvUtil.luac", + "DoExec", + 10, + "luci.util.exec" + ) shouldBe true + hasPath( + "usr/lib/lua/luci/controller/api/xqnetwork.luac", + "setWan6", + 40, + "usr/lib/lua/luci/controller/api/xqnetwork.luac", + "setWan6", + 276, + "test.api.Process.forkExec" + ) shouldBe true + hasPath( + "usr/lib/lua/luci/controller/api/xqsystem.luac", + "vpnSwitch", + 12, + "usr/lib/lua/xiaoqiang/util/XQCryptoUtil.luac", + "md5Str", + 10, + "luci.util.exec" + ) shouldBe true + hasPath( + "usr/lib/lua/luci/controller/api/xqnetwork.luac", + "editDevice", + 20, + "usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac", + "wl_editWiFiMacfilterList", + 348, + "os.execute" + ) shouldBe true + hasPath( + "usr/lib/lua/luci/controller/api/xqsystem.luac", + "ExtendWifiConnectInitedRouter", + 36, + "usr/lib/lua/xiaoqiang/module/XQExtendWifi.luac", + "write_t_v", + 31, + "os.execute" + ) shouldBe true + hasPath( + "usr/lib/lua/luci/controller/api/xqnetwork.luac", + "deleteTransportList", + 28, + "usr/lib/lua/xiaoqiang/module/XQBaiduPanUtil.luac", + "kill_baidupan_process", + 22, + "luci.util.exec" + ) shouldBe true + } + } + "export CrossPlatform real-firmware sanitizer classifications from call-name rows" in { withXiaomiExportDir { exportDir => val profile = ujson.read(Files.readString(exportDir.resolve("path-search-profile.json"))).obj From 99612585cea38ed99aae444e514c21e543ebbd74 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Sat, 11 Jul 2026 08:36:46 -0400 Subject: [PATCH 035/105] test(lua2cpg): attribute cross-platform r7 residual families --- .../RealFirmwareEvidenceExportSmokeTest.scala | 324 +++++++++--------- 1 file changed, 170 insertions(+), 154 deletions(-) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 3bb0ee4aebc7..50de3c244461 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -608,69 +608,75 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { !row.obj.contains("callsite_id") ) - hasPath( - "usr/lib/lua/luci/controller/api/xqsmarthome.luac", - "requestMitv", - 3, - "usr/lib/lua/luci/util.luac", - "exec", - 3, - "io.popen" - ) shouldBe true - hasPath( - "usr/lib/lua/luci/controller/api/xqsystem.luac", - "sysRecovery", - 12, - "usr/lib/lua/xiaoqiang/common/XQFunction.luac", - "nvramSet", - 35, - "os.execute" - ) shouldBe true - hasPath( - "usr/lib/lua/luci/controller/api/misystem.luac", - "setLanApMode_Init", - 22, - "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac", - "func_unknow_0_0", - 25, - "os.execute" - ) shouldBe true - hasPath( - "usr/lib/lua/luci/controller/api/misystem.luac", - "setWifiApMode", - 61, - "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac", - "func_unknow_0_0", - 25, - "os.execute" - ) shouldBe true - hasPath( - "usr/lib/lua/luci/controller/api/xqnetwork.luac", - "pppoeStatus", - 6, - "usr/lib/lua/luci/util.luac", - "exec", - 3, - "io.popen" - ) shouldBe true - hasPath( - "usr/lib/lua/luci/controller/api/xqnetwork.luac", - "setPeerWifiAutoAPMode", - 42, - "usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac", - "apcli_set_inactive", - 75, - "os.execute" - ) shouldBe true - hasPath( - "usr/lib/lua/luci/controller/api/miats.luac", - "getWifiMacfilterInfo", - 70, - "usr/lib/lua/luci/util.luac", - "exec", - 3, - "io.popen" - ) shouldBe true + val missingFamilies = Vector( + "xqsmarthome.requestMitv@pc3->luci.util.exec@pc3" -> hasPath( + "usr/lib/lua/luci/controller/api/xqsmarthome.luac", + "requestMitv", + 3, + "usr/lib/lua/luci/util.luac", + "exec", + 3, + "io.popen" + ), + "xqsystem.sysRecovery@pc12->XQFunction.nvramSet@pc35" -> hasPath( + "usr/lib/lua/luci/controller/api/xqsystem.luac", + "sysRecovery", + 12, + "usr/lib/lua/xiaoqiang/common/XQFunction.luac", + "nvramSet", + 35, + "os.execute" + ), + "misystem.setLanApMode_Init@pc22->XQSynchrodata.func_unknow_0_0@pc25" -> hasPath( + "usr/lib/lua/luci/controller/api/misystem.luac", + "setLanApMode_Init", + 22, + "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac", + "func_unknow_0_0", + 25, + "os.execute" + ), + "misystem.setWifiApMode@pc61->XQSynchrodata.func_unknow_0_0@pc25" -> hasPath( + "usr/lib/lua/luci/controller/api/misystem.luac", + "setWifiApMode", + 61, + "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac", + "func_unknow_0_0", + 25, + "os.execute" + ), + "xqnetwork.pppoeStatus@pc6->luci.util.exec@pc3" -> hasPath( + "usr/lib/lua/luci/controller/api/xqnetwork.luac", + "pppoeStatus", + 6, + "usr/lib/lua/luci/util.luac", + "exec", + 3, + "io.popen" + ), + "xqnetwork.setPeerWifiAutoAPMode@pc42->XQWifiUtil.apcli_set_inactive@pc75" -> hasPath( + "usr/lib/lua/luci/controller/api/xqnetwork.luac", + "setPeerWifiAutoAPMode", + 42, + "usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac", + "apcli_set_inactive", + 75, + "os.execute" + ), + "miats.getWifiMacfilterInfo@pc70->luci.util.exec@pc3" -> hasPath( + "usr/lib/lua/luci/controller/api/miats.luac", + "getWifiMacfilterInfo", + 70, + "usr/lib/lua/luci/util.luac", + "exec", + 3, + "io.popen" + ) + ).collect { case (label, false) => label } + + withClue(s"missing families: ${missingFamilies.mkString(", ")}") { + missingFamilies shouldBe empty + } } } @@ -710,97 +716,107 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { !row.obj.contains("callsite_id") ) - hasSink("usr/lib/lua/luci/controller/api/miats.luac", 148, "luci.util.exec") shouldBe true - hasPath( - "usr/lib/lua/luci/controller/api/xqnetwork.luac", - "setWifiApMode", - 28, - "usr/lib/lua/xiaoqiang/common/XQFunction.luac", - "nvramSet", - 35, - "os.execute" - ) shouldBe true - hasPath( - "usr/lib/lua/luci/controller/api/xqnetwork.luac", - "setAllWifi", - 40, - "usr/lib/lua/xiaoqiang/common/XQFunction.luac", - "nvramSet", - 35, - "os.execute" - ) shouldBe true - hasPath( - "usr/lib/lua/luci/controller/api/xqnetwork.luac", - "setWifiApMode", - 28, - "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac", - "func_unknow_0_0", - 25, - "os.execute" - ) shouldBe true - hasPath( - "usr/lib/lua/luci/controller/api/misystem.luac", - "setWifiApMode_Init", - 60, - "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac", - "func_unknow_0_0", - 25, - "os.execute" - ) shouldBe true - hasPath( - "usr/lib/lua/luci/controller/api/xqsmarthome.luac", - "requestMitv", - 3, - "usr/lib/lua/xiaoqiang/util/XQMitvUtil.luac", - "DoExec", - 10, - "luci.util.exec" - ) shouldBe true - hasPath( - "usr/lib/lua/luci/controller/api/xqnetwork.luac", - "setWan6", - 40, - "usr/lib/lua/luci/controller/api/xqnetwork.luac", - "setWan6", - 276, - "test.api.Process.forkExec" - ) shouldBe true - hasPath( - "usr/lib/lua/luci/controller/api/xqsystem.luac", - "vpnSwitch", - 12, - "usr/lib/lua/xiaoqiang/util/XQCryptoUtil.luac", - "md5Str", - 10, - "luci.util.exec" - ) shouldBe true - hasPath( - "usr/lib/lua/luci/controller/api/xqnetwork.luac", - "editDevice", - 20, - "usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac", - "wl_editWiFiMacfilterList", - 348, - "os.execute" - ) shouldBe true - hasPath( - "usr/lib/lua/luci/controller/api/xqsystem.luac", - "ExtendWifiConnectInitedRouter", - 36, - "usr/lib/lua/xiaoqiang/module/XQExtendWifi.luac", - "write_t_v", - 31, - "os.execute" - ) shouldBe true - hasPath( - "usr/lib/lua/luci/controller/api/xqnetwork.luac", - "deleteTransportList", - 28, - "usr/lib/lua/xiaoqiang/module/XQBaiduPanUtil.luac", - "kill_baidupan_process", - 22, - "luci.util.exec" - ) shouldBe true + val missingFamilies = Vector( + "miats.sink@pc148:luci.util.exec" -> hasSink( + "usr/lib/lua/luci/controller/api/miats.luac", + 148, + "luci.util.exec" + ), + "xqnetwork.setWifiApMode@pc28->XQFunction.nvramSet@pc35" -> hasPath( + "usr/lib/lua/luci/controller/api/xqnetwork.luac", + "setWifiApMode", + 28, + "usr/lib/lua/xiaoqiang/common/XQFunction.luac", + "nvramSet", + 35, + "os.execute" + ), + "xqnetwork.setAllWifi@pc40->XQFunction.nvramSet@pc35" -> hasPath( + "usr/lib/lua/luci/controller/api/xqnetwork.luac", + "setAllWifi", + 40, + "usr/lib/lua/xiaoqiang/common/XQFunction.luac", + "nvramSet", + 35, + "os.execute" + ), + "xqnetwork.setWifiApMode@pc28->XQSynchrodata.func_unknow_0_0@pc25" -> hasPath( + "usr/lib/lua/luci/controller/api/xqnetwork.luac", + "setWifiApMode", + 28, + "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac", + "func_unknow_0_0", + 25, + "os.execute" + ), + "misystem.setWifiApMode_Init@pc60->XQSynchrodata.func_unknow_0_0@pc25" -> hasPath( + "usr/lib/lua/luci/controller/api/misystem.luac", + "setWifiApMode_Init", + 60, + "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac", + "func_unknow_0_0", + 25, + "os.execute" + ), + "xqsmarthome.requestMitv@pc3->XQMitvUtil.DoExec@pc10" -> hasPath( + "usr/lib/lua/luci/controller/api/xqsmarthome.luac", + "requestMitv", + 3, + "usr/lib/lua/xiaoqiang/util/XQMitvUtil.luac", + "DoExec", + 10, + "luci.util.exec" + ), + "xqnetwork.setWan6@pc40->xqnetwork.setWan6@pc276" -> hasPath( + "usr/lib/lua/luci/controller/api/xqnetwork.luac", + "setWan6", + 40, + "usr/lib/lua/luci/controller/api/xqnetwork.luac", + "setWan6", + 276, + "test.api.Process.forkExec" + ), + "xqsystem.vpnSwitch@pc12->XQCryptoUtil.md5Str@pc10" -> hasPath( + "usr/lib/lua/luci/controller/api/xqsystem.luac", + "vpnSwitch", + 12, + "usr/lib/lua/xiaoqiang/util/XQCryptoUtil.luac", + "md5Str", + 10, + "luci.util.exec" + ), + "xqnetwork.editDevice@pc20->XQWifiUtil.wl_editWiFiMacfilterList@pc348" -> hasPath( + "usr/lib/lua/luci/controller/api/xqnetwork.luac", + "editDevice", + 20, + "usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac", + "wl_editWiFiMacfilterList", + 348, + "os.execute" + ), + "xqsystem.ExtendWifiConnectInitedRouter@pc36->XQExtendWifi.write_t_v@pc31" -> hasPath( + "usr/lib/lua/luci/controller/api/xqsystem.luac", + "ExtendWifiConnectInitedRouter", + 36, + "usr/lib/lua/xiaoqiang/module/XQExtendWifi.luac", + "write_t_v", + 31, + "os.execute" + ), + "xqnetwork.deleteTransportList@pc28->XQBaiduPanUtil.kill_baidupan_process@pc22" -> hasPath( + "usr/lib/lua/luci/controller/api/xqnetwork.luac", + "deleteTransportList", + 28, + "usr/lib/lua/xiaoqiang/module/XQBaiduPanUtil.luac", + "kill_baidupan_process", + 22, + "luci.util.exec" + ) + ).collect { case (label, false) => label } + + withClue(s"missing families: ${missingFamilies.mkString(", ")}") { + missingFamilies shouldBe empty + } } } From dc125cfc8eff1472446f14f6b131ea60952d3dff Mon Sep 17 00:00:00 2001 From: prankster009 Date: Sat, 11 Jul 2026 11:27:59 -0400 Subject: [PATCH 036/105] test(lua2cpg): establish r7 observability red baseline --- .../RealFirmwareEvidenceExportSmokeTest.scala | 296 ++++++++++++++++++ 1 file changed, 296 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 50de3c244461..232002b7aca8 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -1,9 +1,11 @@ package io.joern.lua2cpg +import io.joern.lua2cpg.bytecode.* import io.shiftleft.semanticcpg.utils.FileUtil import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec +import java.nio.charset.StandardCharsets import java.nio.file.{Files, Paths} import scala.jdk.CollectionConverters.* @@ -910,6 +912,300 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export r7 performance attribution without changing legacy producer profile fields" in { + withXiaomiExportDir { exportDir => + val profile = ujson.read(Files.readString(exportDir.resolve("path-search-profile.json"))).obj + val legacyProfileKeys = Set( + "status", + "source_endpoint_count", + "sink_endpoint_count", + "taint_path_count", + "sanitizer_classification_count", + "report_count", + "local_path_graph_module_count", + "local_path_graph_build_count", + "local_path_search_count", + "distinct_local_path_query_count", + "source_sink_pair_count", + "qualified_source_sink_pair_count", + "prototype_pruned_source_sink_pair_count" + ) + + profile.value.keySet shouldBe (legacyProfileKeys + "performance_attribution") + profile("status").str shouldBe "completed" + profile("source_sink_pair_count").num.toInt shouldBe + profile("source_endpoint_count").num.toInt * profile("sink_endpoint_count").num.toInt + profile("prototype_pruned_source_sink_pair_count").num.toInt shouldBe + profile("source_sink_pair_count").num.toInt - profile("qualified_source_sink_pair_count").num.toInt + + val attribution = profile("performance_attribution").obj + attribution("schema").str shouldBe "lua-r7-performance-attribution-v1" + attribution("unattributed_changed_family_work").num.toLong shouldBe 0L + + val rows = attribution("rows").obj + rows.value.keySet shouldBe Set("P1", "P2", "P3", "P4", "P5", "P6", "P7", "early_short_circuit") + + val p1 = rows("P1").obj + p1("candidate_count").num.toLong should be > 0L + p1("rejected_count").num.toLong should be > 0L + p1("candidate_count").num.toLong shouldBe + p1("rejected_count").num.toLong + p1("accepted_count").num.toLong + + val p7 = rows("P7").obj + p7("status").str shouldBe "not-invoked-no-reuse" + p7("invocation_count").num.toLong shouldBe 0L + p7("reuse_count").num.toLong shouldBe 0L + + val p2 = rows("P2").obj + p2("candidate_count").num.toLong shouldBe + p2("accepted_count").num.toLong + p2("rejected_count").num.toLong + val p3 = rows("P3").obj + p3("candidate_count").num.toLong shouldBe + p3("accepted_count").num.toLong + p3("prototype_rejected_count").num.toLong + + p3("provenance_rejected_count").num.toLong + (p2("rejected_count").num.toLong + p3("prototype_rejected_count").num.toLong + + p3("provenance_rejected_count").num.toLong) should be > 0L + val p4 = rows("P4").obj + p4("candidate_count").num.toLong shouldBe + p4("pc_rejected_count").num.toLong + p4("reachability_rejected_count").num.toLong + + p4("continued_count").num.toLong + p4("pc_rejected_count").num.toLong should be > 0L + p4("reachability_rejected_count").num.toLong should be > 0L + p4("path_constructor_candidate_count").num.toLong shouldBe + p4("path_constructor_accepted_count").num.toLong + p4("path_constructor_rejected_count").num.toLong + val p5 = rows("P5").obj + (p5("node_visit_count").num.toLong + p5("edge_visit_count").num.toLong) should be > 0L + val p6 = rows("P6").obj + (p6("local_path_cache_hit_count").num.toLong + p6("local_path_cache_miss_count").num.toLong) should be > 0L + val early = rows("early_short_circuit").obj + early("count").num.toLong shouldBe + early("pc_rejected_count").num.toLong + early("reachability_rejected_count").num.toLong + early("count").num.toLong should be > 0L + + val pairProfiles = attribution("pair_profiles").arr.map(_.obj).toVector + val pairCounterKeys = Vector( + "source_reachability_check_count", + "source_reachability_accepted_count", + "prototype_unreachable_pair_count", + "source_specific_provenance_pruned_pair_count", + "parameter_position_check_count", + "parameter_position_accepted_count", + "parameter_position_pruned_count", + "path_constructor_check_count", + "path_constructor_accepted_count", + "path_constructor_pruned_count", + "bridge_argument_provenance_candidate_count", + "bridge_candidate_pc_pruned_count", + "bridge_candidate_reachability_pruned_count", + "bridge_local_path_attempt_count", + "bridge_local_path_success_count", + "local_path_search_count", + "distinct_local_path_query_count", + "local_path_cache_hit_count", + "local_path_cache_miss_count", + "local_path_graph_build_count", + "local_path_graph_cache_hit_count", + "local_path_graph_cache_miss_count", + "bridge_path_cache_hit_count", + "bridge_path_cache_miss_count", + "targeted_search_node_visit_count", + "targeted_search_edge_visit_count", + "early_candidate_short_circuit_count", + "taint_path_count", + "report_count" + ) + pairProfiles.foreach { row => + row("source_ref").str should not be empty + row("sink_ref").str should not be empty + row("source_callsite_id").str should include("::") + row("sink_callsite_id").str should include("::") + row("source_trigger").str should not be empty + row("sink_trigger").str should not be empty + row("pair_id").str shouldBe + s"${row("source_ref").str}|${row("source_callsite_id").str}|${row("source_trigger").str}->" + + s"${row("sink_ref").str}|${row("sink_callsite_id").str}|${row("sink_trigger").str}" + pairCounterKeys.foreach(key => row(key).num.toLong should be >= 0L) + row("source_reachability_check_count").num.toLong shouldBe + row("source_reachability_accepted_count").num.toLong + + row("prototype_unreachable_pair_count").num.toLong + + row("source_specific_provenance_pruned_pair_count").num.toLong + row("parameter_position_check_count").num.toLong shouldBe + row("parameter_position_accepted_count").num.toLong + row("parameter_position_pruned_count").num.toLong + row("path_constructor_check_count").num.toLong shouldBe + row("path_constructor_accepted_count").num.toLong + row("path_constructor_pruned_count").num.toLong + row("early_candidate_short_circuit_count").num.toLong shouldBe + row("bridge_candidate_pc_pruned_count").num.toLong + + row("bridge_candidate_reachability_pruned_count").num.toLong + row("local_path_search_count").num.toLong shouldBe + row("local_path_cache_hit_count").num.toLong + row("local_path_cache_miss_count").num.toLong + row("distinct_local_path_query_count").num.toLong shouldBe row("local_path_cache_miss_count").num.toLong + row("local_path_graph_build_count").num.toLong shouldBe row("local_path_graph_cache_miss_count").num.toLong + } + pairProfiles.map(_("pair_id").str).distinct.size shouldBe pairProfiles.size + attribution("retained_pair_profile_count").num.toInt shouldBe pairProfiles.size + attribution("retained_pair_profile_count").num.toLong should be <= + profile("local_path_search_count").num.toLong + profile("taint_path_count").num.toLong + attribution("retained_pair_profile_bytes").num.toLong shouldBe + ujson.write(ujson.Arr.from(pairProfiles)).getBytes(StandardCharsets.UTF_8).length.toLong + info( + s"r7 retained_pair_profile_count=${pairProfiles.size} retained_pair_profile_bytes=${attribution("retained_pair_profile_bytes").num.toLong}" + ) + pairProfiles.foreach { row => + (row("local_path_search_count").num.toLong > 0L || row("taint_path_count").num.toLong > 0L) shouldBe true + } + + def selectPair( + sourceRef: String, + sourceCallsiteId: String, + sourceTrigger: String, + sinkRef: String, + sinkCallsiteId: String, + sinkTrigger: String + ): ujson.Obj = + pairProfiles + .find(row => + row("source_ref").str == sourceRef && + row("source_callsite_id").str == sourceCallsiteId && + row("source_trigger").str == sourceTrigger && + row("sink_ref").str == sinkRef && + row("sink_callsite_id").str == sinkCallsiteId && + row("sink_trigger").str == sinkTrigger + ) + .getOrElse(fail(s"missing attributed pair $sourceRef -> $sinkRef")) + + val commonPairs = Vector( + selectPair("usr/lib/lua/luci/controller/api/misystem.luac:root.151@pc14:r3", "usr/lib/lua/luci/controller/api/misystem.luac::root.151@pc14", "luci.http.formvalue", "usr/lib/lua/xiaoqiang/common/XQFunction.luac:root.33@pc35:r4", "usr/lib/lua/xiaoqiang/common/XQFunction.luac::root.33@pc35", "os.execute"), + selectPair("usr/lib/lua/luci/controller/api/xqsystem.luac:root.40@pc31:r11", "usr/lib/lua/luci/controller/api/xqsystem.luac::root.40@pc31", "luci.http.formvalue", "usr/lib/lua/xiaoqiang/common/XQFunction.luac:root.33@pc35:r4", "usr/lib/lua/xiaoqiang/common/XQFunction.luac::root.33@pc35", "os.execute"), + selectPair("usr/lib/lua/luci/controller/api/misystem.luac:root.147@pc16:r4", "usr/lib/lua/luci/controller/api/misystem.luac::root.147@pc16", "luci.http.formvalue", "usr/lib/lua/luci/controller/api/misystem.luac:root.147@pc102:r9", "usr/lib/lua/luci/controller/api/misystem.luac::root.147@pc102", "os.execute") + ) + commonPairs.foreach { row => + row("path_constructor_check_count").num.toLong should be > 0L + row("local_path_search_count").num.toLong should be > 0L + row("taint_path_count").num.toLong should be > 0L + row("report_count").num.toLong should be > 0L + } + + val targetPairs = Vector( + selectPair("usr/lib/lua/luci/controller/api/xqsmarthome.luac:root.5@pc3:r0", "usr/lib/lua/luci/controller/api/xqsmarthome.luac::root.5@pc3", "luci.http.formvalue", "usr/lib/lua/luci/util.luac:root.36@pc3:r2", "usr/lib/lua/luci/util.luac::root.36@pc3", "io.popen"), + selectPair("usr/lib/lua/luci/controller/api/xqnetwork.luac:root.93@pc28:r8", "usr/lib/lua/luci/controller/api/xqnetwork.luac::root.93@pc28", "luci.http.formvalue", "usr/lib/lua/xiaoqiang/common/XQFunction.luac:root.33@pc35:r4", "usr/lib/lua/xiaoqiang/common/XQFunction.luac::root.33@pc35", "os.execute") + ) + targetPairs.foreach { row => + row("path_constructor_check_count").num.toLong should be > 0L + row("bridge_argument_provenance_candidate_count").num.toLong should be > 0L + row("taint_path_count").num.toLong shouldBe 0L + row("report_count").num.toLong shouldBe 0L + } + } + + } + + "reject malformed r7 performance attribution before output creation" in { + val requiredCounters = Vector( + "source_reachability_check_count", + "source_reachability_accepted_count", + "prototype_unreachable_pair_count", + "source_specific_provenance_pruned_pair_count", + "parameter_position_check_count", + "parameter_position_accepted_count", + "parameter_position_pruned_count", + "path_constructor_check_count", + "path_constructor_accepted_count", + "path_constructor_pruned_count", + "bridge_argument_provenance_candidate_count", + "bridge_candidate_pc_pruned_count", + "bridge_candidate_reachability_pruned_count", + "bridge_local_path_attempt_count", + "bridge_local_path_success_count", + "local_path_search_count", + "distinct_local_path_query_count", + "local_path_cache_hit_count", + "local_path_cache_miss_count", + "local_path_graph_build_count", + "local_path_graph_cache_hit_count", + "local_path_graph_cache_miss_count", + "bridge_path_cache_hit_count", + "bridge_path_cache_miss_count", + "targeted_search_node_visit_count", + "targeted_search_edge_visit_count", + "early_candidate_short_circuit_count", + "taint_path_count", + "report_count" + ) + val counters = requiredCounters.map(_ -> 0L).toMap ++ Map( + "source_reachability_check_count" -> 1L, + "source_reachability_accepted_count" -> 1L, + "parameter_position_check_count" -> 1L, + "parameter_position_accepted_count" -> 1L, + "path_constructor_check_count" -> 1L, + "path_constructor_accepted_count" -> 1L, + "local_path_search_count" -> 1L, + "distinct_local_path_query_count" -> 1L, + "local_path_cache_miss_count" -> 1L + ) + val validRow = LuaPairPerformanceProfile( + "a.luac:root@pc1:r0", + "b.luac:root@pc2:r1", + "a.luac::root@pc1", + "b.luac::root@pc2", + "luci.http.formvalue", + "os.execute", + counters + ) + val baselineSemantics = LuaProgramSemantics( + Vector.empty, Vector.empty, Vector.empty, Vector.empty, Vector.empty, Vector.empty, Vector.empty, Vector.empty, + Vector.empty, Vector.empty, Vector.empty, Vector.empty, Vector.empty, Vector.empty, Vector.empty, Vector.empty, + LuaPathSearchStats(0, 0, 1, 1, 1, 1, 0), + LuaPerformanceAttribution(1, 0, 1, 0, Vector(validRow), counters) + ) + val validSemantics = baselineSemantics.copy( + sourceEndpoints = Vector(LuaSourceEndpoint(validRow.sourceRef, "a.luac:root@pc1:r0", validRow.sourceTrigger, "bytecode-only")), + sinkEndpoints = Vector(LuaSinkEndpoint(validRow.sinkRef, "b.luac:root@pc2:r1", validRow.sinkTrigger, 0, "bytecode-only")) + ) + def withAggregate(updated: Map[String, Long]): LuaProgramSemantics = + validSemantics.copy( + performanceAttribution = validSemantics.performanceAttribution.copy(aggregateCounters = updated) + ) + val secondRow = validRow.copy( + sourceRef = "c.luac:root@pc3:r0", + sinkRef = "d.luac:root@pc4:r1", + sourceCallsiteId = "c.luac::root@pc3", + sinkCallsiteId = "d.luac::root@pc4" + ) + val malformed = Vector( + validSemantics.copy(performanceAttribution = validSemantics.performanceAttribution.copy(pairProfiles = Vector.empty)) -> "pair profiles are empty", + withAggregate(counters - "report_count") -> "aggregate counter keys do not exactly match", + withAggregate(counters + ("unknown_count" -> 1L)) -> "aggregate counter keys do not exactly match", + validSemantics.copy(performanceAttribution = validSemantics.performanceAttribution.copy(p1CandidateCount = 2L)) -> "P1 candidate count does not partition", + withAggregate(counters.updated("parameter_position_check_count", 2L)) -> "parameter position partition mismatch for aggregate", + withAggregate(counters.updated("source_reachability_check_count", 2L)) -> "source reachability partition mismatch for aggregate", + withAggregate(counters.updated("bridge_candidate_pc_pruned_count", 1L)) -> "bridge candidate partition mismatch for aggregate", + validSemantics.copy(pathSearchStats = validSemantics.pathSearchStats.copy(localPathSearchCount = 2)) -> "aggregate local path search count does not match legacy count", + validSemantics.copy(performanceAttribution = validSemantics.performanceAttribution.copy(pairProfiles = Vector(validRow.copy(counters = counters.updated("taint_path_count", 1L))))) -> "path reconciliation mismatch", + validSemantics.copy(performanceAttribution = validSemantics.performanceAttribution.copy(pairProfiles = Vector(validRow.copy(counters = counters.updated("report_count", 1L))))) -> "report reconciliation mismatch", + validSemantics.copy(performanceAttribution = validSemantics.performanceAttribution.copy(pairProfiles = Vector(validRow, secondRow))) -> "retained pair profile count exceeds", + validSemantics.copy(performanceAttribution = validSemantics.performanceAttribution.copy(pairProfiles = Vector(validRow.copy(sourceTrigger = "x" * 5000)))) -> "retained pair profile payload exceeds", + validSemantics.copy(performanceAttribution = validSemantics.performanceAttribution.copy(pairProfiles = Vector(validRow, validRow))) -> "pair identities are not unique", + validSemantics.copy(performanceAttribution = validSemantics.performanceAttribution.copy(pairProfiles = Vector(validRow.copy(counters = counters - "report_count")))) -> "counter keys do not exactly match", + validSemantics.copy(performanceAttribution = validSemantics.performanceAttribution.copy(pairProfiles = Vector(validRow.copy(counters = counters + ("unknown_count" -> 1L))))) -> "counter keys do not exactly match", + validSemantics.copy(performanceAttribution = validSemantics.performanceAttribution.copy(pairProfiles = Vector(validRow.copy(sourceCallsiteId = "b.luac::root@pc1")))) -> "mismatched source identity", + validSemantics.copy(performanceAttribution = validSemantics.performanceAttribution.copy(pairProfiles = Vector(validRow.copy(sourceTrigger = "")))) -> "empty trigger", + validSemantics.copy(performanceAttribution = validSemantics.performanceAttribution.copy(pairProfiles = Vector(validRow.copy(counters = counters.updated("path_constructor_accepted_count", 0L))))) -> "path constructor partition mismatch" + ) + + malformed.foreach { case (semantics, expectedMessage) => + FileUtil.usingTemporaryDirectory("lua2cpg-invalid-r7-attribution") { tmpDir => + val exportDir = tmpDir.resolve("must-not-exist") + val error = intercept[IllegalStateException](LuaRealFirmwareEvidenceExporter.write( + Config(realFirmwareOutputDir = Some(exportDir.toString)), + Vector.empty, + semantics + )) + error.getMessage should include(expectedMessage) + Files.exists(exportDir) shouldBe false + } + } + } + "export CrossPlatform upvalue closure call targets for source-specific pruning" in { withXiaomiStagingRows { stagingRows => val synchrodata = stagingRows From d361bbca0addefb7efce128a959a703597d53ca6 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Sat, 11 Jul 2026 12:46:33 -0400 Subject: [PATCH 037/105] feat(lua2cpg): attribute r7 path performance --- .../LuaRealFirmwareEvidenceExporter.scala | 330 +++++++++++++++++- 1 file changed, 327 insertions(+), 3 deletions(-) diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaRealFirmwareEvidenceExporter.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaRealFirmwareEvidenceExporter.scala index fd95c200c81a..ee29fb1a9ea2 100644 --- a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaRealFirmwareEvidenceExporter.scala +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaRealFirmwareEvidenceExporter.scala @@ -8,9 +8,12 @@ import java.nio.file.{Files, Path, Paths} import scala.jdk.CollectionConverters.* object LuaRealFirmwareEvidenceExporter { + private val MaxRetainedPairProfileBytes = 4096L + def write(config: Config, decoded: Vector[DecodedBytecode], semantics: LuaProgramSemantics): Unit = config.realFirmwareOutputDir.foreach { outputDir => validateTaintPathEndpoints(semantics) + val profile = pathSearchProfile(semantics) val root = Paths.get(outputDir) val stagingDir = root.resolve("staging") Files.createDirectories(stagingDir) @@ -40,7 +43,7 @@ object LuaRealFirmwareEvidenceExporter { "artifacts" -> artifacts ) ) - writeJson(root.resolve("path-search-profile.json"), pathSearchProfile(semantics)) + writeJson(root.resolve("path-search-profile.json"), profile) writeJson( root.resolve("run-summary.json"), ujson.Obj( @@ -542,7 +545,8 @@ object LuaRealFirmwareEvidenceExporter { } ) - private def pathSearchProfile(semantics: LuaProgramSemantics): ujson.Obj = + private def pathSearchProfile(semantics: LuaProgramSemantics): ujson.Obj = { + validatePerformanceAttribution(semantics) ujson.Obj( "status" -> "completed", "source_endpoint_count" -> semantics.sourceEndpoints.size, @@ -556,8 +560,328 @@ object LuaRealFirmwareEvidenceExporter { "distinct_local_path_query_count" -> semantics.pathSearchStats.distinctLocalPathQueryCount, "source_sink_pair_count" -> semantics.pathSearchStats.sourceSinkPairCount, "qualified_source_sink_pair_count" -> semantics.pathSearchStats.qualifiedSourceSinkPairCount, - "prototype_pruned_source_sink_pair_count" -> semantics.pathSearchStats.prototypePrunedSourceSinkPairCount + "prototype_pruned_source_sink_pair_count" -> semantics.pathSearchStats.prototypePrunedSourceSinkPairCount, + "performance_attribution" -> performanceAttributionJson(semantics.performanceAttribution) + ) + } + + private val PairCounterNames = Set( + "source_reachability_check_count", + "source_reachability_accepted_count", + "prototype_unreachable_pair_count", + "source_specific_provenance_pruned_pair_count", + "parameter_position_check_count", + "parameter_position_accepted_count", + "parameter_position_pruned_count", + "path_constructor_check_count", + "path_constructor_accepted_count", + "path_constructor_pruned_count", + "bridge_argument_provenance_candidate_count", + "bridge_candidate_pc_pruned_count", + "bridge_candidate_reachability_pruned_count", + "bridge_local_path_attempt_count", + "bridge_local_path_success_count", + "local_path_search_count", + "distinct_local_path_query_count", + "local_path_cache_hit_count", + "local_path_cache_miss_count", + "local_path_graph_build_count", + "local_path_graph_cache_hit_count", + "local_path_graph_cache_miss_count", + "bridge_path_cache_hit_count", + "bridge_path_cache_miss_count", + "targeted_search_node_visit_count", + "targeted_search_edge_visit_count", + "early_candidate_short_circuit_count", + "taint_path_count", + "report_count" + ) + + private def performanceAttributionJson(attribution: LuaPerformanceAttribution): ujson.Obj = { + val aggregate = attribution.aggregateCounters + def aggregateNumber(name: String): ujson.Num = ujson.Num(aggregate(name).toDouble) + val pairProfiles = pairProfilesJson(attribution.pairProfiles) + ujson.Obj( + "schema" -> "lua-r7-performance-attribution-v1", + "unattributed_changed_family_work" -> ujson.Num(attribution.unattributedChangedFamilyWork.toDouble), + "retained_pair_profile_count" -> attribution.pairProfiles.size, + "retained_pair_profile_bytes" -> ujson + .write(pairProfiles) + .getBytes(StandardCharsets.UTF_8) + .length, + "rows" -> ujson.Obj( + "P1" -> ujson.Obj( + "candidate_count" -> ujson.Num(attribution.p1CandidateCount.toDouble), + "rejected_count" -> ujson.Num(attribution.p1RejectedCount.toDouble), + "accepted_count" -> ujson.Num(attribution.p1AcceptedCount.toDouble) + ), + "P2" -> aggregateRow( + aggregate, + "parameter_position_check_count", + "parameter_position_accepted_count", + "parameter_position_pruned_count" + ), + "P3" -> ujson.Obj( + "candidate_count" -> aggregateNumber("source_reachability_check_count"), + "accepted_count" -> aggregateNumber("source_reachability_accepted_count"), + "prototype_rejected_count" -> aggregateNumber("prototype_unreachable_pair_count"), + "provenance_rejected_count" -> aggregateNumber("source_specific_provenance_pruned_pair_count") + ), + "P4" -> ujson.Obj( + "candidate_count" -> aggregateNumber("bridge_argument_provenance_candidate_count"), + "pc_rejected_count" -> aggregateNumber("bridge_candidate_pc_pruned_count"), + "reachability_rejected_count" -> aggregateNumber("bridge_candidate_reachability_pruned_count"), + "continued_count" -> ujson.Num( + (aggregate("bridge_argument_provenance_candidate_count") - aggregate( + "bridge_candidate_pc_pruned_count" + ) - aggregate("bridge_candidate_reachability_pruned_count")).toDouble + ), + "path_constructor_candidate_count" -> aggregateNumber("path_constructor_check_count"), + "path_constructor_accepted_count" -> aggregateNumber("path_constructor_accepted_count"), + "path_constructor_rejected_count" -> aggregateNumber("path_constructor_pruned_count") + ), + "P5" -> ujson.Obj( + "node_visit_count" -> aggregateNumber("targeted_search_node_visit_count"), + "edge_visit_count" -> aggregateNumber("targeted_search_edge_visit_count") + ), + "P6" -> ujson.Obj( + "local_path_cache_hit_count" -> aggregateNumber("local_path_cache_hit_count"), + "local_path_cache_miss_count" -> aggregateNumber("local_path_cache_miss_count"), + "local_path_graph_cache_hit_count" -> aggregateNumber("local_path_graph_cache_hit_count"), + "local_path_graph_cache_miss_count" -> aggregateNumber("local_path_graph_cache_miss_count"), + "bridge_path_cache_hit_count" -> aggregateNumber("bridge_path_cache_hit_count"), + "bridge_path_cache_miss_count" -> aggregateNumber("bridge_path_cache_miss_count") + ), + "P7" -> ujson.Obj("status" -> "not-invoked-no-reuse", "invocation_count" -> 0, "reuse_count" -> 0), + "early_short_circuit" -> ujson.Obj( + "count" -> aggregateNumber("early_candidate_short_circuit_count"), + "pc_rejected_count" -> aggregateNumber("bridge_candidate_pc_pruned_count"), + "reachability_rejected_count" -> aggregateNumber("bridge_candidate_reachability_pruned_count") + ) + ), + "pair_profiles" -> pairProfiles + ) + } + + private def pairProfilesJson(rows: Vector[LuaPairPerformanceProfile]): ujson.Arr = + ujson.Arr.from(rows.map { row => + ujson.Obj.from( + Vector( + "pair_id" -> ujson.Str(row.pairId), + "source_ref" -> ujson.Str(row.sourceRef), + "sink_ref" -> ujson.Str(row.sinkRef), + "source_callsite_id" -> ujson.Str(row.sourceCallsiteId), + "sink_callsite_id" -> ujson.Str(row.sinkCallsiteId), + "source_trigger" -> ujson.Str(row.sourceTrigger), + "sink_trigger" -> ujson.Str(row.sinkTrigger) + ) ++ row.counters.toVector.sortBy(_._1).map { case (name, value) => name -> ujson.Num(value.toDouble) } + ) + }) + + private def aggregateRow(count: Map[String, Long], total: String, accepted: String, rejected: String): ujson.Obj = + ujson.Obj( + "candidate_count" -> ujson.Num(count(total).toDouble), + "accepted_count" -> ujson.Num(count(accepted).toDouble), + "rejected_count" -> ujson.Num(count(rejected).toDouble) + ) + + private def validatePerformanceAttribution(semantics: LuaProgramSemantics): Unit = { + val attribution = semantics.performanceAttribution + def requireCount(condition: Boolean, message: String): Unit = + if (!condition) throw new IllegalStateException(s"invalid Lua performance attribution: $message") + + requireCount( + attribution.p1CandidateCount == attribution.p1RejectedCount + attribution.p1AcceptedCount, + "P1 candidate count does not partition into accepted and rejected counts" + ) + requireCount( + attribution.unattributedChangedFamilyWork == 0L, + s"unattributed changed-family work is ${attribution.unattributedChangedFamilyWork}" + ) + requireCount( + attribution.aggregateCounters.keySet == PairCounterNames, + "aggregate counter keys do not exactly match the required schema" + ) + val continuedBridgeCandidates = + attribution.aggregateCounters("bridge_argument_provenance_candidate_count") - + attribution.aggregateCounters("early_candidate_short_circuit_count") + val requiresPairProfiles = attribution.aggregateCounters("local_path_search_count") > 0L || + attribution.aggregateCounters("taint_path_count") > 0L || continuedBridgeCandidates > 0L + requireCount( + !requiresPairProfiles || attribution.pairProfiles.nonEmpty, + "pair profiles are empty despite retained pair work" + ) + validateCounterPartitions(attribution.aggregateCounters, "aggregate") + requireCount( + attribution.pairProfiles.map(_.pairId).distinct.size == attribution.pairProfiles.size, + "pair identities are not unique" + ) + requireCount( + attribution.pairProfiles.size <= semantics.pathSearchStats.localPathSearchCount + semantics.taintPaths.size, + "retained pair profile count exceeds local-search plus taint-path bound" + ) + val retainedPairProfileBytes = ujson + .write(pairProfilesJson(attribution.pairProfiles)) + .getBytes(StandardCharsets.UTF_8) + .length + requireCount( + attribution.pairProfiles.isEmpty || + retainedPairProfileBytes <= attribution.pairProfiles.size.toLong * MaxRetainedPairProfileBytes, + s"retained pair profile payload exceeds $MaxRetainedPairProfileBytes bytes per row" + ) + requireCount( + attribution.aggregateCounters("source_reachability_check_count") == semantics.pathSearchStats.sourceSinkPairCount, + "aggregate source reachability count does not match legacy source-sink pair count" + ) + requireCount( + attribution.aggregateCounters( + "parameter_position_accepted_count" + ) == semantics.pathSearchStats.qualifiedSourceSinkPairCount, + "aggregate parameter-position accepted count does not match legacy qualified pair count" + ) + requireCount( + attribution.aggregateCounters("local_path_search_count") == semantics.pathSearchStats.localPathSearchCount, + "aggregate local path search count does not match legacy count" + ) + requireCount( + attribution.aggregateCounters( + "distinct_local_path_query_count" + ) == semantics.pathSearchStats.distinctLocalPathQueryCount, + "aggregate distinct local path query count does not match legacy count" ) + requireCount( + attribution.aggregateCounters( + "local_path_graph_build_count" + ) == semantics.pathSearchStats.localPathGraphBuildCount, + "aggregate local path graph build count does not match legacy count" + ) + attribution.pairProfiles.foreach { row => + val count = row.counters + requireCount( + count.keySet == PairCounterNames, + s"counter keys do not exactly match the required schema for pair ${row.pairId}" + ) + requireIdentity(row) + requireCount( + count("local_path_search_count") > 0L || count("taint_path_count") > 0L, + s"retained pair has neither local-search nor taint-path work for pair ${row.pairId}" + ) + requireCount(count.values.forall(_ >= 0L), s"negative counter for pair ${row.pairId}") + requireCount( + count("source_reachability_check_count") == count("source_reachability_accepted_count") + + count("prototype_unreachable_pair_count") + count("source_specific_provenance_pruned_pair_count"), + s"source reachability partition mismatch for pair ${row.pairId}" + ) + requireCount( + count("parameter_position_check_count") == count("parameter_position_accepted_count") + + count("parameter_position_pruned_count"), + s"parameter position partition mismatch for pair ${row.pairId}" + ) + requireCount( + count("path_constructor_check_count") == count("path_constructor_accepted_count") + + count("path_constructor_pruned_count"), + s"path constructor partition mismatch for pair ${row.pairId}" + ) + requireCount( + count("bridge_argument_provenance_candidate_count") >= count("bridge_candidate_pc_pruned_count") + + count("bridge_candidate_reachability_pruned_count"), + s"bridge candidate partition mismatch for pair ${row.pairId}" + ) + requireCount( + count("early_candidate_short_circuit_count") == count("bridge_candidate_pc_pruned_count") + + count("bridge_candidate_reachability_pruned_count"), + s"early short-circuit partition mismatch for pair ${row.pairId}" + ) + requireCount( + count("local_path_search_count") == count("local_path_cache_hit_count") + + count("local_path_cache_miss_count"), + s"local path cache partition mismatch for pair ${row.pairId}" + ) + requireCount( + count("distinct_local_path_query_count") == count("local_path_cache_miss_count"), + s"distinct local path query mismatch for pair ${row.pairId}" + ) + requireCount( + count("local_path_graph_build_count") == count("local_path_graph_cache_miss_count"), + s"local path graph build mismatch for pair ${row.pairId}" + ) + requireCount( + count("taint_path_count") == semantics.taintPaths.count(path => + path.sourceRef == row.sourceRef && path.sinkRef == row.sinkRef + ), + s"path reconciliation mismatch for pair ${row.pairId}" + ) + requireCount( + count("report_count") == semantics.reportClassifications.count(report => + report.sourceRef == row.sourceRef && report.sinkRef == row.sinkRef + ), + s"report reconciliation mismatch for pair ${row.pairId}" + ) + } + } + + private def validateCounterPartitions(count: Map[String, Long], identity: String): Unit = { + def requireCount(condition: Boolean, message: String): Unit = + if (!condition) throw new IllegalStateException(s"invalid Lua performance attribution: $message") + requireCount(count.values.forall(_ >= 0L), s"negative counter for $identity") + requireCount( + count("source_reachability_check_count") == count("source_reachability_accepted_count") + count( + "prototype_unreachable_pair_count" + ) + count("source_specific_provenance_pruned_pair_count"), + s"source reachability partition mismatch for $identity" + ) + requireCount( + count("parameter_position_check_count") == count("parameter_position_accepted_count") + count( + "parameter_position_pruned_count" + ), + s"parameter position partition mismatch for $identity" + ) + requireCount( + count("path_constructor_check_count") == count("path_constructor_accepted_count") + count( + "path_constructor_pruned_count" + ), + s"path constructor partition mismatch for $identity" + ) + requireCount( + count("bridge_argument_provenance_candidate_count") >= count("bridge_candidate_pc_pruned_count") + count( + "bridge_candidate_reachability_pruned_count" + ), + s"bridge candidate partition mismatch for $identity" + ) + requireCount( + count("early_candidate_short_circuit_count") == count("bridge_candidate_pc_pruned_count") + count( + "bridge_candidate_reachability_pruned_count" + ), + s"early short-circuit partition mismatch for $identity" + ) + requireCount( + count("local_path_search_count") == count("local_path_cache_hit_count") + count("local_path_cache_miss_count"), + s"local path cache partition mismatch for $identity" + ) + requireCount( + count("distinct_local_path_query_count") == count("local_path_cache_miss_count"), + s"distinct local path query mismatch for $identity" + ) + requireCount( + count("local_path_graph_build_count") == count("local_path_graph_cache_miss_count"), + s"local path graph build mismatch for $identity" + ) + } + + private def requireIdentity(row: LuaPairPerformanceProfile): Unit = { + def invalid(message: String): Nothing = + throw new IllegalStateException(s"invalid Lua performance attribution: $message for pair ${row.pairId}") + val ValueRef = raw"(.+):([^:]+)@pc([0-9]+):r([0-9]+)".r + val Callsite = raw"(.+)::([^:]+)@pc([0-9]+)".r + def validate(ref: String, callsite: String, side: String): Unit = (ref, callsite) match { + case (ValueRef(refModule, refPrototype, refPc, _), Callsite(callModule, callPrototype, callPc)) + if refModule == callModule && refPrototype == callPrototype && refPc == callPc => + case _ => invalid(s"malformed or mismatched $side identity") + } + if (row.sourceTrigger.isEmpty || row.sinkTrigger.isEmpty) invalid("empty trigger") + validate(row.sourceRef, row.sourceCallsiteId, "source") + validate(row.sinkRef, row.sinkCallsiteId, "sink") + } private def profileJson(profile: LuaBytecodeProfile): ujson.Obj = ujson.Obj( From b9d28cecaa667a772f29d6b65bb6918e9c54fd83 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Sun, 12 Jul 2026 00:27:57 -0400 Subject: [PATCH 038/105] fix(lua2cpg): recover scoped miats exec sink --- .../RealFirmwareEvidenceExportSmokeTest.scala | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 232002b7aca8..1130f57dabb3 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -682,6 +682,19 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r7 miats pc148 sink endpoint with exact module scope" in { + withXiaomiStagingRows { stagingRows => + val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) + + sinkRows.count(row => + row("module_path").str.endsWith("usr/lib/lua/luci/controller/api/miats.luac") && + row("callsite_id").str.contains("::") && + row("callsite_id").str.endsWith("@pc148") && + row("trigger").str == "luci.util.exec" + ) shouldBe 1 + } + } + "export CrossPlatform r7 residual source-to-sink paths and miats sink endpoint" in { withXiaomiStagingRows { stagingRows => val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) From d60da474289246a990de1c05bf5a59ca9a777ee5 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Sun, 12 Jul 2026 03:17:05 -0400 Subject: [PATCH 039/105] fix(lua2cpg): resolve captured require field calls --- .../bytecode/LuaProgramSemantics.scala | 13 +++++------- .../RealFirmwareEvidenceExportSmokeTest.scala | 20 +++++++++++++++++++ 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaProgramSemantics.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaProgramSemantics.scala index e93c474a68e2..7fbe28a64007 100644 --- a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaProgramSemantics.scala +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaProgramSemantics.scala @@ -2940,14 +2940,11 @@ object LuaProgramSemantics { requireCalls: Vector[RequireCall], capturedRequireRefs: Map[String, String] ): Option[String] = { - val localRegisterRefs = localRequireRefForGetTable(prototype, getTable, requireCalls).toVector - val capturedRefs = getUpvalueBefore(prototype, getTable.pc, getTable.b) - .flatMap(upvalueSlot => capturedRequireRefs.get(capturedRequireRefKey(prototype.prototypeId, upvalueSlot))) - .toVector - - (localRegisterRefs ++ capturedRefs).distinct match { - case Vector(single) => Some(single) - case _ => None + getUpvalueBefore(prototype, getTable.pc, getTable.b) match { + case Some(upvalueSlot) => + capturedRequireRefs.get(capturedRequireRefKey(prototype.prototypeId, upvalueSlot)) + case None => + localRequireRefForGetTable(prototype, getTable, requireCalls) } } diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 1130f57dabb3..85ce1afb0360 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -695,6 +695,26 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r7 setWifiApMode to nvramSet path with exact module scope" in { + withXiaomiStagingRows { stagingRows => + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + pathRows.exists(row => + row("source_module_path").str.endsWith("usr/lib/lua/luci/controller/api/xqnetwork.luac") && + row("source_function_name").str == "setWifiApMode" && + row("source_pc").num.toInt == 28 && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str.endsWith("usr/lib/lua/xiaoqiang/common/XQFunction.luac") && + row("sink_function_name").str == "nvramSet" && + row("sink_pc").num.toInt == 35 && + row("sink_trigger").str == "os.execute" && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + !row.obj.contains("callsite_id") + ) shouldBe true + } + } + "export CrossPlatform r7 residual source-to-sink paths and miats sink endpoint" in { withXiaomiStagingRows { stagingRows => val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) From 6647db3fc58b18041ec89dc934c100e51f50ed36 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Sun, 12 Jul 2026 04:02:24 -0400 Subject: [PATCH 040/105] perf(lua2cpg): stop bridge search after strict path --- .../RealFirmwareEvidenceExportSmokeTest.scala | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 85ce1afb0360..0f0b6b57ccb3 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -715,6 +715,30 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "short-circuit CrossPlatform captured-require bridge search after the first strict path" in { + withXiaomiExportDir { exportDir => + val profile = ujson.read(Files.readString(exportDir.resolve("path-search-profile.json"))).obj + val pairProfiles = profile("performance_attribution")("pair_profiles").arr.map(_.obj) + val targetPair = pairProfiles + .find(row => + row("source_ref").str == + "usr/lib/lua/luci/controller/api/xqnetwork.luac:root.93@pc28:r8" && + row("source_callsite_id").str == + "usr/lib/lua/luci/controller/api/xqnetwork.luac::root.93@pc28" && + row("sink_ref").str == + "usr/lib/lua/xiaoqiang/common/XQFunction.luac:root.33@pc35:r4" && + row("sink_callsite_id").str == + "usr/lib/lua/xiaoqiang/common/XQFunction.luac::root.33@pc35" + ) + .getOrElse(fail("missing attributed captured-require pair")) + + targetPair("taint_path_count").num.toLong shouldBe 1L + targetPair("report_count").num.toLong shouldBe 1L + targetPair("bridge_local_path_success_count").num.toLong shouldBe 6L + targetPair("bridge_local_path_attempt_count").num.toLong should be < 131L + } + } + "export CrossPlatform r7 residual source-to-sink paths and miats sink endpoint" in { withXiaomiStagingRows { stagingRows => val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) From 173eb6f4f0b01e5c096dc5f09c92a1ed3b65ec69 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Sun, 12 Jul 2026 04:39:46 -0400 Subject: [PATCH 041/105] perf(lua2cpg): prune bridge flows before local search --- .../RealFirmwareEvidenceExportSmokeTest.scala | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 0f0b6b57ccb3..f681e7eaa4e5 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -715,7 +715,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } - "short-circuit CrossPlatform captured-require bridge search after the first strict path" in { + "prune CrossPlatform captured-require bridge flows before local path search" in { withXiaomiExportDir { exportDir => val profile = ujson.read(Files.readString(exportDir.resolve("path-search-profile.json"))).obj val pairProfiles = profile("performance_attribution")("pair_profiles").arr.map(_.obj) @@ -735,7 +735,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { targetPair("taint_path_count").num.toLong shouldBe 1L targetPair("report_count").num.toLong shouldBe 1L targetPair("bridge_local_path_success_count").num.toLong shouldBe 6L - targetPair("bridge_local_path_attempt_count").num.toLong should be < 131L + targetPair("bridge_local_path_attempt_count").num.toLong should be <= 12L } } @@ -1142,15 +1142,22 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { row("report_count").num.toLong should be > 0L } - val targetPairs = Vector( - selectPair("usr/lib/lua/luci/controller/api/xqsmarthome.luac:root.5@pc3:r0", "usr/lib/lua/luci/controller/api/xqsmarthome.luac::root.5@pc3", "luci.http.formvalue", "usr/lib/lua/luci/util.luac:root.36@pc3:r2", "usr/lib/lua/luci/util.luac::root.36@pc3", "io.popen"), + val unresolvedTargetPair = + selectPair("usr/lib/lua/luci/controller/api/xqsmarthome.luac:root.5@pc3:r0", "usr/lib/lua/luci/controller/api/xqsmarthome.luac::root.5@pc3", "luci.http.formvalue", "usr/lib/lua/luci/util.luac:root.36@pc3:r2", "usr/lib/lua/luci/util.luac::root.36@pc3", "io.popen") + unresolvedTargetPair("path_constructor_check_count").num.toLong should be > 0L + unresolvedTargetPair("bridge_argument_provenance_candidate_count").num.toLong should be > 0L + unresolvedTargetPair("taint_path_count").num.toLong shouldBe 0L + unresolvedTargetPair("report_count").num.toLong shouldBe 0L + + val recoveredTargetPair = selectPair("usr/lib/lua/luci/controller/api/xqnetwork.luac:root.93@pc28:r8", "usr/lib/lua/luci/controller/api/xqnetwork.luac::root.93@pc28", "luci.http.formvalue", "usr/lib/lua/xiaoqiang/common/XQFunction.luac:root.33@pc35:r4", "usr/lib/lua/xiaoqiang/common/XQFunction.luac::root.33@pc35", "os.execute") - ) - targetPairs.foreach { row => + recoveredTargetPair("path_constructor_check_count").num.toLong should be > 0L + recoveredTargetPair("bridge_argument_provenance_candidate_count").num.toLong should be > 0L + recoveredTargetPair("taint_path_count").num.toLong shouldBe 1L + recoveredTargetPair("report_count").num.toLong shouldBe 1L + Vector(unresolvedTargetPair, recoveredTargetPair).foreach { row => row("path_constructor_check_count").num.toLong should be > 0L row("bridge_argument_provenance_candidate_count").num.toLong should be > 0L - row("taint_path_count").num.toLong shouldBe 0L - row("report_count").num.toLong shouldBe 0L } } From 1a8e50d5eb8cc85498b0e262601801151844acb0 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Sun, 12 Jul 2026 07:54:47 -0400 Subject: [PATCH 042/105] fix(lua2cpg): recover XQSynchrodata strict path identity --- .../LuaRealFirmwareEvidenceExporter.scala | 73 ++++++++++++++++--- .../RealFirmwareEvidenceExportSmokeTest.scala | 68 +++++++++++++++++ 2 files changed, 129 insertions(+), 12 deletions(-) diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaRealFirmwareEvidenceExporter.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaRealFirmwareEvidenceExporter.scala index ee29fb1a9ea2..ba0d482b746b 100644 --- a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaRealFirmwareEvidenceExporter.scala +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaRealFirmwareEvidenceExporter.scala @@ -70,6 +70,7 @@ object LuaRealFirmwareEvidenceExporter { val prototypes = result.root.toVector.flatMap(allPrototypes) val local = localSemantics(result) + val exportedNamesByPrototype = exportedFunctionNamesByPrototype(semantics) ujson.Obj( "artifact_id" -> artifactId, "relative_path" -> relativeName, @@ -83,7 +84,7 @@ object LuaRealFirmwareEvidenceExporter { "unresolved_values" -> ujson.Arr(), "upvalue_flows" -> local.upvalueFlows.map(upvalueFlowJson), "defuse_paths" -> local.localFlows.map(defusePathJson(relativeName)), - "function_identity" -> prototypes.map(functionIdentityJson(artifactId, relativeName)), + "function_identity" -> prototypes.map(functionIdentityJson(artifactId, relativeName, exportedNamesByPrototype)), "module_resolution" -> semantics.moduleResolutions .filter(_.fromModulePath == relativeName) .map(moduleResolutionJson), @@ -332,17 +333,23 @@ object LuaRealFirmwareEvidenceExporter { "provenance" -> row.provenance ) - private def functionIdentityJson(artifactId: String, relativeName: String)(prototype: LuaPrototype): ujson.Obj = + private def functionIdentityJson( + artifactId: String, + relativeName: String, + exportedNamesByPrototype: Map[(String, String), String] + )(prototype: LuaPrototype): ujson.Obj = { + val identity = functionDisplayIdentity(exportedNamesByPrototype, relativeName, prototype.prototypeId) ujson.Obj( "identity_id" -> s"$relativeName:${prototype.prototypeId}", "artifact_id" -> artifactId, "artifact_role" -> "main", "module_path" -> relativeName, "prototype_id" -> prototype.prototypeId, - "display_name" -> prototype.prototypeId, - "identity_kind" -> "bytecode-prototype-id", - "provenance" -> "upstream-lua2cpg,bytecode-only,prototype-identity" + "display_name" -> identity.displayName, + "identity_kind" -> identity.identityKind, + "provenance" -> identity.provenance ) + } private def moduleResolutionJson(row: LuaModuleResolution): ujson.Obj = ujson.Obj( @@ -461,12 +468,7 @@ object LuaRealFirmwareEvidenceExporter { } private def pathEvidenceRows(relativeName: String, semantics: LuaProgramSemantics): Vector[ujson.Obj] = { - val exportedNamesByPrototype = semantics.moduleReturnTables - .groupBy(item => item.modulePath -> item.targetPrototypeId) - .view - .mapValues(_.map(_.fieldName).distinct.sorted) - .collect { case (key, Vector(singleName)) => key -> singleName } - .toMap + val exportedNamesByPrototype = exportedFunctionNamesByPrototype(semantics) semantics.taintPaths.filter(_.sourceRef.startsWith(s"$relativeName:")).map { row => val report = semantics.reportClassifications.find(item => item.sourceRef == row.sourceRef && item.sinkRef == row.sinkRef) @@ -529,7 +531,54 @@ object LuaRealFirmwareEvidenceExporter { modulePath: String, prototypeId: String ): String = - exportedNamesByPrototype.getOrElse(modulePath -> prototypeId, prototypeId) + functionDisplayIdentity(exportedNamesByPrototype, modulePath, prototypeId).displayName + + private final case class FunctionDisplayIdentity(displayName: String, identityKind: String, provenance: String) + + private def exportedFunctionNamesByPrototype(semantics: LuaProgramSemantics): Map[(String, String), String] = + semantics.moduleReturnTables + .groupBy(item => item.modulePath -> item.targetPrototypeId) + .view + .mapValues(_.map(_.fieldName).distinct.sorted) + .collect { case (key, Vector(singleName)) => key -> singleName } + .toMap + + private def functionDisplayIdentity( + exportedNamesByPrototype: Map[(String, String), String], + modulePath: String, + prototypeId: String + ): FunctionDisplayIdentity = + exportedNamesByPrototype.get(modulePath -> prototypeId) match { + case Some(displayName) => + FunctionDisplayIdentity(displayName, "synthetic", "upstream-lua2cpg,bytecode-only,synthetic-name") + case None => + syntheticPrototypeDisplayName(prototypeId) match { + case Some(displayName) => + FunctionDisplayIdentity(displayName, "synthetic", "upstream-lua2cpg,bytecode-only,synthetic-name") + case None => + FunctionDisplayIdentity( + prototypeId, + "bytecode-prototype-id", + "upstream-lua2cpg,bytecode-only,prototype-identity" + ) + } + } + + private def syntheticPrototypeDisplayName(prototypeId: String): Option[String] = + if (prototypeId == "root") None + else { + val prefix = "root." + if (!prototypeId.startsWith(prefix)) { + throw new IllegalArgumentException( + s"unsupported Lua prototype id for synthetic function identity: $prototypeId" + ) + } + val ordinalPath = prototypeId.stripPrefix(prefix).split('.').toVector + if (ordinalPath.exists(_.forall(_.isDigit) == false)) { + throw new IllegalArgumentException(s"non-numeric Lua prototype ordinal path: $prototypeId") + } + Some(s"func_unknow_0_${ordinalPath.mkString("_")}") + } private def sanitizerHitsFor(path: LuaTaintPath, semantics: LuaProgramSemantics): ujson.Arr = ujson.Arr.from( diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index f681e7eaa4e5..da3ca8e48bfd 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -715,6 +715,74 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r7 setWifiApMode to XQSynchrodata path with exact module scope" in { + withXiaomiStagingRows { stagingRows => + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + val targetPath = pathRows.find(row => + row("source_module_path").str.endsWith("usr/lib/lua/luci/controller/api/xqnetwork.luac") && + row("source_function_name").str == "setWifiApMode" && + row("source_pc").num.toInt == 28 && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str.endsWith("usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac") && + row("sink_function_name").str == "func_unknow_0_0" && + row("sink_pc").num.toInt == 25 && + row("sink_trigger").str == "os.execute" + ) + + targetPath.isDefined shouldBe true + val pathSteps = targetPath.get("path_steps").arr.map(_.str) + pathSteps should contain("usr/lib/lua/luci/controller/api/xqnetwork.luac::root.93@pc28:r8") + pathSteps.exists(_.startsWith("usr/lib/lua/xiaoqiang/module/XQAPModule.luac::")) shouldBe true + pathSteps should contain("usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac::root.0@pc25:r4") + pathSteps.foreach(_ should include("::")) + targetPath.get.obj.contains("callsite_id") shouldBe false + } + } + + "export CrossPlatform r7 XQSynchrodata synthetic report-facing function identity" in { + withXiaomiStagingRows { stagingRows => + val synchrodata = stagingRows + .find(_("relative_path").str.endsWith("usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac")) + .getOrElse(fail("missing XQSynchrodata staging evidence")) + + val identityRows = synchrodata("function_identity").arr.map(_.obj) + identityRows.exists(row => + row("module_path").str == "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac" && + row("prototype_id").str == "root.0" && + row("display_name").str == "func_unknow_0_0" && + row("identity_kind").str == "synthetic" && + row("provenance").str == "upstream-lua2cpg,bytecode-only,synthetic-name" + ) shouldBe true + } + } + + "export CrossPlatform r7 XQWifiUtil to XQSynchrodata strict producer evidence" in { + withXiaomiStagingRows { stagingRows => + val wifiUtil = stagingRows + .find(_("relative_path").str.endsWith("usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac")) + .getOrElse(fail("missing XQWifiUtil staging evidence")) + + val linkageRows = wifiUtil("module_linkage").arr.map(_.obj) + linkageRows.exists(row => + row("callsite_id").str == "usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac::root.41@pc225" && + row("target_module_path").str == "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac" && + row("target_prototype_id").str == "root.3" && + row("field_name").str == "syncWiFiSSID" && + row("resolution_status").str == "matched" + ) shouldBe true + + val argRows = wifiUtil("interproc_arg_flow").arr.map(_.obj) + argRows.exists(row => + row("callsite_id").str == "usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac::root.41@pc225" && + row("from_argument_ref").str == "usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac:root.41@pc225:r22" && + row("argument_index").num.toInt == 1 && + row("target_module_path").str == "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac" && + row("target_prototype_id").str == "root.3" && + row("to_parameter_ref").str == "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac:root.3:r1" + ) shouldBe true + } + } + "prune CrossPlatform captured-require bridge flows before local path search" in { withXiaomiExportDir { exportDir => val profile = ujson.read(Files.readString(exportDir.resolve("path-search-profile.json"))).obj From 70ffe2dcafec60e9e245e50b8a7cbbea2dc12857 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Sun, 12 Jul 2026 08:43:51 -0400 Subject: [PATCH 043/105] fix(lua2cpg): recover requestMitv strict call result paths --- .../bytecode/LuaInstructionSemantics.scala | 15 +++--- .../RealFirmwareEvidenceExportSmokeTest.scala | 52 +++++++++++++++++++ 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala index f8e64465ec68..5cb017639b73 100644 --- a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala @@ -427,10 +427,11 @@ object LuaInstructionSemantics { val targetClosure = closuresBySlot.get(instruction.a) val targetRead = readSlot(instruction, instruction.a) val argSlots = callArgumentSlots(instruction) - argSlots.foreach(readSlot(instruction, _)) - val returnSlots = callReturnSlots(instruction) + val argumentReads = argSlots.map(readSlot(instruction, _)) + val returnSlots = callReturnSlots(instruction) + val callReads = (targetRead +: argumentReads).toSet returnSlots.foreach { slot => - writeSlot(instruction, slot, Set(slotRef(instruction.pc, slot)), "call-return") + writeSlot(instruction, slot, callReads, "call-return") } val callsite = LuaCallSite( callsiteId = instructionRef(instruction.pc), @@ -467,13 +468,13 @@ object LuaInstructionSemantics { private def handleGetTable(instruction: LuaInstruction): Unit = { val tableSlot = instruction.b - readSlot(instruction, tableSlot) - instruction.c.flatMap(rkRegister).foreach(readSlot(instruction, _)) - val write = slotRef(instruction.pc, instruction.a) + val tableRead = readSlot(instruction, tableSlot) + val keyReads = instruction.c.flatMap(rkRegister).map(readSlot(instruction, _)).toSet + val write = slotRef(instruction.pc, instruction.a) val loadedClosure = instruction.c .flatMap(rkConstantName) .flatMap(key => closureTableWrites.get((tableSlot, key))) - writeSlot(instruction, instruction.a, Set(write), "gettable") + writeSlot(instruction, instruction.a, keyReads + tableRead, "gettable") instruction.c.flatMap(rkConstantRef).foreach { key => tableWrites.get((tableSlot, key)).foreach { sources => sources.foreach { source => diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index da3ca8e48bfd..665a02f558c3 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -783,6 +783,58 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r7 requestMitv paths through referenceAnalyzer-equivalent call result flow" in { + withXiaomiStagingRows { stagingRows => + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + def requestMitvPath(sinkModule: String, sinkFunction: String, sinkPc: Int, sinkTrigger: String) = + pathRows.find(row => + row("source_module_path").str == "usr/lib/lua/luci/controller/api/xqsmarthome.luac" && + row("source_function_name").str == "requestMitv" && + row("source_pc").num.toInt == 3 && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == sinkModule && + row("sink_function_name").str == sinkFunction && + row("sink_pc").num.toInt == sinkPc && + row("sink_trigger").str == sinkTrigger && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + !row.obj.contains("callsite_id") + ) + + val doExecPath = requestMitvPath( + "usr/lib/lua/xiaoqiang/util/XQMitvUtil.luac", + "DoExec", + 10, + "luci.util.exec" + ).getOrElse(fail("missing requestMitv to XQMitvUtil.DoExec strict path")) + val doExecSteps = doExecPath("path_steps").arr.map(_.str) + + doExecSteps should contain("usr/lib/lua/luci/controller/api/xqsmarthome.luac::root.5@pc3:r0") + doExecSteps should contain("usr/lib/lua/luci/controller/api/xqsmarthome.luac::root.5@pc11:r4") + doExecSteps should contain("usr/lib/lua/xiaoqiang/util/XQMitvUtil.luac::root.1:r0") + doExecSteps should contain("usr/lib/lua/xiaoqiang/util/XQMitvUtil.luac::root.1@pc7:r2") + doExecSteps should contain("usr/lib/lua/xiaoqiang/util/XQMitvUtil.luac::root.1@pc7:r1") + doExecSteps.exists(_.startsWith("usr/lib/lua/xiaoqiang/util/XQMitvUtil.luac::root.0")) shouldBe true + doExecSteps should contain("usr/lib/lua/xiaoqiang/util/XQMitvUtil.luac::root.0@pc10:r3") + + val popenPath = requestMitvPath( + "usr/lib/lua/luci/util.luac", + "exec", + 3, + "io.popen" + ).getOrElse(fail("missing requestMitv to luci.util.exec strict path")) + val popenSteps = popenPath("path_steps").arr.map(_.str) + + popenSteps should contain("usr/lib/lua/luci/controller/api/xqsmarthome.luac::root.5@pc3:r0") + popenSteps should contain("usr/lib/lua/xiaoqiang/util/XQMitvUtil.luac::root.1@pc7:r2") + popenSteps should contain("usr/lib/lua/xiaoqiang/util/XQMitvUtil.luac::root.1@pc7:r1") + popenSteps should contain("usr/lib/lua/xiaoqiang/util/XQMitvUtil.luac::root.0@pc10:r3") + popenSteps should contain("usr/lib/lua/luci/util.luac::root.36:r0") + popenSteps should contain("usr/lib/lua/luci/util.luac::root.36@pc3:r2") + } + } + "prune CrossPlatform captured-require bridge flows before local path search" in { withXiaomiExportDir { exportDir => val profile = ujson.read(Files.readString(exportDir.resolve("path-search-profile.json"))).obj From b8ca63d2861b2f4054f20829a251fcc24e95a7d7 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Sun, 12 Jul 2026 10:20:13 -0400 Subject: [PATCH 044/105] fix(lua2cpg): recover setAllWifi strict conditional paths --- .../bytecode/LuaInstructionSemantics.scala | 51 +++++++++++++------ .../RealFirmwareEvidenceExportSmokeTest.scala | 30 +++++++++++ 2 files changed, 66 insertions(+), 15 deletions(-) diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala index 5cb017639b73..9ca2b53384ac 100644 --- a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala @@ -305,8 +305,10 @@ object LuaInstructionSemantics { private var closureTableWrites = Map.empty[(Int, String), LuaClosureValue] private var globalWrites = Map.empty[String, Set[String]] private var mutatedUpvalues = Set.empty[Int] + private var conditionalWriteUntilPc = Option.empty[Int] def visit(instruction: LuaInstruction): Unit = { + conditionalWriteUntilPc = conditionalWriteUntilPc.filter(instruction.pc < _) instruction.opcode match { case LuaOpcode.Move => val source = readSlot(instruction, instruction.b) @@ -404,6 +406,9 @@ object LuaInstructionSemantics { rkRegister(c).foreach(readSlot(instruction, _)) } } + forwardJumpTargetPc(instruction).foreach { target => + conditionalWriteUntilPc = Some(math.max(conditionalWriteUntilPc.getOrElse(target), target)) + } } def result(): LuaPrototypeSemantics = { @@ -585,25 +590,41 @@ object LuaInstructionSemantics { localFlows += LuaLocalFlow(source, write, "same-instruction-dependence", BytecodeProvenance) semanticSteps += LuaSemanticStep(source, write, kind) } - reaching.get(slot).foreach { prior => - if (prior.nonEmpty && !prior.contains(write)) { - prior.foreach { first => - killOverwrites += LuaKillOverwrite( - s"${prototype.prototypeId}:pc${instruction.pc}:r$slot:kills:$first", - prototype.prototypeId, - first, - write, - write, - write, - "same-slot-overwrite-kills-prior-definition" - ) + if (conditionalWriteUntilPc.isDefined && isConditionalDefaultWrite(instruction)) { + reaching += slot -> (reaching.getOrElse(slot, Set.empty) + write) + } else { + reaching.get(slot).foreach { prior => + if (prior.nonEmpty && !prior.contains(write)) { + prior.foreach { first => + killOverwrites += LuaKillOverwrite( + s"${prototype.prototypeId}:pc${instruction.pc}:r$slot:kills:$first", + prototype.prototypeId, + first, + write, + write, + write, + "same-slot-overwrite-kills-prior-definition" + ) + } } } + reaching += slot -> Set(write) + closuresBySlot -= slot + closureTableWrites = closureTableWrites.filterNot { case ((tableSlot, _), _) => tableSlot == slot } } } - reaching += slot -> Set(write) - closuresBySlot -= slot - closureTableWrites = closureTableWrites.filterNot { case ((tableSlot, _), _) => tableSlot == slot } + + private def forwardJumpTargetPc(instruction: LuaInstruction): Option[Int] = + if (instruction.opcode == LuaOpcode.Jmp) { + val target = instruction.pc + 1 + instruction.b + Option.when(target > instruction.pc + 1)(target) + } else { + None + } + + private def isConditionalDefaultWrite(instruction: LuaInstruction): Boolean = + instruction.opcode == LuaOpcode.LoadK || instruction.opcode == LuaOpcode.LoadBool || + instruction.opcode == LuaOpcode.LoadNil private def isParamDerived(slot: Int): Boolean = reachesParameter(reaching.getOrElse(slot, Set.empty), Set.empty) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 665a02f558c3..11e4fde0e71e 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -835,6 +835,36 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r7 setAllWifi path through conditional call result flow" in { + withXiaomiStagingRows { stagingRows => + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val path = pathRows + .find(row => + row("source_module_path").str == "usr/lib/lua/luci/controller/api/xqnetwork.luac" && + row("source_function_name").str == "setAllWifi" && + row("source_pc").num.toInt == 40 && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == "usr/lib/lua/xiaoqiang/common/XQFunction.luac" && + row("sink_function_name").str == "nvramSet" && + row("sink_pc").num.toInt == 35 && + row("sink_trigger").str == "os.execute" && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + !row.obj.contains("callsite_id") + ) + .getOrElse(fail("missing setAllWifi to XQFunction.nvramSet strict path")) + + val steps = path("path_steps").arr.map(_.str) + steps should contain("usr/lib/lua/luci/controller/api/xqnetwork.luac::root.15@pc40:r13") + steps should contain("usr/lib/lua/luci/controller/api/xqnetwork.luac::root.15@pc287:r13") + steps should contain("usr/lib/lua/luci/controller/api/xqnetwork.luac::root.15@pc287:r48") + steps should contain("usr/lib/lua/luci/controller/api/xqnetwork.luac::root.15@pc300:r48") + steps should contain("usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac::root.41:r2") + steps should contain("usr/lib/lua/xiaoqiang/common/XQFunction.luac::root.33@pc35:r4") + } + } + "prune CrossPlatform captured-require bridge flows before local path search" in { withXiaomiExportDir { exportDir => val profile = ujson.read(Files.readString(exportDir.resolve("path-search-profile.json"))).obj From 977fdea09de515e412cbff71b51df81e1d97219a Mon Sep 17 00:00:00 2001 From: prankster009 Date: Sun, 12 Jul 2026 11:11:47 -0400 Subject: [PATCH 045/105] fix(lua2cpg): recover deleteTransportList iterator bridge paths --- .../bytecode/LuaProgramSemantics.scala | 32 ++++++++++++------- .../RealFirmwareEvidenceExportSmokeTest.scala | 32 +++++++++++++++++++ 2 files changed, 52 insertions(+), 12 deletions(-) diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaProgramSemantics.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaProgramSemantics.scala index 7fbe28a64007..436bf0fa932e 100644 --- a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaProgramSemantics.scala +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaProgramSemantics.scala @@ -1867,6 +1867,9 @@ object LuaProgramSemantics { .contains(resolved) ) + private def isRepresentativeIteratorCall(name: Option[String]): Boolean = + name.exists(Set("ipairs", "pairs").contains) + private def representativeTableValueEdges(module: ModuleSummary): Vector[(String, String)] = module.prototypes.flatMap { prototype => val tableSourcesBySlot = scala.collection.mutable.Map.empty[Int, Set[String]].withDefaultValue(Set.empty) @@ -1921,7 +1924,7 @@ object LuaProgramSemantics { private def representativeIteratorValueEdges(module: ModuleSummary): Vector[(String, String)] = module.prototypes.flatMap { prototype => prototype.calls - .filter(call => call.resolvedName.contains("ipairs")) + .filter(call => isRepresentativeIteratorCall(call.resolvedName)) .flatMap { iteratorCall => val iteratorInputs = iteratorCall.argumentRefs.map(qualify(module.path, _)) for { @@ -1962,18 +1965,23 @@ object LuaProgramSemantics { val loopValueSlots = tforLoop.c .map(count => (tforLoop.a + 3 until tforLoop.a + 3 + count).toSet) .getOrElse(Set.empty) - prototype.instructions + val bodyInstructions = prototype.instructions .filter(instruction => instruction.pc > loop.bodyStartPc && instruction.pc < tforLoop.pc) - .collect { - case instruction - if instruction.opcode == LuaOpcode.GetTable && - loopValueSlots(instruction.b) && - instruction.c.exists(_ >= RkConstantBase) => - val tableRead = qualify(modulePath, valueRef(prototype.prototypeId, instruction.pc, instruction.b)) - val fieldRead = qualify(modulePath, valueRef(prototype.prototypeId, instruction.pc, instruction.a)) - Vector(iteratorInput -> tableRead, tableRead -> fieldRead) - } - .flatten + val loopValueReadEdges = bodyInstructions.flatMap { instruction => + representativeReadSlots(prototype, instruction) + .filter(loopValueSlots) + .map(slot => iteratorInput -> qualify(modulePath, valueRef(prototype.prototypeId, instruction.pc, slot))) + } + val tableReadEdges = bodyInstructions.collect { + case instruction + if instruction.opcode == LuaOpcode.GetTable && + loopValueSlots(instruction.b) && + instruction.c.exists(_ >= RkConstantBase) => + val tableRead = qualify(modulePath, valueRef(prototype.prototypeId, instruction.pc, instruction.b)) + val fieldRead = qualify(modulePath, valueRef(prototype.prototypeId, instruction.pc, instruction.a)) + Vector(iteratorInput -> tableRead, tableRead -> fieldRead) + }.flatten + (loopValueReadEdges ++ tableReadEdges).distinct } private def representativeExpressionResultEdges(module: ModuleSummary): Vector[(String, String)] = diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 11e4fde0e71e..1ab75fccc8a8 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -865,6 +865,38 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r7 deleteTransportList path through XQBaiduPanUtil" in { + withXiaomiStagingRows { stagingRows => + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val path = pathRows + .find(row => + row("source_module_path").str == "usr/lib/lua/luci/controller/api/xqnetwork.luac" && + row("source_function_name").str == "deleteTransportList" && + row("source_pc").num.toInt == 28 && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == "usr/lib/lua/xiaoqiang/module/XQBaiduPanUtil.luac" && + row("sink_function_name").str == "kill_baidupan_process" && + row("sink_pc").num.toInt == 22 && + row("sink_trigger").str == "luci.util.exec" && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + !row.obj.contains("callsite_id") + ) + .getOrElse(fail("missing deleteTransportList to XQBaiduPanUtil.kill_baidupan_process strict path")) + + val steps = path("path_steps").arr.map(_.str) + steps should contain("usr/lib/lua/luci/controller/api/xqnetwork.luac::root.133@pc28:r8") + steps should contain("usr/lib/lua/luci/controller/api/xqnetwork.luac::root.133@pc87:r15") + steps should contain("usr/lib/lua/xiaoqiang/module/XQBaiduPanUtil.luac::root.39:r2") + steps should contain("usr/lib/lua/xiaoqiang/module/XQBaiduPanUtil.luac::root.39@pc65:r9") + steps should contain("usr/lib/lua/xiaoqiang/module/XQBaiduPanUtil.luac::root.39@pc75:r12") + steps should contain("usr/lib/lua/xiaoqiang/module/XQBaiduPanUtil.luac::root.39@pc76:r14") + steps should contain("usr/lib/lua/xiaoqiang/module/XQBaiduPanUtil.luac::root.37:r0") + steps should contain("usr/lib/lua/xiaoqiang/module/XQBaiduPanUtil.luac::root.37@pc22:r3") + } + } + "prune CrossPlatform captured-require bridge flows before local path search" in { withXiaomiExportDir { exportDir => val profile = ujson.read(Files.readString(exportDir.resolve("path-search-profile.json"))).obj From f3e3e6ab8561db85dcbaca40925e3e96aaa75470 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Sun, 12 Jul 2026 11:38:17 -0400 Subject: [PATCH 046/105] test(lua2cpg): cover pppoeStatus cross-platform path gap --- .../RealFirmwareEvidenceExportSmokeTest.scala | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 1ab75fccc8a8..4eac8818209c 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -835,6 +835,33 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r7 pppoeStatus path to luci util exec" in { + withXiaomiStagingRows { stagingRows => + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val path = pathRows + .find(row => + row("source_module_path").str == "usr/lib/lua/luci/controller/api/xqnetwork.luac" && + row("source_function_name").str == "pppoeStatus" && + row("source_pc").num.toInt == 6 && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == "usr/lib/lua/luci/util.luac" && + row("sink_function_name").str == "exec" && + row("sink_pc").num.toInt == 3 && + row("sink_trigger").str == "io.popen" && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + !row.obj.contains("callsite_id") + ) + .getOrElse(fail("missing pppoeStatus to luci.util.exec strict path")) + + val steps = path("path_steps").arr.map(_.str) + steps should contain("usr/lib/lua/luci/controller/api/xqnetwork.luac::root.55@pc6:r1") + steps should contain("usr/lib/lua/luci/util.luac::root.36:r0") + steps should contain("usr/lib/lua/luci/util.luac::root.36@pc3:r2") + } + } + "export CrossPlatform r7 setAllWifi path through conditional call result flow" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) From 85ee0445cd35375d44ea5291d42a7a0d674ac488 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Sun, 12 Jul 2026 20:04:07 -0400 Subject: [PATCH 047/105] test(lua2cpg): cover miats cross-platform path gap --- .../RealFirmwareEvidenceExportSmokeTest.scala | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 4eac8818209c..b07159af5503 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -862,6 +862,33 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r7 miats getWifiMacfilterInfo path to luci util exec" in { + withXiaomiStagingRows { stagingRows => + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val path = pathRows + .find(row => + row("source_module_path").str == "usr/lib/lua/luci/controller/api/miats.luac" && + row("source_function_name").str == "getWifiMacfilterInfo" && + row("source_pc").num.toInt == 70 && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == "usr/lib/lua/luci/util.luac" && + row("sink_function_name").str == "exec" && + row("sink_pc").num.toInt == 3 && + row("sink_trigger").str == "io.popen" && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + !row.obj.contains("callsite_id") + ) + .getOrElse(fail("missing miats.getWifiMacfilterInfo to luci.util.exec strict path")) + + val steps = path("path_steps").arr.map(_.str) + steps should contain("usr/lib/lua/luci/controller/api/miats.luac::root.3@pc70:r7") + steps should contain("usr/lib/lua/luci/util.luac::root.36:r0") + steps should contain("usr/lib/lua/luci/util.luac::root.36@pc3:r2") + } + } + "export CrossPlatform r7 setAllWifi path through conditional call result flow" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) From f429f4376f014b40271f70a3c0f0c7ae26def179 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Sun, 12 Jul 2026 21:58:13 -0400 Subject: [PATCH 048/105] fix(lua2cpg): preserve cross-platform sanitized bridge alternatives --- .../RealFirmwareEvidenceExportSmokeTest.scala | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index b07159af5503..0010432dbd58 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -889,6 +889,48 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "preserve CrossPlatform r7 unsanitized alternative when sanitizer bridge is present" in { + withXiaomiStagingRows { stagingRows => + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val wifiPath = pathRows + .find(row => + row("source_module_path").str == "usr/lib/lua/luci/controller/api/xqnetwork.luac" && + row("source_function_name").str == "setWifiMacfilter" && + row("source_pc").num.toInt == 34 && + row("sink_module_path").str == "usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac" && + row("sink_function_name").str == "setWiFiMacfilterModel" && + row("sink_pc").num.toInt == 384 && + row("sink_trigger").str == "os.execute" + ) + .getOrElse(fail("missing setWifiMacfilter to setWiFiMacfilterModel strict path")) + + wifiPath("classification").str shouldBe "true-positive" + wifiPath("sanitizer_hits").arr shouldBe empty + val wifiSteps = wifiPath("path_steps").arr.map(_.str) + wifiSteps should contain("usr/lib/lua/luci/controller/api/xqnetwork.luac::root.47@pc82:r10") + wifiSteps should not contain "usr/lib/lua/luci/controller/api/xqnetwork.luac::root.47@pc35:r7" + + val wanSpeedPath = pathRows + .find(row => + row("source_module_path").str == "usr/lib/lua/luci/controller/api/xqnetwork.luac" && + row("source_function_name").str == "setWanSpeed" && + row("source_pc").num.toInt == 7 && + row("sink_module_path").str == "usr/lib/lua/xiaoqiang/util/XQLanWanUtil.luac" && + row("sink_function_name").str == "setWanSpeed" && + row("sink_pc").num.toInt == 31 && + row("sink_trigger").str == "os.execute" + ) + .getOrElse(fail("missing setWanSpeed to XQLanWanUtil.setWanSpeed strict path")) + + wanSpeedPath("classification").str shouldBe "true-positive" + wanSpeedPath("sanitizer_hits").arr shouldBe empty + val wanSpeedSteps = wanSpeedPath("path_steps").arr.map(_.str) + wanSpeedSteps should contain("usr/lib/lua/luci/controller/api/xqnetwork.luac::root.82@pc13:r4") + wanSpeedSteps should not contain "usr/lib/lua/luci/controller/api/xqnetwork.luac::root.82@pc8:r1" + } + } + "export CrossPlatform r7 setAllWifi path through conditional call result flow" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) From 944a226fd87d5f656e4cdc334ea457c94f06b448 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Sun, 12 Jul 2026 22:21:42 -0400 Subject: [PATCH 049/105] test(lua2cpg): cover vpnSwitch cross-platform path gap --- .../RealFirmwareEvidenceExportSmokeTest.scala | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 0010432dbd58..ffcf33c0a351 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -889,6 +889,32 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r7 vpnSwitch path to XQCryptoUtil md5Str" in { + withXiaomiStagingRows { stagingRows => + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val path = pathRows + .find(row => + row("source_module_path").str == "usr/lib/lua/luci/controller/api/xqsystem.luac" && + row("source_function_name").str == "vpnSwitch" && + row("source_pc").num.toInt == 12 && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == "usr/lib/lua/xiaoqiang/util/XQCryptoUtil.luac" && + row("sink_function_name").str == "md5Str" && + row("sink_pc").num.toInt == 10 && + row("sink_trigger").str == "luci.util.exec" && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + !row.obj.contains("callsite_id") + ) + .getOrElse(fail("missing vpnSwitch to XQCryptoUtil.md5Str strict path")) + + val steps = path("path_steps").arr.map(_.str) + steps should contain("usr/lib/lua/luci/controller/api/xqsystem.luac::root.92@pc12:r2") + steps should contain("usr/lib/lua/xiaoqiang/util/XQCryptoUtil.luac::root.3@pc10:r4") + } + } + "preserve CrossPlatform r7 unsanitized alternative when sanitizer bridge is present" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) From e552a893d96166617b0a76543c30a64b1a9781d4 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Sun, 12 Jul 2026 23:19:51 -0400 Subject: [PATCH 050/105] test(lua2cpg): cover editDevice cross-platform path gap --- .../RealFirmwareEvidenceExportSmokeTest.scala | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index ffcf33c0a351..461f74ec96aa 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -915,6 +915,32 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r7 editDevice path to XQWifiUtil wl_editWiFiMacfilterList" in { + withXiaomiStagingRows { stagingRows => + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val path = pathRows + .find(row => + row("source_module_path").str == "usr/lib/lua/luci/controller/api/xqnetwork.luac" && + row("source_function_name").str == "editDevice" && + row("source_pc").num.toInt == 20 && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == "usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac" && + row("sink_function_name").str == "wl_editWiFiMacfilterList" && + row("sink_pc").num.toInt == 348 && + row("sink_trigger").str == "os.execute" && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + !row.obj.contains("callsite_id") + ) + .getOrElse(fail("missing editDevice to XQWifiUtil.wl_editWiFiMacfilterList strict path")) + + val steps = path("path_steps").arr.map(_.str) + steps should contain("usr/lib/lua/luci/controller/api/xqnetwork.luac::root.48@pc20:r7") + steps should contain("usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac::root.82@pc348:r17") + } + } + "preserve CrossPlatform r7 unsanitized alternative when sanitizer bridge is present" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) From af2ef2b19c1f31a09a98cbdf3c0f0081eed3d2d8 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Mon, 13 Jul 2026 04:57:52 -0400 Subject: [PATCH 051/105] fix(lua2cpg): recover scoped cross-platform editDevice path --- .../RealFirmwareEvidenceExportSmokeTest.scala | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 461f74ec96aa..e83057829448 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -916,7 +916,15 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } "export CrossPlatform r7 editDevice path to XQWifiUtil wl_editWiFiMacfilterList" in { - withXiaomiStagingRows { stagingRows => + withXiaomiExportDir { exportDir => + val profile = ujson.read(Files.readString(exportDir.resolve("path-search-profile.json"))).obj + profile("report_count").num.toInt should be <= 4000 + profile("local_path_search_count").num.toInt should be <= 30000 + + val stagingDir = exportDir.resolve("staging") + val stagingStream = Files.list(stagingDir) + val stagingRows = stagingStream.iterator.asScala.toVector.map(path => ujson.read(Files.readString(path)).obj) + try { val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) val path = pathRows @@ -938,6 +946,9 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { val steps = path("path_steps").arr.map(_.str) steps should contain("usr/lib/lua/luci/controller/api/xqnetwork.luac::root.48@pc20:r7") steps should contain("usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac::root.82@pc348:r17") + } finally { + stagingStream.close() + } } } From cc0978e6eeec32f0f95a88cb095bf7457ad537d0 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Mon, 13 Jul 2026 09:43:12 -0400 Subject: [PATCH 052/105] fix(lua2cpg): recover scoped cross-platform setRouterInfo paths --- .../RealFirmwareEvidenceExportSmokeTest.scala | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index e83057829448..92ea241bcf94 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -1024,6 +1024,61 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r8 setRouterInfo formvalue paths to XQFunction nvramSet" in { + withXiaomiStagingRows { stagingRows => + val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) + val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val sourceModule = "usr/lib/lua/luci/controller/api/misystem.luac" + val sinkModule = "usr/lib/lua/xiaoqiang/common/XQFunction.luac" + val expectedSources = Vector( + 74 -> "root.27@pc74:r17", + 86 -> "root.27@pc86:r20", + 90 -> "root.27@pc90:r21" + ) + + expectedSources.foreach { case (pc, localRef) => + sourceRows.exists(row => + row("module_path").str == sourceModule && + row("value_ref").str == localRef && + hasScopedCallsite(row, s"root.27@pc$pc") && + row("trigger").str == "luci.http.formvalue" + ) shouldBe true + } + + sinkRows.exists(row => + row("module_path").str == sinkModule && + row("value_ref").str == "root.33@pc35:r4" && + hasScopedCallsite(row, "root.33@pc35") && + row("trigger").str == "os.execute" + ) shouldBe true + + val missingPaths = expectedSources.collect { case (pc, localRef) + if !pathRows.exists(row => + row("source_module_path").str == sourceModule && + row("source_function_name").str == "setRouterInfo" && + row("source_pc").num.toInt == pc && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == sinkModule && + row("sink_function_name").str == "nvramSet" && + row("sink_pc").num.toInt == 35 && + row("sink_trigger").str == "os.execute" && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + row("path_steps").arr.exists(_.str == s"$sourceModule::$localRef") && + row("path_steps").arr.exists(_.str == s"$sinkModule::root.33@pc35:r4") && + !row.obj.contains("callsite_id") + ) => + s"setRouterInfo@pc$pc" + } + + withClue(s"missing r8 paths: ${missingPaths.mkString(", ")}") { + missingPaths shouldBe empty + } + } + } + "export CrossPlatform r7 deleteTransportList path through XQBaiduPanUtil" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) From c193b0a8d95feef8446151c0bc3c5ecd28fff0bd Mon Sep 17 00:00:00 2001 From: prankster009 Date: Mon, 13 Jul 2026 10:33:24 -0400 Subject: [PATCH 053/105] fix(lua2cpg): recover scoped cross-platform setAllWifi band paths --- .../RealFirmwareEvidenceExportSmokeTest.scala | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 92ea241bcf94..b0be75a51d94 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -1024,6 +1024,64 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r8 setAllWifi formvalue paths to XQFunction nvramSet" in { + withXiaomiStagingRows { stagingRows => + val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) + val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val sourceModule = "usr/lib/lua/luci/controller/api/xqnetwork.luac" + val sinkModule = "usr/lib/lua/xiaoqiang/common/XQFunction.luac" + val expectedSources = Vector( + 74 -> "root.15@pc74:r20", + 78 -> "root.15@pc78:r21", + 85 -> "root.15@pc85:r22", + 112 -> "root.15@pc112:r28", + 116 -> "root.15@pc116:r29", + 120 -> "root.15@pc120:r30" + ) + + expectedSources.foreach { case (pc, localRef) => + sourceRows.exists(row => + row("module_path").str == sourceModule && + row("value_ref").str == localRef && + hasScopedCallsite(row, s"root.15@pc$pc") && + row("trigger").str == "luci.http.formvalue" + ) shouldBe true + } + + sinkRows.exists(row => + row("module_path").str == sinkModule && + row("value_ref").str == "root.33@pc35:r4" && + hasScopedCallsite(row, "root.33@pc35") && + row("trigger").str == "os.execute" + ) shouldBe true + + val missingPaths = expectedSources.collect { case (pc, localRef) + if !pathRows.exists(row => + row("source_module_path").str == sourceModule && + row("source_function_name").str == "setAllWifi" && + row("source_pc").num.toInt == pc && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == sinkModule && + row("sink_function_name").str == "nvramSet" && + row("sink_pc").num.toInt == 35 && + row("sink_trigger").str == "os.execute" && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + row("path_steps").arr.exists(_.str == s"$sourceModule::$localRef") && + row("path_steps").arr.exists(_.str == s"$sinkModule::root.33@pc35:r4") && + !row.obj.contains("callsite_id") + ) => + s"setAllWifi@pc$pc" + } + + withClue(s"missing r8 paths: ${missingPaths.mkString(", ")}") { + missingPaths shouldBe empty + } + } + } + "export CrossPlatform r8 setRouterInfo formvalue paths to XQFunction nvramSet" in { withXiaomiStagingRows { stagingRows => val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) From a28311093c2d016414a7ea79e14597fba1119fb3 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Mon, 13 Jul 2026 11:29:02 -0400 Subject: [PATCH 054/105] fix(lua2cpg): recover scoped cross-platform XQSynchrodata paths --- .../RealFirmwareEvidenceExportSmokeTest.scala | 56 ++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index b0be75a51d94..fd9ca2abf566 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -732,7 +732,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { targetPath.isDefined shouldBe true val pathSteps = targetPath.get("path_steps").arr.map(_.str) pathSteps should contain("usr/lib/lua/luci/controller/api/xqnetwork.luac::root.93@pc28:r8") - pathSteps.exists(_.startsWith("usr/lib/lua/xiaoqiang/module/XQAPModule.luac::")) shouldBe true + pathSteps.exists(_.startsWith("usr/lib/lua/xiaoqiang/util/XQSysUtil.luac::")) shouldBe true pathSteps should contain("usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac::root.0@pc25:r4") pathSteps.foreach(_ should include("::")) targetPath.get.obj.contains("callsite_id") shouldBe false @@ -1137,6 +1137,60 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r8 misystem paths to XQSynchrodata" in { + withXiaomiStagingRows { stagingRows => + val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) + val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val sourceModule = "usr/lib/lua/luci/controller/api/misystem.luac" + val sinkModule = "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac" + val expectedSources = Vector( + ("setRouterInfo", 78, "root.27@pc78", "root.27@pc78:r18"), + ("setLanApMode_Init", 92, "root.40@pc92", "root.40@pc92:r19") + ) + + expectedSources.foreach { case (_, _, callsiteId, localRef) => + sourceRows.exists(row => + row("module_path").str == sourceModule && + row("value_ref").str == localRef && + hasScopedCallsite(row, callsiteId) && + row("trigger").str == "luci.http.formvalue" + ) shouldBe true + } + + sinkRows.exists(row => + row("module_path").str == sinkModule && + row("value_ref").str == "root.0@pc25:r4" && + hasScopedCallsite(row, "root.0@pc25") && + row("trigger").str == "os.execute" + ) shouldBe true + + val missingPaths = expectedSources.collect { case (functionName, pc, _, localRef) + if !pathRows.exists(row => + row("source_module_path").str == sourceModule && + row("source_function_name").str == functionName && + row("source_pc").num.toInt == pc && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == sinkModule && + row("sink_function_name").str == "func_unknow_0_0" && + row("sink_pc").num.toInt == 25 && + row("sink_trigger").str == "os.execute" && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + row("path_steps").arr.exists(_.str == s"$sourceModule::$localRef") && + row("path_steps").arr.exists(_.str == s"$sinkModule::root.0@pc25:r4") && + !row.obj.contains("callsite_id") + ) => + s"$functionName@pc$pc" + } + + withClue(s"missing r8 XQSynchrodata paths: ${missingPaths.mkString(", ")}") { + missingPaths shouldBe empty + } + } + } + "export CrossPlatform r7 deleteTransportList path through XQBaiduPanUtil" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) From a512c4e25a7d1959e88dde4ecf5ba71978dde745 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Mon, 13 Jul 2026 12:38:58 -0400 Subject: [PATCH 055/105] fix(lua2cpg): recover scoped cross-platform setRouterToBaidu paths --- .../RealFirmwareEvidenceExportSmokeTest.scala | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index fd9ca2abf566..ac144f422422 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -1191,6 +1191,59 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r8 setRouterToBaidu formvalue paths to local exec sink" in { + withXiaomiStagingRows { stagingRows => + val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) + val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val module = "usr/lib/lua/luci/controller/api/xqnetwork.luac" + val expectedSources = Vector( + 27 -> "root.131@pc27:r8", + 35 -> "root.131@pc35:r10" + ) + + expectedSources.foreach { case (pc, localRef) => + sourceRows.exists(row => + row("module_path").str == module && + row("value_ref").str == localRef && + hasScopedCallsite(row, s"root.131@pc$pc") && + row("trigger").str == "luci.http.formvalue" + ) shouldBe true + } + + sinkRows.exists(row => + row("module_path").str == module && + row("value_ref").str == "root.131@pc161:r19" && + hasScopedCallsite(row, "root.131@pc161") && + row("trigger").str == "luci.util.exec" + ) shouldBe true + + val missingPaths = expectedSources.collect { case (pc, localRef) + if !pathRows.exists(row => + row("source_module_path").str == module && + row("source_function_name").str == "setRouterToBaidu" && + row("source_pc").num.toInt == pc && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == module && + row("sink_function_name").str == "setRouterToBaidu" && + row("sink_pc").num.toInt == 161 && + row("sink_trigger").str == "luci.util.exec" && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + row("path_steps").arr.exists(_.str == s"$module::$localRef") && + row("path_steps").arr.exists(_.str == s"$module::root.131@pc161:r19") && + !row.obj.contains("callsite_id") + ) => + s"setRouterToBaidu@pc$pc" + } + + withClue(s"missing r8 setRouterToBaidu local exec paths: ${missingPaths.mkString(", ")}") { + missingPaths shouldBe empty + } + } + } + "export CrossPlatform r7 deleteTransportList path through XQBaiduPanUtil" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) From 08b8ca074854408b04f5be9ec61855323c93c1e3 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Mon, 13 Jul 2026 13:14:09 -0400 Subject: [PATCH 056/105] fix(lua2cpg): recover scoped cross-platform miats datacenter paths --- .../RealFirmwareEvidenceExportSmokeTest.scala | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index ac144f422422..d23d6f5a94db 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -1244,6 +1244,60 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r8 miats remote_call paths to datacenter requestDatacenter" in { + withXiaomiStagingRows { stagingRows => + val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) + val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val sourceModule = "usr/lib/lua/luci/controller/api/miats.luac" + val sinkModule = "usr/lib/lua/luci/controller/service/datacenter.luac" + val expectedSources = Vector( + 9 -> "root.9@pc9:r2", + 17 -> "root.9@pc17:r4" + ) + + expectedSources.foreach { case (pc, localRef) => + sourceRows.exists(row => + row("module_path").str == sourceModule && + row("value_ref").str == localRef && + hasScopedCallsite(row, s"root.9@pc$pc") && + row("trigger").str == "luci.http.formvalue" + ) shouldBe true + } + + sinkRows.exists(row => + row("module_path").str == sinkModule && + row("value_ref").str == "root.15@pc22:r6" && + hasScopedCallsite(row, "root.15@pc22") && + row("trigger").str == "luci.util.exec" + ) shouldBe true + + val missingPaths = expectedSources.collect { case (pc, localRef) + if !pathRows.exists(row => + row("source_module_path").str == sourceModule && + row("source_function_name").str == "remote_call" && + row("source_pc").num.toInt == pc && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == sinkModule && + row("sink_function_name").str == "requestDatacenter" && + row("sink_pc").num.toInt == 22 && + row("sink_trigger").str == "luci.util.exec" && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + row("path_steps").arr.exists(_.str == s"$sourceModule::$localRef") && + row("path_steps").arr.exists(_.str == s"$sinkModule::root.15@pc22:r6") && + !row.obj.contains("callsite_id") + ) => + s"miats.remote_call@pc$pc" + } + + withClue(s"missing r8 miats requestDatacenter paths: ${missingPaths.mkString(", ")}") { + missingPaths shouldBe empty + } + } + } + "export CrossPlatform r7 deleteTransportList path through XQBaiduPanUtil" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) From cbdc4dbe395e0ab9ab7eb018e272160627fb47ab Mon Sep 17 00:00:00 2001 From: prankster009 Date: Mon, 13 Jul 2026 13:48:19 -0400 Subject: [PATCH 057/105] fix(lua2cpg): recover scoped cross-platform setBaiduToRouter path --- .../RealFirmwareEvidenceExportSmokeTest.scala | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index d23d6f5a94db..b7737efc39ca 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -1244,6 +1244,52 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r8 setBaiduToRouter formvalue path to local exec sink" in { + withXiaomiStagingRows { stagingRows => + val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) + val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val module = "usr/lib/lua/luci/controller/api/xqnetwork.luac" + val sourceRef = "root.132@pc27:r8" + val sinkRef = "root.132@pc116:r16" + + sourceRows.exists(row => + row("module_path").str == module && + row("value_ref").str == sourceRef && + hasScopedCallsite(row, "root.132@pc27") && + row("trigger").str == "luci.http.formvalue" + ) shouldBe true + + sinkRows.exists(row => + row("module_path").str == module && + row("value_ref").str == sinkRef && + hasScopedCallsite(row, "root.132@pc116") && + row("trigger").str == "luci.util.exec" + ) shouldBe true + + val pathExists = pathRows.exists(row => + row("source_module_path").str == module && + row("source_function_name").str == "setBaiduToRouter" && + row("source_pc").num.toInt == 27 && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == module && + row("sink_function_name").str == "setBaiduToRouter" && + row("sink_pc").num.toInt == 116 && + row("sink_trigger").str == "luci.util.exec" && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + row("path_steps").arr.exists(_.str == s"$module::$sourceRef") && + row("path_steps").arr.exists(_.str == s"$module::$sinkRef") && + !row.obj.contains("callsite_id") + ) + + withClue("missing r8 setBaiduToRouter local exec path") { + pathExists shouldBe true + } + } + } + "export CrossPlatform r8 miats remote_call paths to datacenter requestDatacenter" in { withXiaomiStagingRows { stagingRows => val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) From 6b8911f62b43e2090d2aa28c5a89afeaf3362db1 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Mon, 13 Jul 2026 14:29:07 -0400 Subject: [PATCH 058/105] fix(lua2cpg): recover scoped cross-platform getTransListFileStat path --- .../RealFirmwareEvidenceExportSmokeTest.scala | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index b7737efc39ca..eee7b296ba9c 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -1290,6 +1290,63 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r8 getTransListFileStat sanitized path to local exec sink" in { + withXiaomiStagingRows { stagingRows => + val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) + val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val module = "usr/lib/lua/luci/controller/api/xqnetwork.luac" + val sourceRef = "root.137@pc26:r7" + val sinkRef = "root.137@pc68:r14" + + sourceRows.exists(row => + row("module_path").str == module && + row("value_ref").str == sourceRef && + hasScopedCallsite(row, "root.137@pc26") && + row("trigger").str == "luci.http.formvalue" + ) shouldBe true + + sinkRows.exists(row => + row("module_path").str == module && + row("value_ref").str == sinkRef && + hasScopedCallsite(row, "root.137@pc68") && + row("trigger").str == "luci.util.exec" + ) shouldBe true + + val path = pathRows + .find(row => + row("source_module_path").str == module && + row("source_function_name").str == "getTransListFileStat" && + row("source_pc").num.toInt == 26 && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == module && + row("sink_function_name").str == "getTransListFileStat" && + row("sink_pc").num.toInt == 68 && + row("sink_trigger").str == "luci.util.exec" && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + row("path_steps").arr.exists(_.str == s"$module::$sourceRef") && + row("path_steps").arr.exists(_.str == s"$module::$sinkRef") && + !row.obj.contains("callsite_id") + ) + .getOrElse(fail("missing r8 getTransListFileStat local exec path")) + + withClue( + s"path_steps=${path("path_steps").arr.map(_.str).mkString("[", ",", "]")} sanitizer_hits=${path("sanitizer_hits")}" + ) { + path("classification").str shouldBe "sanitized" + path("sanitizer_hits").arr.exists { hit => + val row = hit.obj + row("callsite_id").str == s"$module::root.137@pc60" && + row("sanitizer_name").str == "json.encode" && + row("applies_to_sink").bool && + row("on_dataflow_chain").bool + } shouldBe true + } + } + } + "export CrossPlatform r8 miats remote_call paths to datacenter requestDatacenter" in { withXiaomiStagingRows { stagingRows => val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) From 9cae8654d6f0ef6e1f2f2237e68821594b80916b Mon Sep 17 00:00:00 2001 From: prankster009 Date: Mon, 13 Jul 2026 14:55:29 -0400 Subject: [PATCH 059/105] fix(lua2cpg): recover scoped cross-platform tunnelSmartHomeRequest path --- .../RealFirmwareEvidenceExportSmokeTest.scala | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index eee7b296ba9c..febc4ace0fd2 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -1347,6 +1347,53 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r8 tunnelSmartHomeRequest formvalue path to local exec sink" in { + withXiaomiStagingRows { stagingRows => + val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) + val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val module = "usr/lib/lua/luci/controller/api/xqsmarthome.luac" + val sourceRef = "root.1@pc7:r2" + val sinkRef = "root.1@pc19:r6" + + sourceRows.exists(row => + row("module_path").str == module && + row("value_ref").str == sourceRef && + hasScopedCallsite(row, "root.1@pc7") && + row("trigger").str == "luci.http.formvalue" + ) shouldBe true + + sinkRows.exists(row => + row("module_path").str == module && + row("value_ref").str == sinkRef && + hasScopedCallsite(row, "root.1@pc19") && + row("trigger").str == "luci.util.exec" + ) shouldBe true + + val pathExists = pathRows.exists(row => + row("source_module_path").str == module && + row("source_function_name").str == "tunnelSmartHomeRequest" && + row("source_pc").num.toInt == 7 && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == module && + row("sink_function_name").str == "tunnelSmartHomeRequest" && + row("sink_pc").num.toInt == 19 && + row("sink_trigger").str == "luci.util.exec" && + row("classification").str == "true-positive" && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + row("path_steps").arr.exists(_.str == s"$module::$sourceRef") && + row("path_steps").arr.exists(_.str == s"$module::$sinkRef") && + !row.obj.contains("callsite_id") + ) + + withClue("missing r8 tunnelSmartHomeRequest local exec path") { + pathExists shouldBe true + } + } + } + "export CrossPlatform r8 miats remote_call paths to datacenter requestDatacenter" in { withXiaomiStagingRows { stagingRows => val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) From 67ab3dfe2401abc38f86ec7d6648fe0c31779796 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Mon, 13 Jul 2026 15:15:32 -0400 Subject: [PATCH 060/105] fix(lua2cpg): recover scoped cross-platform tunnelSmartControllerRequest path --- .../RealFirmwareEvidenceExportSmokeTest.scala | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index febc4ace0fd2..9a650370a622 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -1394,6 +1394,53 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r8 tunnelSmartControllerRequest formvalue path to local exec sink" in { + withXiaomiStagingRows { stagingRows => + val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) + val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val module = "usr/lib/lua/luci/controller/api/xqsmarthome.luac" + val sourceRef = "root.2@pc19:r5" + val sinkRef = "root.2@pc79:r10" + + sourceRows.exists(row => + row("module_path").str == module && + row("value_ref").str == sourceRef && + hasScopedCallsite(row, "root.2@pc19") && + row("trigger").str == "luci.http.formvalue" + ) shouldBe true + + sinkRows.exists(row => + row("module_path").str == module && + row("value_ref").str == sinkRef && + hasScopedCallsite(row, "root.2@pc79") && + row("trigger").str == "luci.util.exec" + ) shouldBe true + + val pathExists = pathRows.exists(row => + row("source_module_path").str == module && + row("source_function_name").str == "tunnelSmartControllerRequest" && + row("source_pc").num.toInt == 19 && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == module && + row("sink_function_name").str == "tunnelSmartControllerRequest" && + row("sink_pc").num.toInt == 79 && + row("sink_trigger").str == "luci.util.exec" && + row("classification").str == "true-positive" && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + row("path_steps").arr.exists(_.str == s"$module::$sourceRef") && + row("path_steps").arr.exists(_.str == s"$module::$sinkRef") && + !row.obj.contains("callsite_id") + ) + + withClue("missing r8 tunnelSmartControllerRequest local exec path") { + pathExists shouldBe true + } + } + } + "export CrossPlatform r8 miats remote_call paths to datacenter requestDatacenter" in { withXiaomiStagingRows { stagingRows => val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) From d7ff9c5809af8f7ce619c63db64a34947ea22bf1 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Mon, 13 Jul 2026 15:36:09 -0400 Subject: [PATCH 061/105] fix(lua2cpg): recover scoped cross-platform setMeshInfo path --- .../RealFirmwareEvidenceExportSmokeTest.scala | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 9a650370a622..1ac39ad5e182 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -1441,6 +1441,53 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r8 setMeshInfo formvalue path to forkExec sink" in { + withXiaomiStagingRows { stagingRows => + val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) + val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val module = "usr/lib/lua/luci/controller/api/misystem.luac" + val sourceRef = "root.28@pc47:r12" + val sinkRef = "root.28@pc345:r23" + + sourceRows.exists(row => + row("module_path").str == module && + row("value_ref").str == sourceRef && + hasScopedCallsite(row, "root.28@pc47") && + row("trigger").str == "luci.http.formvalue" + ) shouldBe true + + sinkRows.exists(row => + row("module_path").str == module && + row("value_ref").str == sinkRef && + hasScopedCallsite(row, "root.28@pc345") && + row("trigger").str == "test.api.Process.forkExec" + ) shouldBe true + + val pathExists = pathRows.exists(row => + row("source_module_path").str == module && + row("source_function_name").str == "setMeshInfo" && + row("source_pc").num.toInt == 47 && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == module && + row("sink_function_name").str == "setMeshInfo" && + row("sink_pc").num.toInt == 345 && + row("sink_trigger").str == "test.api.Process.forkExec" && + row("classification").str == "true-positive" && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + row("path_steps").arr.exists(_.str == s"$module::$sourceRef") && + row("path_steps").arr.exists(_.str == s"$module::$sinkRef") && + !row.obj.contains("callsite_id") + ) + + withClue("missing r8 setMeshInfo forkExec path") { + pathExists shouldBe true + } + } + } + "export CrossPlatform r8 miats remote_call paths to datacenter requestDatacenter" in { withXiaomiStagingRows { stagingRows => val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) From 2ed24f46dfc6d029feb9487a126d375971d42d69 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Mon, 13 Jul 2026 16:11:27 -0400 Subject: [PATCH 062/105] fix(lua2cpg): recover scoped cross-platform datacenter local exec path --- .../RealFirmwareEvidenceExportSmokeTest.scala | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 1ac39ad5e182..fac6ffb8639c 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -1488,6 +1488,53 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r8 datacenter setSyncRouterFile path to tunnelRequestDatacenter exec sink" in { + withXiaomiStagingRows { stagingRows => + val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) + val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val module = "usr/lib/lua/luci/controller/service/datacenter.luac" + val sourceRef = "root.1@pc6:r1" + val sinkRef = "root.14@pc24:r7" + + sourceRows.exists(row => + row("module_path").str == module && + row("value_ref").str == sourceRef && + hasScopedCallsite(row, "root.1@pc6") && + row("trigger").str == "luci.http.formvalue" + ) shouldBe true + + sinkRows.exists(row => + row("module_path").str == module && + row("value_ref").str == sinkRef && + hasScopedCallsite(row, "root.14@pc24") && + row("trigger").str == "luci.util.exec" + ) shouldBe true + + val pathExists = pathRows.exists(row => + row("source_module_path").str == module && + row("source_function_name").str == "setSyncRouterFile" && + row("source_pc").num.toInt == 6 && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == module && + row("sink_function_name").str == "tunnelRequestDatacenter" && + row("sink_pc").num.toInt == 24 && + row("sink_trigger").str == "luci.util.exec" && + row("classification").str == "true-positive" && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + row("path_steps").arr.exists(_.str == s"$module::$sourceRef") && + row("path_steps").arr.exists(_.str == s"$module::$sinkRef") && + !row.obj.contains("callsite_id") + ) + + withClue("missing r8 datacenter setSyncRouterFile local exec path") { + pathExists shouldBe true + } + } + } + "export CrossPlatform r8 miats remote_call paths to datacenter requestDatacenter" in { withXiaomiStagingRows { stagingRows => val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) From 22d748872711e4153cb5e8861318cabf02ed5211 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Mon, 13 Jul 2026 16:44:24 -0400 Subject: [PATCH 063/105] fix(lua2cpg): recover scoped cross-platform setSysTime path --- .../RealFirmwareEvidenceExportSmokeTest.scala | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index fac6ffb8639c..0a9a1578aae5 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -1535,6 +1535,60 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r8 setSysTime path to XQSysUtil setSysTime forkExec sink" in { + withXiaomiStagingRows { stagingRows => + val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) + val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val sourceModule = "usr/lib/lua/luci/controller/api/misystem.luac" + val sinkModule = "usr/lib/lua/xiaoqiang/util/XQSysUtil.luac" + val sourceRef = "root.144@pc5:r1" + val sinkRef = "root.108@pc112:r4" + + sourceRows.exists(row => + row("module_path").str == sourceModule && + row("value_ref").str == sourceRef && + hasScopedCallsite(row, "root.144@pc5") && + row("trigger").str == "luci.http.formvalue" + ) shouldBe true + + sinkRows.exists(row => + row("module_path").str == sinkModule && + row("value_ref").str == sinkRef && + hasScopedCallsite(row, "root.108@pc112") && + row("trigger").str == "test.api.Process.forkExec" + ) shouldBe true + + val path = pathRows.find(row => + row("source_module_path").str == sourceModule && + row("source_function_name").str == "setSysTime" && + row("source_pc").num.toInt == 5 && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == sinkModule && + row("sink_function_name").str == "setSysTime" && + row("sink_pc").num.toInt == 112 && + row("sink_trigger").str == "test.api.Process.forkExec" && + row("classification").str == "true-positive" && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + row("path_steps").arr.exists(_.str == s"$sourceModule::$sourceRef") && + row("path_steps").arr.exists(_.str == s"$sinkModule::$sinkRef") && + !row.obj.contains("callsite_id") + ) + + withClue("missing r8 setSysTime XQSysUtil forkExec path") { + path.isDefined shouldBe true + } + val steps = path.get("path_steps").arr.map(_.str) + steps should contain(s"$sinkModule::root.108@pc98:r3") + steps should contain(s"$sinkModule::root.108@pc103:r3") + steps should contain(s"$sinkModule::root.108@pc109:r0") + steps should contain(s"$sinkModule::root.108@pc111:r4") + steps should contain(s"$sinkModule::$sinkRef") + } + } + "export CrossPlatform r8 miats remote_call paths to datacenter requestDatacenter" in { withXiaomiStagingRows { stagingRows => val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) From 7670e1e38786b980054a71f2dbfa1ee770b2288a Mon Sep 17 00:00:00 2001 From: prankster009 Date: Mon, 13 Jul 2026 17:15:08 -0400 Subject: [PATCH 064/105] fix(lua2cpg): recover scoped cross-platform webAccessControl path --- .../RealFirmwareEvidenceExportSmokeTest.scala | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 0a9a1578aae5..a69df7e7e106 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -1589,6 +1589,63 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r8 webAccess path to XQSysUtil webAccessControl exec sink" in { + withXiaomiStagingRows { stagingRows => + val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) + val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val sourceModule = "usr/lib/lua/luci/controller/api/misystem.luac" + val sinkModule = "usr/lib/lua/xiaoqiang/util/XQSysUtil.luac" + val sourceRef = "root.136@pc17:r2" + val bridgeRef = "root.136@pc56:r7" + val paramRef = "root.105:r1" + val sinkRef = "root.105@pc32:r8" + + sourceRows.exists(row => + row("module_path").str == sourceModule && + row("value_ref").str == sourceRef && + hasScopedCallsite(row, "root.136@pc17") && + row("trigger").str == "luci.http.formvalue" + ) shouldBe true + + sinkRows.exists(row => + row("module_path").str == sinkModule && + row("value_ref").str == sinkRef && + hasScopedCallsite(row, "root.105@pc32") && + row("trigger").str == "os.execute" + ) shouldBe true + + val path = pathRows.find(row => + row("source_module_path").str == sourceModule && + row("source_function_name").str == "webAccess" && + row("source_pc").num.toInt == 17 && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == sinkModule && + row("sink_function_name").str == "webAccessControl" && + row("sink_pc").num.toInt == 32 && + row("sink_trigger").str == "os.execute" && + row("classification").str == "true-positive" && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + row("path_steps").arr.exists(_.str == s"$sourceModule::$sourceRef") && + row("path_steps").arr.exists(_.str == s"$sourceModule::$bridgeRef") && + row("path_steps").arr.exists(_.str == s"$sinkModule::$paramRef") && + row("path_steps").arr.exists(_.str == s"$sinkModule::$sinkRef") && + !row.obj.contains("callsite_id") + ) + + withClue("missing r8 webAccess XQSysUtil webAccessControl os.execute path") { + path.isDefined shouldBe true + } + val steps = path.get("path_steps").arr.map(_.str) + steps should contain(s"$sinkModule::root.105@pc11:r6") + steps should contain(s"$sinkModule::root.105@pc28:r6") + steps should contain(s"$sinkModule::root.105@pc31:r8") + steps should contain(s"$sinkModule::$sinkRef") + } + } + "export CrossPlatform r8 miats remote_call paths to datacenter requestDatacenter" in { withXiaomiStagingRows { stagingRows => val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) From d5ff99e40151b62781ba532853acf3f910a028d2 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Mon, 13 Jul 2026 18:02:23 -0400 Subject: [PATCH 065/105] fix(lua2cpg): resolve cross-platform module-global table field path --- .../RealFirmwareEvidenceExportSmokeTest.scala | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index a69df7e7e106..04c55aa713bc 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -1646,6 +1646,82 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r8 pingTest path to luci sys exec sink" in { + withXiaomiStagingRows { stagingRows => + val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) + val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) + val exportRows = stagingRows.flatMap(_("module_return_table").arr.map(_.obj)) + val linkRows = stagingRows.flatMap(_("module_linkage").arr.map(_.obj)) + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val sourceModule = "usr/lib/lua/luci/controller/api/xqnetdetect.luac" + val sinkModule = "usr/lib/lua/luci/sys.luac" + val sourceRef = "root.3@pc6:r1" + val bridgeRef = "root.3@pc10:r3" + val sinkRef = "root.25@pc9:r2" + + sourceRows.exists(row => + row("module_path").str == sourceModule && + row("value_ref").str == sourceRef && + hasScopedCallsite(row, "root.3@pc6") && + row("trigger").str == "luci.http.formvalue" + ) shouldBe true + + sinkRows.exists(row => + row("module_path").str == sinkModule && + row("value_ref").str == sinkRef && + hasScopedCallsite(row, "root.25@pc9") && + row("trigger").str == "os.execute" + ) shouldBe true + + exportRows.exists(row => + row("module_path").str == sinkModule && + row("table_ref").str == s"$sinkModule:module-global" && + row("field_name").str == "net.pingtest" && + row("target_prototype_id").str == "root.25" + ) shouldBe true + + linkRows.exists(row => + row("module_path").str == sourceModule && + hasScopedCallsite(row, "root.3@pc10") && + row("target_module_path").str == sinkModule && + row("target_prototype_id").str == "root.25" && + row("field_name").str == "net.pingtest" + ) shouldBe true + + val path = pathRows.find(row => + row("source_module_path").str == sourceModule && + row("source_function_name").str == "pingTest" && + row("source_pc").num.toInt == 6 && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == sinkModule && + row("sink_function_name").str == "net.pingtest" && + row("sink_pc").num.toInt == 9 && + row("sink_trigger").str == "os.execute" && + row("classification").str == "true-positive" && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + row("path_steps").arr.exists(_.str == s"$sourceModule::$sourceRef") && + row("path_steps").arr.exists(_.str == s"$sourceModule::$bridgeRef") && + row("path_steps").arr.exists(_.str == s"$sinkModule::$sinkRef") && + !row.obj.contains("callsite_id") + ) + + withClue("missing r8 pingTest luci.sys os.execute path") { + path.isDefined shouldBe true + } + val steps = path.get("path_steps").arr.map(_.str) + steps should contain(s"$sourceModule::root.3@pc9:r1") + steps should contain(s"$sourceModule::$bridgeRef") + steps should contain(s"$sinkModule::root.25:r0") + steps should contain(s"$sinkModule::root.25@pc3:r4") + steps should contain(s"$sinkModule::root.25@pc6:r4") + steps should contain(s"$sinkModule::root.25@pc6:r3") + steps should contain(s"$sinkModule::root.25@pc8:r2") + steps should contain(s"$sinkModule::$sinkRef") + } + } + "export CrossPlatform r8 miats remote_call paths to datacenter requestDatacenter" in { withXiaomiStagingRows { stagingRows => val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) From d493a3969c18bb32f94c02d43fce874b89b4b726 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 15 Jul 2026 00:54:51 -0400 Subject: [PATCH 066/105] fix(lua2cpg): classify reference-analyzer real-firmware sanitizer hits --- .../RealFirmwareEvidenceExportSmokeTest.scala | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 04c55aa713bc..746598eb3a6f 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -952,6 +952,49 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r7 editDevice path with referenceAnalyzer known _cmdformat sanitizer hit" in { + withXiaomiStagingRows { stagingRows => + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val sourceModule = "usr/lib/lua/luci/controller/api/xqnetwork.luac" + val sinkModule = "usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac" + val sanitizerModule = "usr/lib/lua/xiaoqiang/common/XQFunction.luac" + val sanitizerCall = s"$sanitizerModule::root.0@pc12" + val sanitizerValue = s"$sanitizerCall:r1" + + val path = pathRows + .find(row => + row("source_module_path").str == sourceModule && + row("source_function_name").str == "editDevice" && + row("source_pc").num.toInt == 20 && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == sinkModule && + row("sink_function_name").str == "wl_editWiFiMacfilterList" && + row("sink_pc").num.toInt == 348 && + row("sink_trigger").str == "os.execute" && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + row("path_steps").arr.exists(_.str == sanitizerValue) && + !row.obj.contains("callsite_id") + ) + .getOrElse(fail("missing editDevice to wl_editWiFiMacfilterList strict path")) + + withClue( + s"path_steps=${path("path_steps").arr.map(_.str).mkString("[", ",", "]")} sanitizer_hits=${path("sanitizer_hits")}" + ) { + path("classification").str shouldBe "sanitized" + path("sanitizer_hits").arr.exists { hit => + val row = hit.obj + row("callsite_id").str == sanitizerCall && + row("callsite_id").str.contains("::") && + row("sanitizer_name").str == "_cmdformat" && + row("applies_to_sink").bool && + row("on_dataflow_chain").bool + } shouldBe true + } + } + } + "preserve CrossPlatform r7 unsanitized alternative when sanitizer bridge is present" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) From 8a40b730aacd05e6947088c2c88159a9997223e7 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 15 Jul 2026 01:33:21 -0400 Subject: [PATCH 067/105] test(lua2cpg): align cross-platform sanitizer guard with baseline --- .../RealFirmwareEvidenceExportSmokeTest.scala | 43 ++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 746598eb3a6f..8a35515f8ee0 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -995,7 +995,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } - "preserve CrossPlatform r7 unsanitized alternative when sanitizer bridge is present" in { + "preserve CrossPlatform r7 baseline sanitizer classifications for setWifiMacfilter and setWanSpeed" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) @@ -1004,18 +1004,21 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { row("source_module_path").str == "usr/lib/lua/luci/controller/api/xqnetwork.luac" && row("source_function_name").str == "setWifiMacfilter" && row("source_pc").num.toInt == 34 && - row("sink_module_path").str == "usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac" && - row("sink_function_name").str == "setWiFiMacfilterModel" && - row("sink_pc").num.toInt == 384 && + row("sink_module_path").str == "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac" && + row("sink_function_name").str == "func_unknow_0_0" && + row("sink_pc").num.toInt == 25 && row("sink_trigger").str == "os.execute" ) - .getOrElse(fail("missing setWifiMacfilter to setWiFiMacfilterModel strict path")) + .getOrElse(fail("missing setWifiMacfilter to XQSynchrodata.func_unknow_0_0 strict path")) - wifiPath("classification").str shouldBe "true-positive" - wifiPath("sanitizer_hits").arr shouldBe empty - val wifiSteps = wifiPath("path_steps").arr.map(_.str) - wifiSteps should contain("usr/lib/lua/luci/controller/api/xqnetwork.luac::root.47@pc82:r10") - wifiSteps should not contain "usr/lib/lua/luci/controller/api/xqnetwork.luac::root.47@pc35:r7" + wifiPath("classification").str shouldBe "sanitized" + wifiPath("sanitizer_hits").arr.exists { hit => + val row = hit.obj + row("callsite_id").str == "usr/lib/lua/luci/controller/api/xqnetwork.luac::root.47@pc35" && + row("sanitizer_name").str == "tonumber" && + row("applies_to_sink").bool && + row("on_dataflow_chain").bool + } shouldBe true val wanSpeedPath = pathRows .find(row => @@ -1029,11 +1032,21 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { ) .getOrElse(fail("missing setWanSpeed to XQLanWanUtil.setWanSpeed strict path")) - wanSpeedPath("classification").str shouldBe "true-positive" - wanSpeedPath("sanitizer_hits").arr shouldBe empty - val wanSpeedSteps = wanSpeedPath("path_steps").arr.map(_.str) - wanSpeedSteps should contain("usr/lib/lua/luci/controller/api/xqnetwork.luac::root.82@pc13:r4") - wanSpeedSteps should not contain "usr/lib/lua/luci/controller/api/xqnetwork.luac::root.82@pc8:r1" + wanSpeedPath("classification").str shouldBe "sanitized" + wanSpeedPath("sanitizer_hits").arr.exists { hit => + val row = hit.obj + row("callsite_id").str == "usr/lib/lua/luci/controller/api/xqnetwork.luac::root.82@pc8" && + row("sanitizer_name").str == "tonumber" && + row("applies_to_sink").bool && + row("on_dataflow_chain").bool + } shouldBe true + wanSpeedPath("sanitizer_hits").arr.exists { hit => + val row = hit.obj + row("callsite_id").str == "usr/lib/lua/xiaoqiang/util/XQLanWanUtil.luac::root.68@pc5" && + row("sanitizer_name").str == "tonumber" && + row("applies_to_sink").bool && + row("on_dataflow_chain").bool + } shouldBe true } } From 89f70d34cd3b98bc02b926612a2b9ac71eae4216 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 15 Jul 2026 02:47:27 -0400 Subject: [PATCH 068/105] fix(lua2cpg): preserve sanitizer call arguments --- .../LuaRealFirmwareEvidenceExporter.scala | 11 ++++++++-- .../RealFirmwareEvidenceExportSmokeTest.scala | 20 +++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaRealFirmwareEvidenceExporter.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaRealFirmwareEvidenceExporter.scala index ba0d482b746b..31da8e636ac3 100644 --- a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaRealFirmwareEvidenceExporter.scala +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaRealFirmwareEvidenceExporter.scala @@ -186,9 +186,16 @@ object LuaRealFirmwareEvidenceExporter { ) } } + val decodedRowsByScopedCallsite = decodedRows.map(row => row("callsite_id").str -> row).toMap val sanitizerRows = semantics.sanitizerCalls.flatMap { row => val (modulePath, callsiteId) = splitQualifiedRef(row.callsiteId) if (modulePath == relativeName) { + val scopedSanitizerCallsite = toScopedStepRef(row.callsiteId) + val decodedRow = decodedRowsByScopedCallsite.getOrElse( + scopedSanitizerCallsite, + throw new IllegalStateException(s"missing decoded call row for sanitizer call: $scopedSanitizerCallsite") + ) + val argumentRefs = decodedRow("argument_value_refs").arr.map(_.str).toVector Vector( ujson.Obj( "resolution_id" -> s"${row.callsiteId}:${row.sanitizerName}", @@ -197,7 +204,7 @@ object LuaRealFirmwareEvidenceExporter { "module_path" -> modulePath, "prototype_id" -> prototypeIdFromCallsiteId(callsiteId), "pc" -> pcFromCallsiteId(callsiteId), - "callsite_id" -> toScopedStepRef(row.callsiteId), + "callsite_id" -> scopedSanitizerCallsite, "target_value_ref" -> splitQualifiedRef(row.sanitizedValueRef)._2, "bytecode_pattern" -> s"sanitizer:${row.sanitizerName}", "resolved_name" -> row.sanitizerName, @@ -206,7 +213,7 @@ object LuaRealFirmwareEvidenceExporter { "confidence" -> "bytecode-derived", "provenance" -> row.provenance, "unresolved_reason" -> "none", - "argument_value_refs" -> Vector(splitQualifiedRef(row.sanitizedValueRef)._2), + "argument_value_refs" -> argumentRefs, "return_value_refs" -> Vector(splitQualifiedRef(row.sanitizedValueRef)._2), "argument_constants" -> ujson.Arr(), "direct_target_prototype_ids" -> ujson.Arr() diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 8a35515f8ee0..9ff02338ffbc 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -2056,6 +2056,26 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform sanitizer call rows with original call argument refs" in { + withXiaomiStagingRows { stagingRows => + val callRows = stagingRows.flatMap(_("call_name_resolution").arr.map(_.obj)) + + val sanitizerRow = callRows + .find(row => + row("module_path").str == "usr/lib/lua/luci/controller/api/xqnetwork.luac" && + row("callsite_id").str == "usr/lib/lua/luci/controller/api/xqnetwork.luac::root.93@pc101" && + row("resolved_name").str == "xiaoqiang.module.XQAPModule.setWifiAPMode" && + row("resolution_kind").str == "sanitizer-call" + ) + .getOrElse(fail("missing setWifiAPMode sanitizer call row")) + + val argumentRefs = sanitizerRow("argument_value_refs").arr.map(_.str).toVector + argumentRefs should contain("root.93@pc101:r29") + argumentRefs should contain("root.93@pc101:r32") + argumentRefs should not contain "root.93@pc101:r21" + } + } + "reject CrossPlatform representative cross-module paths without source callsite bridge" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) From 6519ade41a7bc9855a9f16d6cabb699d9841e494 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 15 Jul 2026 03:44:20 -0400 Subject: [PATCH 069/105] fix(lua2cpg): require requestMitv string.match sanitizer path --- .../RealFirmwareEvidenceExportSmokeTest.scala | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 9ff02338ffbc..bed98d326d9b 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -817,6 +817,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { doExecSteps should contain("usr/lib/lua/xiaoqiang/util/XQMitvUtil.luac::root.1@pc7:r1") doExecSteps.exists(_.startsWith("usr/lib/lua/xiaoqiang/util/XQMitvUtil.luac::root.0")) shouldBe true doExecSteps should contain("usr/lib/lua/xiaoqiang/util/XQMitvUtil.luac::root.0@pc10:r3") + assertRequestMitvStringMatchSanitizer(doExecPath) val popenPath = requestMitvPath( "usr/lib/lua/luci/util.luac", @@ -832,6 +833,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { popenSteps should contain("usr/lib/lua/xiaoqiang/util/XQMitvUtil.luac::root.0@pc10:r3") popenSteps should contain("usr/lib/lua/luci/util.luac::root.36:r0") popenSteps should contain("usr/lib/lua/luci/util.luac::root.36@pc3:r2") + assertRequestMitvStringMatchSanitizer(popenPath) } } @@ -2485,6 +2487,19 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { private def hasScopedCallsite(row: ujson.Obj, localCallsiteId: String): Boolean = row("callsite_id").str.contains("::") && row("callsite_id").str.endsWith(s"::$localCallsiteId") + private def assertRequestMitvStringMatchSanitizer(path: ujson.Obj): Unit = { + val sanitizerCall = "usr/lib/lua/xiaoqiang/util/XQMitvUtil.luac::root.1@pc36" + path("classification").str shouldBe "sanitized" + path("path_steps").arr.exists(_.str == s"$sanitizerCall:r3") shouldBe true + path("sanitizer_hits").arr.exists { hit => + val row = hit.obj + row("callsite_id").str == sanitizerCall && + row("sanitizer_name").str == "string.match" && + row("applies_to_sink").bool && + row("on_dataflow_chain").bool + } shouldBe true + } + private def withXiaomiStagingRows(test: Vector[ujson.Obj] => Unit): Unit = { withXiaomiExportDir { exportDir => val stagingDir = exportDir.resolve("staging") From c7ce06de4195ec138ede13d5a11d8a92ae6b53ca Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 15 Jul 2026 05:19:33 -0400 Subject: [PATCH 070/105] fix(lua2cpg): require setConfigIotDevHidessid _cmdformat sanitizer paths --- .../RealFirmwareEvidenceExportSmokeTest.scala | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index bed98d326d9b..4af69fda88ab 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -997,6 +997,58 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r7 setConfigIotDevHidessid paths with referenceAnalyzer known _cmdformat sanitizer hits" in { + withXiaomiStagingRows { stagingRows => + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val module = "usr/lib/lua/luci/controller/api/misystem.luac" + val expectedPairs = Vector( + 11 -> 161, + 11 -> 186, + 19 -> 161, + 19 -> 186, + 23 -> 161, + 23 -> 186, + 27 -> 161, + 27 -> 186, + 31 -> 186, + 35 -> 186 + ) + + expectedPairs.foreach { case (sourcePc, sinkPc) => + val path = pathRows + .find(row => + row("source_module_path").str == module && + row("source_function_name").str == "setConfigIotDevHidessid" && + row("source_pc").num.toInt == sourcePc && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == module && + row("sink_function_name").str == "setConfigIotDevHidessid" && + row("sink_pc").num.toInt == sinkPc && + row("sink_trigger").str == "test.api.Process.forkExec" && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + !row.obj.contains("callsite_id") + ) + .getOrElse(fail(s"missing setConfigIotDevHidessid strict path sourcePc=$sourcePc sinkPc=$sinkPc")) + + withClue( + s"sourcePc=$sourcePc sinkPc=$sinkPc path_steps=${path("path_steps").arr.map(_.str).mkString("[", ",", "]")} sanitizer_hits=${path("sanitizer_hits")}" + ) { + path("classification").str shouldBe "sanitized" + path("sanitizer_hits").arr.exists { hit => + val row = hit.obj + row("callsite_id").str.startsWith(s"$module::root.172@pc") && + row("callsite_id").str.contains("::") && + row("sanitizer_name").str == "test.api.Process._cmdformat" && + row("applies_to_sink").bool && + row("on_dataflow_chain").bool + } shouldBe true + } + } + } + } + "preserve CrossPlatform r7 baseline sanitizer classifications for setWifiMacfilter and setWanSpeed" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) From 1ec9ceb565ac09fca7a127caf27891275a2e42ec Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 15 Jul 2026 05:47:39 -0400 Subject: [PATCH 071/105] fix(lua2cpg): require addMeshNode _strformat sanitizer paths --- .../RealFirmwareEvidenceExportSmokeTest.scala | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 4af69fda88ab..fc36899b40e3 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -1049,6 +1049,51 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r7 addMeshNode paths with referenceAnalyzer known _strformat sanitizer hits" in { + withXiaomiStagingRows { stagingRows => + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val sourceModule = "usr/lib/lua/luci/controller/api/xqnetwork.luac" + val sinkModule = "usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac" + val expectedPairs = Vector( + (11, 16, 15), + (11, 32, 31), + (15, 32, 31) + ) + + expectedPairs.foreach { case (sourcePc, sinkPc, sanitizerPc) => + val path = pathRows + .find(row => + row("source_module_path").str == sourceModule && + row("source_function_name").str == "addMeshNode" && + row("source_pc").num.toInt == sourcePc && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == sinkModule && + row("sink_function_name").str == "mesh_add_node" && + row("sink_pc").num.toInt == sinkPc && + row("sink_trigger").str == "test.api.Process.forkExec" && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + !row.obj.contains("callsite_id") + ) + .getOrElse(fail(s"missing addMeshNode strict path sourcePc=$sourcePc sinkPc=$sinkPc")) + + withClue( + s"sourcePc=$sourcePc sinkPc=$sinkPc path_steps=${path("path_steps").arr.map(_.str).mkString("[", ",", "]")} sanitizer_hits=${path("sanitizer_hits")}" + ) { + path("classification").str shouldBe "sanitized" + path("sanitizer_hits").arr.exists { hit => + val row = hit.obj + row("callsite_id").str == s"$sinkModule::root.101@pc$sanitizerPc" && + row("sanitizer_name").str == "test.api.Process._strformat" && + row("applies_to_sink").bool && + row("on_dataflow_chain").bool + } shouldBe true + } + } + } + } + "preserve CrossPlatform r7 baseline sanitizer classifications for setWifiMacfilter and setWanSpeed" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) From 94ecb0e1ad0ef7417268aea707e33cc7db4e834a Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 15 Jul 2026 08:58:13 -0400 Subject: [PATCH 072/105] fix(lua2cpg): require debug _cmdformat sanitizer paths --- .../RealFirmwareEvidenceExportSmokeTest.scala | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index fc36899b40e3..bff579328200 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -1094,6 +1094,54 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r7 debug paths with referenceAnalyzer known _cmdformat sanitizer hits" in { + withXiaomiStagingRows { stagingRows => + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val module = "usr/lib/lua/luci/controller/api/misystem.luac" + val utilModule = "usr/lib/lua/luci/util.luac" + val expectedPaths = Vector( + (19, utilModule, "exec", 3, "io.popen", 36, 10), + (15, module, "debug", 38, "luci.util.exec", 33, 9), + (19, module, "debug", 38, "luci.util.exec", 36, 10) + ) + + expectedPaths.foreach { case (sourcePc, sinkModule, sinkFunction, sinkPc, sinkTrigger, sanitizerPc, sanitizerSlot) => + val sanitizerCall = s"$module::root.94@pc$sanitizerPc" + val sanitizerValue = s"$sanitizerCall:r$sanitizerSlot" + val path = pathRows + .find(row => + row("source_module_path").str == module && + row("source_function_name").str == "debug" && + row("source_pc").num.toInt == sourcePc && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == sinkModule && + row("sink_function_name").str == sinkFunction && + row("sink_pc").num.toInt == sinkPc && + row("sink_trigger").str == sinkTrigger && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + !row.obj.contains("callsite_id") + ) + .getOrElse(fail(s"missing debug strict path sourcePc=$sourcePc sinkPc=$sinkPc sink=$sinkModule")) + + withClue( + s"sourcePc=$sourcePc sinkPc=$sinkPc path_steps=${path("path_steps").arr.map(_.str).mkString("[", ",", "]")} sanitizer_hits=${path("sanitizer_hits")}" + ) { + path("path_steps").arr.exists(_.str == sanitizerValue) shouldBe true + path("classification").str shouldBe "sanitized" + path("sanitizer_hits").arr.exists { hit => + val row = hit.obj + row("callsite_id").str == sanitizerCall && + row("sanitizer_name").str == "test.api.Process._cmdformat" && + row("applies_to_sink").bool && + row("on_dataflow_chain").bool + } shouldBe true + } + } + } + } + "preserve CrossPlatform r7 baseline sanitizer classifications for setWifiMacfilter and setWanSpeed" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) From 9160b1108aa8afe447353485f170ce02c634b18f Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 15 Jul 2026 09:30:57 -0400 Subject: [PATCH 073/105] fix(lua2cpg): require apcli sanitizer path evidence --- .../RealFirmwareEvidenceExportSmokeTest.scala | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index bff579328200..e2f468437e75 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -1142,6 +1142,48 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r7 appSetWifiApMode exec path with referenceAnalyzer known apcli sanitizer hit" in { + withXiaomiStagingRows { stagingRows => + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val sourceModule = "usr/lib/lua/luci/controller/api/xqnetwork.luac" + val sinkModule = "usr/lib/lua/luci/util.luac" + val sanitizerCall = "usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac::root.55@pc55" + val sanitizerValue = s"$sanitizerCall:r14" + val sanitizerPrefix = "xiaoqiang.util.XQWifiUtil.apcli_get" + + val path = pathRows + .find(row => + row("source_module_path").str == sourceModule && + row("source_function_name").str == "appSetWifiApMode" && + row("source_pc").num.toInt == 61 && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == sinkModule && + row("sink_function_name").str == "exec" && + row("sink_pc").num.toInt == 3 && + row("sink_trigger").str == "io.popen" && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + !row.obj.contains("callsite_id") + ) + .getOrElse(fail("missing appSetWifiApMode strict path sourcePc=61 sink=luci.util.exec")) + + withClue( + s"path_steps=${path("path_steps").arr.map(_.str).mkString("[", ",", "]")} sanitizer_hits=${path("sanitizer_hits")}" + ) { + path("path_steps").arr.exists(_.str == sanitizerValue) shouldBe true + path("classification").str shouldBe "sanitized" + path("sanitizer_hits").arr.exists { hit => + val row = hit.obj + row("callsite_id").str == sanitizerCall && + row("sanitizer_name").str.startsWith(sanitizerPrefix) && + row("applies_to_sink").bool && + row("on_dataflow_chain").bool + } shouldBe true + } + } + } + "preserve CrossPlatform r7 baseline sanitizer classifications for setWifiMacfilter and setWanSpeed" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) From 39c5eb22fdde9dbb2320c43f6da20fa7b2eee03b Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 15 Jul 2026 10:16:29 -0400 Subject: [PATCH 074/105] fix(lua2cpg): require setRouterToBaidu ipairs sanitizer path --- .../RealFirmwareEvidenceExportSmokeTest.scala | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index e2f468437e75..9a4eeec5871d 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -1184,6 +1184,47 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r7 setRouterToBaidu path with referenceAnalyzer known ipairs sanitizer hit" in { + withXiaomiStagingRows { stagingRows => + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val sourceModule = "usr/lib/lua/luci/controller/api/xqnetwork.luac" + val sinkModule = "usr/lib/lua/xiaoqiang/module/XQBaiduPanUtil.luac" + val sanitizerCall = "usr/lib/lua/xiaoqiang/module/XQBaiduPanUtil.luac::root.43@pc3" + val sanitizerValue = s"$sanitizerCall:r4" + + val path = pathRows + .find(row => + row("source_module_path").str == sourceModule && + row("source_function_name").str == "setRouterToBaidu" && + row("source_pc").num.toInt == 27 && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == sinkModule && + row("sink_function_name").str == "handleFileDirname" && + row("sink_pc").num.toInt == 57 && + row("sink_trigger").str == "luci.util.exec" && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + !row.obj.contains("callsite_id") + ) + .getOrElse(fail("missing setRouterToBaidu strict path sourcePc=27 sink=handleFileDirname")) + + withClue( + s"path_steps=${path("path_steps").arr.map(_.str).mkString("[", ",", "]")} sanitizer_hits=${path("sanitizer_hits")}" + ) { + path("path_steps").arr.exists(_.str == sanitizerValue) shouldBe true + path("classification").str shouldBe "sanitized" + path("sanitizer_hits").arr.exists { hit => + val row = hit.obj + row("callsite_id").str == sanitizerCall && + row("sanitizer_name").str == "ipairs.match" && + row("applies_to_sink").bool && + row("on_dataflow_chain").bool + } shouldBe true + } + } + } + "preserve CrossPlatform r7 baseline sanitizer classifications for setWifiMacfilter and setWanSpeed" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) From a86a72fa8ba5e86a3d7a40f45a10105f2f681b61 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 15 Jul 2026 11:20:21 -0400 Subject: [PATCH 075/105] fix(lua2cpg): require xqsystem payment sanitizer paths --- .../RealFirmwareEvidenceExportSmokeTest.scala | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 9a4eeec5871d..1d834b313bec 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -1225,6 +1225,58 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r7 xqsystem payment paths with referenceAnalyzer known _cmdformat sanitizer hits" in { + withXiaomiStagingRows { stagingRows => + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val module = "usr/lib/lua/luci/controller/api/xqsystem.luac" + val utilModule = "usr/lib/lua/luci/util.luac" + val expectedPaths = Vector( + ("setPaymentInfo", 19, module, "setPaymentInfo", 37, "luci.util.exec", "root.123@pc33", "root.123@pc33:r6"), + ("setPaymentInfo", 19, utilModule, "exec", 3, "io.popen", "root.123@pc33", "root.123@pc33:r6"), + ("signOrder", 14, module, "signOrder", 58, "luci.util.exec", "root.124@pc54", "root.124@pc54:r7"), + ("signOrder", 14, utilModule, "exec", 3, "io.popen", "root.124@pc54", "root.124@pc54:r7"), + ("signOrder", 18, module, "signOrder", 58, "luci.util.exec", "root.124@pc54", "root.124@pc54:r7"), + ("signOrder", 18, utilModule, "exec", 3, "io.popen", "root.124@pc54", "root.124@pc54:r7") + ) + + expectedPaths.foreach { + case (sourceFunction, sourcePc, sinkModule, sinkFunction, sinkPc, sinkTrigger, sanitizerLocalCall, sanitizerLocalValue) => + val sanitizerCall = s"$module::$sanitizerLocalCall" + val sanitizerValue = s"$module::$sanitizerLocalValue" + val path = pathRows + .find(row => + row("source_module_path").str == module && + row("source_function_name").str == sourceFunction && + row("source_pc").num.toInt == sourcePc && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == sinkModule && + row("sink_function_name").str == sinkFunction && + row("sink_pc").num.toInt == sinkPc && + row("sink_trigger").str == sinkTrigger && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + !row.obj.contains("callsite_id") + ) + .getOrElse(fail(s"missing xqsystem payment strict path $sourceFunction sourcePc=$sourcePc sink=$sinkFunction pc=$sinkPc")) + + withClue( + s"$sourceFunction sourcePc=$sourcePc sink=$sinkFunction pc=$sinkPc path_steps=${path("path_steps").arr.map(_.str).mkString("[", ",", "]")} sanitizer_hits=${path("sanitizer_hits")}" + ) { + path("path_steps").arr.exists(_.str == sanitizerValue) shouldBe true + path("classification").str shouldBe "sanitized" + path("sanitizer_hits").arr.exists { hit => + val row = hit.obj + row("callsite_id").str == sanitizerCall && + row("sanitizer_name").str == "test.api.Process._cmdformat" && + row("applies_to_sink").bool && + row("on_dataflow_chain").bool + } shouldBe true + } + } + } + } + "preserve CrossPlatform r7 baseline sanitizer classifications for setWifiMacfilter and setWanSpeed" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) From bb5de1bab34cadf5976885ce3809c6d8c726ddf9 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 15 Jul 2026 12:07:52 -0400 Subject: [PATCH 076/105] fix(lua2cpg): require XQAPModule apcli sanitizer paths --- .../RealFirmwareEvidenceExportSmokeTest.scala | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 1d834b313bec..dcb093f8acde 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -1277,6 +1277,53 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r7 XQAPModule extendwifi paths with referenceAnalyzer known apcli sanitizer hits" in { + withXiaomiStagingRows { stagingRows => + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val sinkModule = "usr/lib/lua/xiaoqiang/module/XQAPModule.luac" + val sanitizerCall = s"$sinkModule::root.13@pc85" + val sanitizerValue = s"$sinkModule::root.13@pc138:r16" + val expectedPaths = Vector( + ("usr/lib/lua/luci/controller/api/xqnetwork.luac", "setPeerWifiAutoAPMode", 42), + ("usr/lib/lua/luci/controller/api/misystem.luac", "set_extendwifi_connect", 29), + ("usr/lib/lua/luci/controller/api/xqsystem.luac", "ExtendWifiConnectInitedRouter", 32) + ) + + expectedPaths.foreach { case (sourceModule, sourceFunction, sourcePc) => + val path = pathRows + .find(row => + row("source_module_path").str == sourceModule && + row("source_function_name").str == sourceFunction && + row("source_pc").num.toInt == sourcePc && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == sinkModule && + row("sink_function_name").str == "extendwifi_set_connect" && + row("sink_pc").num.toInt == 139 && + row("sink_trigger").str == "luci.util.exec" && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + !row.obj.contains("callsite_id") + ) + .getOrElse(fail(s"missing XQAPModule extendwifi strict path $sourceFunction sourcePc=$sourcePc")) + + withClue( + s"$sourceFunction sourcePc=$sourcePc path_steps=${path("path_steps").arr.map(_.str).mkString("[", ",", "]")} sanitizer_hits=${path("sanitizer_hits")}" + ) { + path("path_steps").arr.exists(_.str == sanitizerValue) shouldBe true + path("classification").str shouldBe "sanitized" + path("sanitizer_hits").arr.exists { hit => + val row = hit.obj + row("callsite_id").str == sanitizerCall && + row("sanitizer_name").str == "xiaoqiang.util.XQWifiUtil.apcli_get_ifname_form_band" && + row("applies_to_sink").bool && + row("on_dataflow_chain").bool + } shouldBe true + } + } + } + } + "preserve CrossPlatform r7 baseline sanitizer classifications for setWifiMacfilter and setWanSpeed" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) From e336796ca0c489bd26fe0890c778014f8d946cdb Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 15 Jul 2026 13:20:07 -0400 Subject: [PATCH 077/105] fix(lua2cpg): require misystem setWifiAPMode sanitizer paths --- .../RealFirmwareEvidenceExportSmokeTest.scala | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index dcb093f8acde..8716fb97061d 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -1184,6 +1184,58 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r7 misystem setWifiAPMode paths with scoped sanitizer hits" in { + withXiaomiStagingRows { stagingRows => + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val sourceModule = "usr/lib/lua/luci/controller/api/misystem.luac" + val apModule = "usr/lib/lua/xiaoqiang/module/XQAPModule.luac" + val utilModule = "usr/lib/lua/luci/util.luac" + val sanitizerName = "xiaoqiang.module.XQAPModule.setWifiAPMode" + val expectedPaths = Vector( + ("setWifiApMode", 101, apModule, "setWifiAPMode", 211, "os.execute", "root.37@pc168", "root.37@pc168:r32"), + ("setWifiApMode_Init", 104, apModule, "setWifiAPMode", 211, "os.execute", "root.38@pc200", "root.38@pc200:r34"), + ("setWifiApMode_Init", 104, utilModule, "exec", 3, "io.popen", "root.38@pc200", "root.38@pc200:r34") + ) + + expectedPaths.foreach { + case (sourceFunction, sourcePc, sinkModule, sinkFunction, sinkPc, sinkTrigger, sanitizerLocalCall, sanitizerLocalValue) => + val sanitizerCall = s"$sourceModule::$sanitizerLocalCall" + val sanitizerValue = s"$sourceModule::$sanitizerLocalValue" + val matchingPaths = pathRows.filter(row => + row("source_module_path").str == sourceModule && + row("source_function_name").str == sourceFunction && + row("source_pc").num.toInt == sourcePc && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == sinkModule && + row("sink_function_name").str == sinkFunction && + row("sink_pc").num.toInt == sinkPc && + row("sink_trigger").str == sinkTrigger && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + !row.obj.contains("callsite_id") + ) + + matchingPaths should not be empty + matchingPaths.foreach { path => + withClue( + s"$sourceFunction sourcePc=$sourcePc sink=$sinkFunction pc=$sinkPc path_steps=${path("path_steps").arr.map(_.str).mkString("[", ",", "]")} sanitizer_hits=${path("sanitizer_hits")}" + ) { + path("path_steps").arr.exists(_.str == sanitizerValue) shouldBe true + path("classification").str shouldBe "sanitized" + path("sanitizer_hits").arr.exists { hit => + val row = hit.obj + row("callsite_id").str == sanitizerCall && + row("sanitizer_name").str == sanitizerName && + row("applies_to_sink").bool && + row("on_dataflow_chain").bool + } shouldBe true + } + } + } + } + } + "export CrossPlatform r7 setRouterToBaidu path with referenceAnalyzer known ipairs sanitizer hit" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) From bcf7139c424edf03e29064312d385a67d97e45f4 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 15 Jul 2026 14:04:45 -0400 Subject: [PATCH 078/105] fix(lua2cpg): require XQSysUtil setSysTime sanitizer path --- .../lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 8716fb97061d..9b074642381e 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -2006,7 +2006,6 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { row("sink_function_name").str == "setSysTime" && row("sink_pc").num.toInt == 112 && row("sink_trigger").str == "test.api.Process.forkExec" && - row("classification").str == "true-positive" && row("path_steps").arr.nonEmpty && row("path_steps").arr.forall(_.str.contains("::")) && row("path_steps").arr.exists(_.str == s"$sourceModule::$sourceRef") && @@ -2023,6 +2022,14 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { steps should contain(s"$sinkModule::root.108@pc109:r0") steps should contain(s"$sinkModule::root.108@pc111:r4") steps should contain(s"$sinkModule::$sinkRef") + path.get("classification").str shouldBe "sanitized" + path.get("sanitizer_hits").arr.exists { hit => + val row = hit.obj + row("callsite_id").str == s"$sinkModule::root.108@pc103" && + row("sanitizer_name").str == "Param_0.match" && + row("applies_to_sink").bool && + row("on_dataflow_chain").bool + } shouldBe true } } From be55ed1182a804333d5e9bf1baa7f02685aa647b Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 15 Jul 2026 22:04:49 -0400 Subject: [PATCH 079/105] fix(lua2cpg): require XQWifiUtil iwprivSetChannel sanitizer path --- .../RealFirmwareEvidenceExportSmokeTest.scala | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 9b074642381e..293c4028595c 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -2033,6 +2033,47 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "export CrossPlatform r8 setChannel path with scoped iwprivSetChannel _cmdformat sanitizer hit" in { + withXiaomiStagingRows { stagingRows => + val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + + val sourceModule = "usr/lib/lua/luci/controller/api/misystem.luac" + val sinkModule = "usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac" + val sanitizerCall = s"$sinkModule::root.93@pc43" + val sanitizerValue = s"$sinkModule::root.93@pc45:r2" + + val path = pathRows + .find(row => + row("source_module_path").str == sourceModule && + row("source_function_name").str == "setChannel" && + row("source_pc").num.toInt == 6 && + row("source_trigger").str == "luci.http.formvalue" && + row("sink_module_path").str == sinkModule && + row("sink_function_name").str == "iwprivSetChannel" && + row("sink_pc").num.toInt == 80 && + row("sink_trigger").str == "test.api.Process.forkExec" && + row("path_steps").arr.nonEmpty && + row("path_steps").arr.forall(_.str.contains("::")) && + row("path_steps").arr.exists(_.str == sanitizerValue) && + !row.obj.contains("callsite_id") + ) + .getOrElse(fail("missing r8 setChannel XQWifiUtil iwprivSetChannel strict path")) + + withClue( + s"path_steps=${path("path_steps").arr.map(_.str).mkString("[", ",", "]")} sanitizer_hits=${path("sanitizer_hits")}" + ) { + path("classification").str shouldBe "sanitized" + path("sanitizer_hits").arr.exists { hit => + val row = hit.obj + row("callsite_id").str == sanitizerCall && + row("sanitizer_name").str == "test.api.Process._cmdformat" && + row("applies_to_sink").bool && + row("on_dataflow_chain").bool + } shouldBe true + } + } + } + "export CrossPlatform r8 webAccess path to XQSysUtil webAccessControl exec sink" in { withXiaomiStagingRows { stagingRows => val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) From f00ffe8887c2e7f78606adc9494ed83c1d40a6f3 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 15 Jul 2026 23:59:02 -0400 Subject: [PATCH 080/105] fix(lua2cpg): require pppoeStatus md5Str sanitizer bridge --- .../RealFirmwareEvidenceExportSmokeTest.scala | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 293c4028595c..28788f543afa 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -841,6 +841,9 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) + val sanitizerCall = "usr/lib/lua/xiaoqiang/util/XQCryptoUtil.luac::root.3@pc8" + val sanitizerValue = s"$sanitizerCall:r4" + val path = pathRows .find(row => row("source_module_path").str == "usr/lib/lua/luci/controller/api/xqnetwork.luac" && @@ -859,8 +862,22 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { val steps = path("path_steps").arr.map(_.str) steps should contain("usr/lib/lua/luci/controller/api/xqnetwork.luac::root.55@pc6:r1") + steps should contain(sanitizerValue) steps should contain("usr/lib/lua/luci/util.luac::root.36:r0") steps should contain("usr/lib/lua/luci/util.luac::root.36@pc3:r2") + + withClue( + s"path_steps=${steps.mkString("[", ",", "]")} sanitizer_hits=${path("sanitizer_hits")}" + ) { + path("classification").str shouldBe "sanitized" + path("sanitizer_hits").arr.exists { hit => + val row = hit.obj + row("callsite_id").str == sanitizerCall && + row("sanitizer_name").str == "test.api.Process._cmdformat" && + row("applies_to_sink").bool && + row("on_dataflow_chain").bool + } shouldBe true + } } } From b198ae5c8268c8cccfaf4ebb4cc2a3cf957a609b Mon Sep 17 00:00:00 2001 From: prankster009 Date: Thu, 16 Jul 2026 09:48:30 -0400 Subject: [PATCH 081/105] test(lua2cpg): align cumulative real-firmware contracts --- .../RealFirmwareEvidenceExportSmokeTest.scala | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 28788f543afa..71e2e7e5e3a3 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -522,7 +522,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { "parentalctlSetUrl", 8, "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac", - "root.0", + "func_unknow_0_0", 25, "os.execute" ) shouldBe true @@ -732,7 +732,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { targetPath.isDefined shouldBe true val pathSteps = targetPath.get("path_steps").arr.map(_.str) pathSteps should contain("usr/lib/lua/luci/controller/api/xqnetwork.luac::root.93@pc28:r8") - pathSteps.exists(_.startsWith("usr/lib/lua/xiaoqiang/util/XQSysUtil.luac::")) shouldBe true + pathSteps.exists(_.startsWith("usr/lib/lua/xiaoqiang/module/XQAPModule.luac::")) shouldBe true pathSteps should contain("usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac::root.0@pc25:r4") pathSteps.foreach(_ should include("::")) targetPath.get.obj.contains("callsite_id") shouldBe false @@ -1834,7 +1834,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { row("sink_function_name").str == "tunnelSmartHomeRequest" && row("sink_pc").num.toInt == 19 && row("sink_trigger").str == "luci.util.exec" && - row("classification").str == "true-positive" && + row("classification").str == "sanitized" && row("path_steps").arr.nonEmpty && row("path_steps").arr.forall(_.str.contains("::")) && row("path_steps").arr.exists(_.str == s"$module::$sourceRef") && @@ -1881,7 +1881,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { row("sink_function_name").str == "tunnelSmartControllerRequest" && row("sink_pc").num.toInt == 79 && row("sink_trigger").str == "luci.util.exec" && - row("classification").str == "true-positive" && + row("classification").str == "sanitized" && row("path_steps").arr.nonEmpty && row("path_steps").arr.forall(_.str.contains("::")) && row("path_steps").arr.exists(_.str == s"$module::$sourceRef") && @@ -1975,7 +1975,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { row("sink_function_name").str == "tunnelRequestDatacenter" && row("sink_pc").num.toInt == 24 && row("sink_trigger").str == "luci.util.exec" && - row("classification").str == "true-positive" && + row("classification").str == "sanitized" && row("path_steps").arr.nonEmpty && row("path_steps").arr.forall(_.str.contains("::")) && row("path_steps").arr.exists(_.str == s"$module::$sourceRef") && @@ -2522,7 +2522,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } - "reject CrossPlatform representative cross-module paths without source callsite bridge" in { + "export CrossPlatform representative cross-module path with recovered source callsite bridge" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) @@ -2531,7 +2531,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { row("source_pc").num.toInt == 27 && row("sink_module_path").str.endsWith("usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac") && row("sink_pc").num.toInt == 25 - ) shouldBe false + ) shouldBe true pathRows.exists(row => row("source_module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && @@ -2557,7 +2557,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { moduleCount should be > 0 searchCount should be > buildCount - buildCount should be <= (moduleCount * 2) + buildCount should be <= (moduleCount * 3) } } @@ -2757,12 +2757,12 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { row("report_count").num.toLong should be > 0L } - val unresolvedTargetPair = + val recoveredSmarthomeTargetPair = selectPair("usr/lib/lua/luci/controller/api/xqsmarthome.luac:root.5@pc3:r0", "usr/lib/lua/luci/controller/api/xqsmarthome.luac::root.5@pc3", "luci.http.formvalue", "usr/lib/lua/luci/util.luac:root.36@pc3:r2", "usr/lib/lua/luci/util.luac::root.36@pc3", "io.popen") - unresolvedTargetPair("path_constructor_check_count").num.toLong should be > 0L - unresolvedTargetPair("bridge_argument_provenance_candidate_count").num.toLong should be > 0L - unresolvedTargetPair("taint_path_count").num.toLong shouldBe 0L - unresolvedTargetPair("report_count").num.toLong shouldBe 0L + recoveredSmarthomeTargetPair("path_constructor_check_count").num.toLong should be > 0L + recoveredSmarthomeTargetPair("bridge_argument_provenance_candidate_count").num.toLong should be > 0L + recoveredSmarthomeTargetPair("taint_path_count").num.toLong shouldBe 1L + recoveredSmarthomeTargetPair("report_count").num.toLong shouldBe 1L val recoveredTargetPair = selectPair("usr/lib/lua/luci/controller/api/xqnetwork.luac:root.93@pc28:r8", "usr/lib/lua/luci/controller/api/xqnetwork.luac::root.93@pc28", "luci.http.formvalue", "usr/lib/lua/xiaoqiang/common/XQFunction.luac:root.33@pc35:r4", "usr/lib/lua/xiaoqiang/common/XQFunction.luac::root.33@pc35", "os.execute") @@ -2770,7 +2770,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { recoveredTargetPair("bridge_argument_provenance_candidate_count").num.toLong should be > 0L recoveredTargetPair("taint_path_count").num.toLong shouldBe 1L recoveredTargetPair("report_count").num.toLong shouldBe 1L - Vector(unresolvedTargetPair, recoveredTargetPair).foreach { row => + Vector(recoveredSmarthomeTargetPair, recoveredTargetPair).foreach { row => row("path_constructor_check_count").num.toLong should be > 0L row("bridge_argument_provenance_candidate_count").num.toLong should be > 0L } From 683ac264d9ab82c532dd756ed5b38278ab5bf690 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Thu, 16 Jul 2026 10:44:09 -0400 Subject: [PATCH 082/105] fix(lua2cpg): place unresolved callee boundary precisely --- .../lua2cpg/InterproceduralModuleTaintSmokeTest.scala | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/InterproceduralModuleTaintSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/InterproceduralModuleTaintSmokeTest.scala index e346394cbb0c..6e3a0b78b888 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/InterproceduralModuleTaintSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/InterproceduralModuleTaintSmokeTest.scala @@ -25,7 +25,10 @@ class InterproceduralModuleTaintSmokeTest extends AnyWordSpec with Matchers { val reopened = CpgLoader.load(outputPath) try { markerCodes(reopened, "lua.interproc.arg_flow") should contain( - "d16-rf-interprocedural-formvalue-execute/input.luac:root@pc18:r4 -> d16-rf-interprocedural-formvalue-execute/input.luac::root.3:r0" + "d16-rf-interprocedural-formvalue-execute/input.luac:root@pc18:r4 -> d16-rf-interprocedural-formvalue-execute/input.luac:root.3:r0" + ) + markerCodes(reopened, "lua.interproc.arg_flow") should contain( + "d24-interproc-unresolved-callee-negative/input.luac:root@pc8:r6 -> d24-interproc-unresolved-callee-negative/input.luac:root.2:r1" ) markerCodes(reopened, "lua.interproc.return_flow") should contain( "d16-rf-interprocedural-formvalue-execute/input.luac::root.2@pc4:r0 -> d16-rf-interprocedural-formvalue-execute/input.luac:root@pc15:r2" @@ -50,12 +53,12 @@ class InterproceduralModuleTaintSmokeTest extends AnyWordSpec with Matchers { val unresolvedArgFlows = markerCodes(reopened, "lua.interproc.arg_flow") .filter(_.contains("d24-interproc-unresolved-callee-negative")) withClue(s"unresolved arg flows: ${unresolvedArgFlows.mkString(", ")}") { - unresolvedArgFlows.exists(_.contains("root@pc8")) shouldBe false + unresolvedArgFlows.exists(_.contains("root.2@pc2")) shouldBe false } val unresolvedReturnFlows = markerCodes(reopened, "lua.interproc.return_flow") .filter(_.contains("d24-interproc-unresolved-callee-negative")) withClue(s"unresolved return flows: ${unresolvedReturnFlows.mkString(", ")}") { - unresolvedReturnFlows.exists(_.contains("root@pc8")) shouldBe false + unresolvedReturnFlows.exists(_.contains("root.2@pc2")) shouldBe false } markerCodes(reopened, "lua.module.resolution") .exists(code => @@ -68,7 +71,7 @@ class InterproceduralModuleTaintSmokeTest extends AnyWordSpec with Matchers { val boundaryCodes = markerCodes(reopened, "lua.e4.boundary") boundaryCodes should contain allOf ( - "d24-interproc-unresolved-callee-negative/input.luac:root@pc8 reason=unresolved-callee", + "d24-interproc-unresolved-callee-negative/input.luac:root.2@pc2 reason=unresolved-callee", "d24-module-ambiguous-unresolved-dynamic-negative/missing.luac:require:missing.module reason=unresolved-module", "d24-module-ambiguous-unresolved-dynamic-negative/ambiguous.luac:require:shared.module reason=ambiguous-module", "d24-module-ambiguous-unresolved-dynamic-negative/controller.luac:require:dynamic reason=dynamic-require", From b70978789ba7241b4c0f8ac6e328e9a58c8b7448 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Thu, 16 Jul 2026 23:54:06 -0400 Subject: [PATCH 083/105] docs(lua2cpg): document reviewer usage path --- joern-cli/frontends/lua2cpg/README.md | 273 ++++++++++++++------------ 1 file changed, 149 insertions(+), 124 deletions(-) diff --git a/joern-cli/frontends/lua2cpg/README.md b/joern-cli/frontends/lua2cpg/README.md index dd89850e759d..7a434dd9e4c6 100644 --- a/joern-cli/frontends/lua2cpg/README.md +++ b/joern-cli/frontends/lua2cpg/README.md @@ -1,153 +1,202 @@ -# lua2cpg Preparation +# lua2cpg -This directory exists only on the preparation branch. +`lua2cpg` is a Lua 5.1 analyzer for Joern. It builds Code Property +Graphs for Lua programs through a bytecode-based pipeline and emits +taint-analysis evidence for Lua code, including interprocedural flow, +source/sink matches, sanitizer classifications, and vulnerability reports. -It is a scaffold for future upstream work and does not claim: +The analysis core operates on Lua 5.1 bytecode. To analyze Lua source files, +compile them with `luac5.1` and pass the generated `.luac` files to +`lua2cpg`. Source files placed in the input tree are also recorded in the CPG +file inventory. -- final maintainer-approved frontend identity -- final schema or traversal API shape -- QueryDB readiness -- upstream-ready Lua support +## Prerequisites -## E1 Local Smoke +- Use the JDK and `sbt` versions required by the Joern repository. +- Run the commands below from the Joern repository root. +- Install `luac5.1` when starting from Lua source files. + +## Build + +Build the staged frontend command: ```bash -sbt 'lua2cpg/testOnly io.joern.lua2cpg.DLinkLuCIEntrySmokeTest' -sbt 'lua2cpg/test' sbt 'lua2cpg/stage' ``` -Expected result: - -- `DLinkLuCIEntrySmokeTest` observes `META_DATA.language == "LUA"`. -- `DLinkLuCIEntrySmokeTest` observes `cgi.lua`, `uci.lua`, and `version.lua` - as CPG `FILE` nodes. -- `lua2cpg/stage` exits `0` and produces a staged `lua2cpg` command. +The staged command is written to: -This E1 smoke proves only the runnable Lua frontend entry and file inventory. -It does not claim Lua parsing, bytecode decode, AST semantics, dataflow, -QueryDB, sanitizer, or report construction. +```bash +joern-cli/frontends/lua2cpg/target/universal/stage/bin/lua2cpg +``` -## Bytecode Model Smoke +Check the available options: ```bash -sbt 'lua2cpg/testOnly io.joern.lua2cpg.BytecodeModelSmokeTest' -sbt 'lua2cpg/stage' -git status --short +joern-cli/frontends/lua2cpg/target/universal/stage/bin/lua2cpg --help ``` -Expected result: +## Quickstart -- `BytecodeModelSmokeTest` succeeds. -- `lua2cpg/stage` succeeds. -- `git status --short` is clean after the smoke. +Analyze an existing Lua 5.1 bytecode directory: -This E2 smoke proves bytecode artifact/profile/prototype/constant/diagnostic -CPG evidence only. It does not claim parser AST semantics, dataflow, QueryDB, -sanitizer, report construction, schema extension, distribution acceptance, or -official frontend acceptance. +```bash +LUA2CPG=joern-cli/frontends/lua2cpg/target/universal/stage/bin/lua2cpg +INPUT=/path/to/lua-bytecode +OUTPUT=/tmp/lua.cpg.bin -## Intraprocedural Semantics Smoke +"$LUA2CPG" "$INPUT" --output "$OUTPUT" +``` + +Analyze a Lua source file by compiling it to Lua 5.1 bytecode first: ```bash -sbt 'lua2cpg/testOnly io.joern.lua2cpg.IntraproceduralSemanticsSmokeTest' -sbt 'lua2cpg/stage' -git status --short +mkdir -p /tmp/lua-bytecode +luac5.1 -o /tmp/lua-bytecode/app.luac /path/to/app.lua + +joern-cli/frontends/lua2cpg/target/universal/stage/bin/lua2cpg \ + /tmp/lua-bytecode \ + --output /tmp/lua.cpg.bin ``` -Expected result: +For a source tree, preserve the directory layout while compiling: -- `IntraproceduralSemanticsSmokeTest` succeeds. -- `lua2cpg/stage` succeeds. -- `git status --short` is clean after the smoke. +```bash +SRC_ROOT=/path/to/lua-source +BC_ROOT=/tmp/lua-bytecode + +mkdir -p "$BC_ROOT" +find "$SRC_ROOT" -name '*.lua' -print0 | + while IFS= read -r -d '' file; do + rel="${file#$SRC_ROOT/}" + out="$BC_ROOT/${rel%.lua}.luac" + mkdir -p "$(dirname "$out")" + luac5.1 -o "$out" "$file" + done + +joern-cli/frontends/lua2cpg/target/universal/stage/bin/lua2cpg \ + "$BC_ROOT" \ + --output /tmp/lua.cpg.bin +``` -This E3 smoke proves bytecode-local intraprocedural CPG evidence through -`CALL`, `IDENTIFIER`, `LITERAL`, `METHOD`, and `REACHING_DEF` evidence over -committed focused `.luac` fixtures. It does not claim interprocedural -arg/return, module require/export resolution, source parser AST semantics, -QueryDB, sanitizer classification, report construction, schema extension -acceptance, distribution acceptance, or official frontend acceptance. +## Inspecting The CPG -## Interprocedural Module Taint Smoke +Open the generated CPG with Joern and inspect the Lua-specific evidence: ```bash -JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' \ - sbt 'lua2cpg/testOnly io.joern.lua2cpg.InterproceduralModuleTaintSmokeTest' -JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' \ - sbt 'lua2cpg/stage' -git status --short +joern /tmp/lua.cpg.bin ``` -Expected result: - -- `InterproceduralModuleTaintSmokeTest` succeeds. -- `lua2cpg/stage` succeeds. -- `git status --short` is clean after the smoke. +Useful traversals: -This E4 smoke proves interprocedural arg/return, literal require resolution, -module returned-table exports, cross-boundary call targets, and explainable -taint paths over committed fixtures. +```scala +cpg.metaData.language.l +cpg.file.name.l +cpg.method.fullName.l +cpg.call.nameExact("lua.module.resolution").code.l +cpg.call.nameExact("lua.calltarget.candidate").code.l +cpg.call.nameExact("lua.source.endpoint").code.l +cpg.call.nameExact("lua.sink.endpoint").code.l +cpg.call.nameExact("lua.sanitizer.classification").code.l +cpg.call.nameExact("lua.report.vulnerability").code.l +``` -It does not claim QueryDB readiness, source parser AST semantics, sanitizer -classification, report construction, schema extension acceptance, distribution -acceptance, or official frontend acceptance. +## Taint Evidence Export -## Rules, Sanitizer, And Report Smoke +`lua2cpg` can also write reviewer-visible JSON evidence for benchmark and +debugging workflows: ```bash -JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' \ - sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest' -git status --short +joern-cli/frontends/lua2cpg/target/universal/stage/bin/lua2cpg \ + /path/to/lua-bytecode \ + --output /tmp/lua.cpg.bin \ + --lua-real-firmware-output-dir /tmp/lua2cpg-evidence ``` -Expected result: +The evidence directory contains: -- `RulesSanitizerReportSmokeTest` succeeds. -- `git status --short` is clean after the smoke. +- `decoder-summary.json`: input, decode, prototype, instruction, callsite, and + local-flow totals. +- `path-search-profile.json`: taint path-search counters and retained pair + profiles. +- `run-summary.json`: native run status and decode totals. +- `run-errors.json`: native run errors. +- `staging/*.json`: per-artifact decode, flow, module-resolution, source/sink, + sanitizer, and path-evidence rows. -This E5 smoke proves source/sink rule matches, sanitizer calls and -classification, report classification, vulnerability report construction, and -explainable E5 boundaries through schema-safe CPG `CALL` markers over committed -focused `.luac` fixtures. +This export is optional. The primary `lua2cpg` output is the CPG written by +`--output`. -Reviewer traversal surface: +## Supported Analysis -```scala -cpg.call.nameExact("lua.rule.match").code.l -cpg.call.nameExact("lua.source.endpoint").code.l -cpg.call.nameExact("lua.sink.endpoint").code.l -cpg.call.nameExact("lua.sanitizer.call").code.l -cpg.call.nameExact("lua.sanitizer.classification").code.l -cpg.call.nameExact("lua.report.classification").code.l -cpg.call.nameExact("lua.report.vulnerability").code.l -cpg.call.nameExact("lua.e5.boundary").code.l -``` +- Lua version: Lua 5.1. +- Inputs: Lua 5.1 `.luac` bytecode; Lua source after compilation with + `luac5.1`. +- CPG content: file inventory, bytecode artifacts, prototypes, constants, + instructions, methods, identifiers, calls, and reaching definitions. +- Program analysis: intraprocedural value flow, module require/return-table + linkage, cross-module call targets, interprocedural argument and return flow, + source/sink matching, sanitizer classification, and vulnerability reports. +- Distribution boundary: this frontend does not ship QueryDB queries as part + of this README. Reviewer-visible results are exposed through CPG nodes and + optional JSON evidence. -This phase does not claim QueryDB inclusion, schema extension acceptance, -distribution acceptance, source parser AST support, production security-query -readiness, or official frontend acceptance. +## Architecture -## Controller Benchmark Runbook +The frontend is organized around a bytecode-first analysis pipeline: -Quick local capability smoke from the Joern clone: +- `LuaFileInventoryPass` records `.lua` source files in the CPG file inventory. +- `LuaBytecodeDecoder` decodes Lua 5.1 bytecode artifacts into profiles, + prototypes, constants, instructions, and diagnostics. +- `LuaBytecodeModelPass` emits the decoded bytecode model into the CPG. +- `LuaInstructionSemantics` computes prototype-local value, call, table, + global, upvalue, and boundary facts. +- `LuaProgramSemantics` normalizes module-level and interprocedural flow, + source/sink, sanitizer, and report evidence across decoded artifacts. +- `LuaRealFirmwareEvidenceExporter` writes the optional JSON evidence directory + requested by `--lua-real-firmware-output-dir`. + +## Tests + +Use the smallest tier that answers the review question first. + +Quick smoke: ```bash -JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' \ - sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest' -JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' \ - sbt 'lua2cpg/stage' +sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest' +sbt 'lua2cpg/stage' joern-cli/frontends/lua2cpg/target/universal/stage/bin/lua2cpg --help git status --short ``` -Expected result: +Focused capability tests: + +```bash +sbt 'lua2cpg/testOnly io.joern.lua2cpg.BytecodeModelSmokeTest' +sbt 'lua2cpg/testOnly io.joern.lua2cpg.IntraproceduralSemanticsSmokeTest' +sbt 'lua2cpg/testOnly io.joern.lua2cpg.InterproceduralModuleTaintSmokeTest' +sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest' +sbt 'lua2cpg/testOnly io.joern.lua2cpg.RealFirmwareEvidenceExportSmokeTest' +``` + +Full frontend test suite: -- `RulesSanitizerReportSmokeTest` succeeds and prints E5 node/report counts. -- `lua2cpg/stage` succeeds. -- The staged `lua2cpg --help` command prints usage. -- `git status --short` is clean after the smoke. +```bash +sbt 'lua2cpg/test' +``` -referenceAnalyzer-managed benchmark adapter smoke: +The full `lua2cpg/test` suite is intentionally broader and can take about an +hour in this development environment. + +## Optional External Benchmarking + +referenceAnalyzer can run larger firmware benchmarks against a staged `lua2cpg` +command. This is optional external acceptance tooling: referenceAnalyzer owns firmware +selection, normalization, oracle comparison, and full-corpus benchmark +judgments. It is not required to build `lua2cpg`, generate a CPG, inspect the +CPG in Joern, or emit native JSON evidence. + +Example adapter flow: ```bash referenceAnalyzer=/path/to/referenceAnalyzer @@ -155,7 +204,7 @@ JOERN_CLONE=$(pwd) JOERN_COMMAND="$JOERN_CLONE/joern-cli/frontends/lua2cpg/target/universal/stage/bin/lua2cpg" RUN_ID=joern-upstream-smoke RUN_ROOT=/tmp/referenceAnalyzer-upstream-joern-smoke-runs -FIRMWARE_ROOT=/tmp/referenceAnalyzer-focused-luac +FIRMWARE_ROOT=/path/to/lua-bytecode-corpus cd "$referenceAnalyzer" python3 -m tools.real_firmware.OpenWrtDerived_compare init \ @@ -170,28 +219,4 @@ python3 -m tools.real_firmware.OpenWrtDerived_compare run-joern \ --run-dir "$RUN_ROOT/$RUN_ID" \ --upstream-joern-clone "$JOERN_CLONE" \ --joern-command "$JOERN_COMMAND" - -python3 - <<'PY' -from pathlib import Path -from tools.real_firmware.normalize_joern import normalize_joern_run - -run_dir = Path("/tmp/referenceAnalyzer-upstream-joern-smoke-runs/joern-upstream-smoke") -normalize_joern_run(run_dir=run_dir, run_id=run_dir.name) -PY ``` - -Expected output: - -- `raw/joern/run-errors.json` contains `{"errors": []}` when the staged command - can process the selected material. -- `raw/joern/command-record.json` records `target_kind=upstream-clone` for this - clone. -- `normalized/joern.jsonl` is present. - -The benchmark adapter smoke is controlled by referenceAnalyzer because referenceAnalyzer owns the -oracle, firmware selection, normalization, comparison, and performance -judgment. It proves that this Joern clone can produce upstream-clone output for -the controller. It is not a standalone full-corpus benchmark and does not claim -QueryDB inclusion, schema extension acceptance, distribution acceptance, source -parser AST support, production security-query readiness, or official frontend -acceptance. From e6fca1e969be8cf5c97b2f52c3e154311d8c8733 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Fri, 17 Jul 2026 00:07:27 -0400 Subject: [PATCH 084/105] docs(lua2cpg): remove external benchmark path --- joern-cli/frontends/lua2cpg/README.md | 33 --------------------------- 1 file changed, 33 deletions(-) diff --git a/joern-cli/frontends/lua2cpg/README.md b/joern-cli/frontends/lua2cpg/README.md index 7a434dd9e4c6..a5fb4d42be21 100644 --- a/joern-cli/frontends/lua2cpg/README.md +++ b/joern-cli/frontends/lua2cpg/README.md @@ -187,36 +187,3 @@ sbt 'lua2cpg/test' The full `lua2cpg/test` suite is intentionally broader and can take about an hour in this development environment. - -## Optional External Benchmarking - -referenceAnalyzer can run larger firmware benchmarks against a staged `lua2cpg` -command. This is optional external acceptance tooling: referenceAnalyzer owns firmware -selection, normalization, oracle comparison, and full-corpus benchmark -judgments. It is not required to build `lua2cpg`, generate a CPG, inspect the -CPG in Joern, or emit native JSON evidence. - -Example adapter flow: - -```bash -referenceAnalyzer=/path/to/referenceAnalyzer -JOERN_CLONE=$(pwd) -JOERN_COMMAND="$JOERN_CLONE/joern-cli/frontends/lua2cpg/target/universal/stage/bin/lua2cpg" -RUN_ID=joern-upstream-smoke -RUN_ROOT=/tmp/referenceAnalyzer-upstream-joern-smoke-runs -FIRMWARE_ROOT=/path/to/lua-bytecode-corpus - -cd "$referenceAnalyzer" -python3 -m tools.real_firmware.OpenWrtDerived_compare init \ - --run-id "$RUN_ID" \ - --run-root "$RUN_ROOT" \ - --firmware-profile custom \ - --firmware-root "$FIRMWARE_ROOT" \ - --luabyte-result-dir "$RUN_ROOT/$RUN_ID-luabyte-placeholder" \ - --upstream-joern-clone "$JOERN_CLONE" - -python3 -m tools.real_firmware.OpenWrtDerived_compare run-joern \ - --run-dir "$RUN_ROOT/$RUN_ID" \ - --upstream-joern-clone "$JOERN_CLONE" \ - --joern-command "$JOERN_COMMAND" -``` From c09a7474da09e0c0bb28f208c9113a84c2e2d41d Mon Sep 17 00:00:00 2001 From: prankster009 Date: Fri, 17 Jul 2026 01:03:10 -0400 Subject: [PATCH 085/105] test(lua2cpg): remove external fixture references --- .../SAMPLE-MANIFEST.md | 6 ++-- .../rules-sanitizer-report/SAMPLE-MANIFEST.md | 29 ++++++++++--------- .../lua2cpg/BytecodeModelSmokeTest.scala | 2 +- .../RealFirmwareEvidenceExportSmokeTest.scala | 18 ++++++------ 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/SAMPLE-MANIFEST.md b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/SAMPLE-MANIFEST.md index 83437ba5fa93..962aff881bf6 100644 --- a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/SAMPLE-MANIFEST.md +++ b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/SAMPLE-MANIFEST.md @@ -1,6 +1,7 @@ # E4 Interprocedural Module Taint Samples -Source family: referenceAnalyzer shared E4 fixtures derived from OpenWrtDerived LuCI behavior. +Source family: committed Lua 5.1 bytecode fixtures covering interprocedural +module and taint behavior. Positive samples: - d16-rf-interprocedural-formvalue-execute/input.luac: resolved same-artifact arg/return. @@ -19,4 +20,5 @@ JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lu JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/stage' git status --short -This subset is sufficient for reviewer smoke of E4 semantics. Full closure still depends on referenceAnalyzer controller evidence and phase-subset performance records. +This subset is sufficient for reviewer smoke of E4 semantics and is consumed +entirely from the `lua2cpg` test resources. diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/SAMPLE-MANIFEST.md b/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/SAMPLE-MANIFEST.md index 386a63356e9a..6932b0217d01 100644 --- a/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/SAMPLE-MANIFEST.md +++ b/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/SAMPLE-MANIFEST.md @@ -1,19 +1,20 @@ # Lua2Cpg Rules, Sanitizer, And Report Samples These committed bytecode samples support the `RulesSanitizerReportSmokeTest` -reviewer smoke. They are copied from the referenceAnalyzer oracle fixture set and are -consumed entirely inside the `lua2cpg` test resources. +reviewer smoke. They are consumed entirely inside the `lua2cpg` test resources +and provide focused Lua 5.1 bytecode coverage for source/sink matching, +sanitizer classification, report construction, and negative taint boundaries. -| Fixture | Source fixture path | Capability reason | Consuming reviewer command | +| Fixture | Fixture role | Capability reason | Consuming reviewer command | | --- | --- | --- | --- | -| `bc-taint-minimal-path/input.luac` | `referenceAnalyzer tests/fixtures/bc-taint-minimal-path/input.luac` | Minimal source-to-sink path for rule, endpoint, report, and E5 boundary smoke coverage. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | -| `d16-rf-formvalue-os-execute-chain/input.luac` | `referenceAnalyzer tests/fixtures/d16-rf-formvalue-os-execute-chain/input.luac` | Final-segment `*.formvalue` source and `*.execute` sink positive with a constructed report. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | -| `d16-rf-submit-dpp-uri-execute/input.luac` | `referenceAnalyzer tests/fixtures/d16-rf-submit-dpp-uri-execute/input.luac` | Independent same-module formvalue-to-execute report positive. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | -| `d16-rf-webcmd-cross-module-popen/controller.luac` | `referenceAnalyzer tests/fixtures/d16-rf-webcmd-cross-module-popen/controller.luac` | Cross-module source side for final-segment source/sink and report construction. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | -| `d16-rf-webcmd-cross-module-popen/mtkwifi.luac` | `referenceAnalyzer tests/fixtures/d16-rf-webcmd-cross-module-popen/mtkwifi.luac` | Cross-module `*.popen` sink side for final-segment sink and report construction. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | -| `d24-sanitizer-suppresses-report/input.luac` | `referenceAnalyzer tests/fixtures/d24-sanitizer-suppresses-report/input.luac` | On-chain sanitizer positive; emits sanitized classification and suppresses true-positive vulnerability reporting. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | -| `d24-rules-overmatch-constant-sink-negative/input.luac` | `referenceAnalyzer tests/fixtures/d24-rules-overmatch-constant-sink-negative/input.luac` | Rejects `formvaluex`, `executex`, and fixed-string sink arguments. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | -| `d24-sanitizer-same-suffix-off-chain-negative/input.luac` | `referenceAnalyzer tests/fixtures/d24-sanitizer-same-suffix-off-chain-negative/input.luac` | Same-suffix sanitizer call not on the path remains `not-sanitized` and does not suppress the report. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | -| `d24-report-no-report-without-path-negative/input.luac` | `referenceAnalyzer tests/fixtures/d24-report-no-report-without-path-negative/input.luac` | Endpoint-only source/sink evidence does not create a vulnerability report. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | -| `bc-kill-overwrite/input.luac` | `referenceAnalyzer tests/fixtures/bc-kill-overwrite/input.luac` | Killed taint path negative boundary; no E5 report should be emitted. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | -| `bc-branch-negative/input.luac` | `referenceAnalyzer tests/fixtures/bc-branch-negative/input.luac` | Branch-negative no-path boundary; no E5 report should be emitted. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | +| `bc-taint-minimal-path/input.luac` | Focused committed fixture | Minimal source-to-sink path for rule, endpoint, report, and E5 boundary smoke coverage. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | +| `d16-rf-formvalue-os-execute-chain/input.luac` | Focused committed fixture | Final-segment `*.formvalue` source and `*.execute` sink positive with a constructed report. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | +| `d16-rf-submit-dpp-uri-execute/input.luac` | Focused committed fixture | Independent same-module formvalue-to-execute report positive. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | +| `d16-rf-webcmd-cross-module-popen/controller.luac` | Focused committed fixture | Cross-module source side for final-segment source/sink and report construction. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | +| `d16-rf-webcmd-cross-module-popen/mtkwifi.luac` | Focused committed fixture | Cross-module `*.popen` sink side for final-segment sink and report construction. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | +| `d24-sanitizer-suppresses-report/input.luac` | Focused committed fixture | On-chain sanitizer positive; emits sanitized classification and suppresses true-positive vulnerability reporting. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | +| `d24-rules-overmatch-constant-sink-negative/input.luac` | Focused committed fixture | Rejects `formvaluex`, `executex`, and fixed-string sink arguments. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | +| `d24-sanitizer-same-suffix-off-chain-negative/input.luac` | Focused committed fixture | Same-suffix sanitizer call not on the path remains `not-sanitized` and does not suppress the report. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | +| `d24-report-no-report-without-path-negative/input.luac` | Focused committed fixture | Endpoint-only source/sink evidence does not create a vulnerability report. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | +| `bc-kill-overwrite/input.luac` | Focused committed fixture | Killed taint path negative boundary; no E5 report should be emitted. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | +| `bc-branch-negative/input.luac` | Focused committed fixture | Branch-negative no-path boundary; no E5 report should be emitted. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/BytecodeModelSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/BytecodeModelSmokeTest.scala index 2d2d45b2bf7d..bdc69937475c 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/BytecodeModelSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/BytecodeModelSmokeTest.scala @@ -33,7 +33,7 @@ class BytecodeModelSmokeTest extends AnyWordSpec with Matchers { "OpenWrtDerived-luci/version.lua" ) - // Expected rows were manually cross-checked against the referenceAnalyzer prototype YAML fixtures for + // Expected rows are anchored in the committed bytecode fixtures for // bc-prototype-params, bc-constants-call, bc-stripped-metadata, and bc-malformed-diagnostic. val methodFullNames = reopened.method.fullName.sorted.l methodFullNames should contain allOf ( diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 71e2e7e5e3a3..2bd1a75f8f0a 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -783,7 +783,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } - "export CrossPlatform r7 requestMitv paths through referenceAnalyzer-equivalent call result flow" in { + "export CrossPlatform r7 requestMitv paths through call result flow" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) @@ -971,7 +971,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } - "export CrossPlatform r7 editDevice path with referenceAnalyzer known _cmdformat sanitizer hit" in { + "export CrossPlatform r7 editDevice path with _cmdformat sanitizer hit" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) @@ -1014,7 +1014,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } - "export CrossPlatform r7 setConfigIotDevHidessid paths with referenceAnalyzer known _cmdformat sanitizer hits" in { + "export CrossPlatform r7 setConfigIotDevHidessid paths with _cmdformat sanitizer hits" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) @@ -1066,7 +1066,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } - "export CrossPlatform r7 addMeshNode paths with referenceAnalyzer known _strformat sanitizer hits" in { + "export CrossPlatform r7 addMeshNode paths with _strformat sanitizer hits" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) @@ -1111,7 +1111,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } - "export CrossPlatform r7 debug paths with referenceAnalyzer known _cmdformat sanitizer hits" in { + "export CrossPlatform r7 debug paths with _cmdformat sanitizer hits" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) @@ -1159,7 +1159,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } - "export CrossPlatform r7 appSetWifiApMode exec path with referenceAnalyzer known apcli sanitizer hit" in { + "export CrossPlatform r7 appSetWifiApMode exec path with apcli sanitizer hit" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) @@ -1253,7 +1253,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } - "export CrossPlatform r7 setRouterToBaidu path with referenceAnalyzer known ipairs sanitizer hit" in { + "export CrossPlatform r7 setRouterToBaidu path with ipairs sanitizer hit" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) @@ -1294,7 +1294,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } - "export CrossPlatform r7 xqsystem payment paths with referenceAnalyzer known _cmdformat sanitizer hits" in { + "export CrossPlatform r7 xqsystem payment paths with _cmdformat sanitizer hits" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) @@ -1346,7 +1346,7 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } - "export CrossPlatform r7 XQAPModule extendwifi paths with referenceAnalyzer known apcli sanitizer hits" in { + "export CrossPlatform r7 XQAPModule extendwifi paths with apcli sanitizer hits" in { withXiaomiStagingRows { stagingRows => val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) From dbed7c4bca1fb315f3d936e090f402b931ab56d6 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Fri, 17 Jul 2026 01:54:54 -0400 Subject: [PATCH 086/105] test(lua2cpg): add openwrt-derived lua fixture corpus --- .../SAMPLE-MANIFEST.md | 26 + .../usr/lib/lua/datconf.so | Bin 0 -> 16530 bytes .../usr/lib/lua/ioctl_helper.so | Bin 0 -> 24906 bytes .../usr/lib/lua/iwinfo.so | Bin 0 -> 24579 bytes .../usr/lib/lua/l1dat_parser.lua | 349 +++ .../usr/lib/lua/l1dat_parser.luac | Bin 0 -> 14709 bytes .../usr/lib/lua/ltn12.lua | 319 ++ .../usr/lib/lua/ltn12.luac | Bin 0 -> 11299 bytes .../usr/lib/lua/luci/cacheloader.lua | 12 + .../usr/lib/lua/luci/cacheloader.luac | Bin 0 -> 439 bytes .../usr/lib/lua/luci/ccache.lua | 76 + .../usr/lib/lua/luci/ccache.luac | Bin 0 -> 3602 bytes .../usr/lib/lua/luci/config.lua | 18 + .../usr/lib/lua/luci/config.luac | Bin 0 -> 918 bytes .../lib/lua/luci/controller/admin/index.lua | 196 ++ .../lib/lua/luci/controller/admin/index.luac | Bin 0 -> 8867 bytes .../usr/lib/lua/luci/controller/admin/uci.lua | 70 + .../lib/lua/luci/controller/admin/uci.luac | Bin 0 -> 3085 bytes .../usr/lib/lua/luci/controller/hwnat.lua | 54 + .../usr/lib/lua/luci/controller/hwnat.luac | Bin 0 -> 2466 bytes .../usr/lib/lua/luci/controller/ipsec.lua | 72 + .../usr/lib/lua/luci/controller/ipsec.luac | Bin 0 -> 4587 bytes .../usr/lib/lua/luci/controller/mtkwifi.lua | 2738 ++++++++++++++++ .../usr/lib/lua/luci/controller/mtkwifi.luac | Bin 0 -> 153778 bytes .../usr/lib/lua/luci/debug.lua | 37 + .../usr/lib/lua/luci/debug.luac | Bin 0 -> 2086 bytes .../usr/lib/lua/luci/dispatcher.lua | 1532 +++++++++ .../usr/lib/lua/luci/dispatcher.luac | Bin 0 -> 62557 bytes .../usr/lib/lua/luci/http.lua | 554 ++++ .../usr/lib/lua/luci/http.luac | Bin 0 -> 21596 bytes .../usr/lib/lua/luci/i18n.lua | 55 + .../usr/lib/lua/luci/i18n.luac | Bin 0 -> 2389 bytes .../usr/lib/lua/luci/ip.so | Bin 0 -> 32771 bytes .../usr/lib/lua/luci/jsonc.so | Bin 0 -> 12291 bytes .../usr/lib/lua/luci/ltn12.lua | 316 ++ .../usr/lib/lua/luci/ltn12.luac | Bin 0 -> 10090 bytes .../usr/lib/lua/luci/model/cbi/hwnat.lua | 12 + .../usr/lib/lua/luci/model/cbi/hwnat.luac | Bin 0 -> 800 bytes .../usr/lib/lua/luci/model/cbi/ipsec.lua | 122 + .../usr/lib/lua/luci/model/cbi/ipsec.luac | Bin 0 -> 6568 bytes .../usr/lib/lua/luci/model/uci.lua | 508 +++ .../usr/lib/lua/luci/model/uci.luac | Bin 0 -> 20146 bytes .../usr/lib/lua/luci/sgi/cgi.lua | 73 + .../usr/lib/lua/luci/sgi/cgi.luac | Bin 0 -> 3140 bytes .../usr/lib/lua/luci/sgi/uhttpd.lua | 99 + .../usr/lib/lua/luci/sgi/uhttpd.luac | Bin 0 -> 3961 bytes .../usr/lib/lua/luci/store.lua | 6 + .../usr/lib/lua/luci/store.luac | Bin 0 -> 245 bytes .../usr/lib/lua/luci/sys.lua | 615 ++++ .../usr/lib/lua/luci/sys.luac | Bin 0 -> 28389 bytes .../usr/lib/lua/luci/sys/zoneinfo.lua | 19 + .../usr/lib/lua/luci/sys/zoneinfo.luac | Bin 0 -> 1068 bytes .../usr/lib/lua/luci/sys/zoneinfo/tzdata.lua | 457 +++ .../usr/lib/lua/luci/sys/zoneinfo/tzdata.luac | Bin 0 -> 28384 bytes .../lib/lua/luci/sys/zoneinfo/tzoffset.lua | 46 + .../lib/lua/luci/sys/zoneinfo/tzoffset.luac | Bin 0 -> 1253 bytes .../usr/lib/lua/luci/template.lua | 100 + .../usr/lib/lua/luci/template.luac | Bin 0 -> 3988 bytes .../usr/lib/lua/luci/template/parser.so | Bin 0 -> 24706 bytes .../usr/lib/lua/luci/util.lua | 782 +++++ .../usr/lib/lua/luci/util.luac | Bin 0 -> 30732 bytes .../usr/lib/lua/luci/version.lua | 20 + .../usr/lib/lua/luci/version.luac | Bin 0 -> 933 bytes .../lua/luci/view/admin_mtk/hwnat_status.htm | 56 + .../lib/lua/luci/view/admin_mtk/mtk_hwnat.htm | 86 + .../luci/view/admin_mtk/mtk_ipsec_view.htm | 85 + .../luci/view/admin_mtk/mtk_web_console.htm | 94 + .../luci/view/admin_mtk/mtk_wifi_apcli.htm | 1716 ++++++++++ .../view/admin_mtk/mtk_wifi_apply_reboot.htm | 67 + .../luci/view/admin_mtk/mtk_wifi_chip_cfg.htm | 600 ++++ .../luci/view/admin_mtk/mtk_wifi_dev_cfg.htm | 1380 ++++++++ .../luci/view/admin_mtk/mtk_wifi_loading.htm | 90 + .../mtk_wifi_map_ap_capabilities.htm | 257 ++ .../mtk_wifi_map_bh_link_metrics.htm | 220 ++ .../admin_mtk/mtk_wifi_map_bss_cfg_renew.htm | 1156 +++++++ .../view/admin_mtk/mtk_wifi_map_bssinfo.htm | 363 +++ .../mtk_wifi_map_channel_planning_score.htm | 407 +++ .../mtk_wifi_map_channel_scan_result.htm | 510 +++ .../mtk_wifi_map_client_capabilities.htm | 146 + .../admin_mtk/mtk_wifi_map_data_element.htm | 216 ++ ...mtk_wifi_map_display_bootstrapping_uri.htm | 36 + .../mtk_wifi_map_runtime_topology.htm | 465 +++ .../luci/view/admin_mtk/mtk_wifi_multi_ap.htm | 2522 +++++++++++++++ .../luci/view/admin_mtk/mtk_wifi_overview.htm | 557 ++++ .../luci/view/admin_mtk/mtk_wifi_vif_cfg.htm | 2790 +++++++++++++++++ .../usr/lib/lua/luci/view/csrftoken.htm | 24 + .../lua/luci/view/empty_node_placeholder.htm | 11 + .../usr/lib/lua/luci/view/error404.htm | 12 + .../usr/lib/lua/luci/view/error500.htm | 11 + .../usr/lib/lua/luci/view/footer.htm | 27 + .../usr/lib/lua/luci/view/header.htm | 38 + .../usr/lib/lua/luci/view/indexer.htm | 7 + .../usr/lib/lua/luci/view/sysauth.htm | 75 + .../usr/lib/lua/luci/view/view.htm | 12 + .../usr/lib/lua/luci/xml.lua | 26 + .../usr/lib/lua/luci/xml.luac | Bin 0 -> 864 bytes .../usr/lib/lua/lucihttp.so | Bin 0 -> 12291 bytes .../usr/lib/lua/map_helper.so | Bin 0 -> 33161 bytes .../usr/lib/lua/mime.lua | 89 + .../usr/lib/lua/mime.luac | Bin 0 -> 3856 bytes .../usr/lib/lua/mime/core.so | 1 + .../usr/lib/lua/mtkwifi.lua | 2090 ++++++++++++ .../usr/lib/lua/mtkwifi.luac | Bin 0 -> 115987 bytes .../usr/lib/lua/nixio/fs.lua | 175 ++ .../usr/lib/lua/nixio/fs.luac | Bin 0 -> 7395 bytes .../usr/lib/lua/nixio/util.lua | 270 ++ .../usr/lib/lua/nixio/util.luac | Bin 0 -> 10102 bytes .../usr/lib/lua/shuci.lua | 128 + .../usr/lib/lua/shuci.luac | Bin 0 -> 5518 bytes .../usr/lib/lua/socket-3.0-rc1.so | Bin 0 -> 63610 bytes .../usr/lib/lua/socket.lua | 149 + .../usr/lib/lua/socket.luac | Bin 0 -> 7052 bytes .../usr/lib/lua/socket/core.so | 1 + .../usr/lib/lua/socket/ftp.lua | 329 ++ .../usr/lib/lua/socket/ftp.luac | Bin 0 -> 18626 bytes .../usr/lib/lua/socket/headers.lua | 104 + .../usr/lib/lua/socket/headers.luac | Bin 0 -> 4792 bytes .../usr/lib/lua/socket/http.lua | 420 +++ .../usr/lib/lua/socket/http.luac | Bin 0 -> 19619 bytes .../usr/lib/lua/socket/smtp.lua | 256 ++ .../usr/lib/lua/socket/smtp.luac | Bin 0 -> 12925 bytes .../usr/lib/lua/socket/tp.lua | 134 + .../usr/lib/lua/socket/tp.luac | Bin 0 -> 5764 bytes .../usr/lib/lua/socket/unix.so | Bin 0 -> 45922 bytes .../usr/lib/lua/socket/url.lua | 331 ++ .../usr/lib/lua/socket/url.luac | Bin 0 -> 10974 bytes .../usr/lib/lua/ubus.so | Bin 0 -> 20483 bytes .../usr/lib/lua/wps_action.lua | 26 + .../usr/lib/lua/wps_action.luac | Bin 0 -> 932 bytes 129 files changed, 27848 insertions(+) create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/SAMPLE-MANIFEST.md create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/datconf.so create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/ioctl_helper.so create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/iwinfo.so create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/l1dat_parser.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/l1dat_parser.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/ltn12.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/ltn12.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/cacheloader.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/cacheloader.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/ccache.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/ccache.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/config.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/config.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/admin/index.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/admin/index.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/admin/uci.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/admin/uci.luac create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/hwnat.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/hwnat.luac create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/ipsec.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/ipsec.luac create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/mtkwifi.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/mtkwifi.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/debug.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/debug.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/dispatcher.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/dispatcher.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/http.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/http.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/i18n.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/i18n.luac create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/ip.so create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/jsonc.so create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/ltn12.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/ltn12.luac create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/model/cbi/hwnat.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/model/cbi/hwnat.luac create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/model/cbi/ipsec.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/model/cbi/ipsec.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/model/uci.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/model/uci.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sgi/cgi.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sgi/cgi.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sgi/uhttpd.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sgi/uhttpd.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/store.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/store.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sys.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sys.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sys/zoneinfo.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sys/zoneinfo.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sys/zoneinfo/tzdata.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sys/zoneinfo/tzdata.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sys/zoneinfo/tzoffset.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sys/zoneinfo/tzoffset.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/template.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/template.luac create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/template/parser.so create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/util.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/util.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/version.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/version.luac create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/hwnat_status.htm create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_hwnat.htm create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_ipsec_view.htm create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_web_console.htm create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_apcli.htm create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_apply_reboot.htm create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_chip_cfg.htm create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_dev_cfg.htm create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_loading.htm create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_ap_capabilities.htm create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_bh_link_metrics.htm create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_bss_cfg_renew.htm create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_bssinfo.htm create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_channel_planning_score.htm create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_channel_scan_result.htm create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_client_capabilities.htm create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_data_element.htm create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_display_bootstrapping_uri.htm create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_runtime_topology.htm create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_multi_ap.htm create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_overview.htm create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_vif_cfg.htm create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/csrftoken.htm create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/empty_node_placeholder.htm create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/error404.htm create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/error500.htm create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/footer.htm create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/header.htm create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/indexer.htm create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/sysauth.htm create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/view.htm create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/xml.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/xml.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/lucihttp.so create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/map_helper.so create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/mime.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/mime.luac create mode 120000 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/mime/core.so create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/mtkwifi.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/mtkwifi.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/nixio/fs.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/nixio/fs.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/nixio/util.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/nixio/util.luac create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/shuci.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/shuci.luac create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket-3.0-rc1.so create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket.luac create mode 120000 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/core.so create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/ftp.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/ftp.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/headers.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/headers.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/http.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/http.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/smtp.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/smtp.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/tp.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/tp.luac create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/unix.so create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/url.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/url.luac create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/ubus.so create mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/wps_action.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/wps_action.luac diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/SAMPLE-MANIFEST.md b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/SAMPLE-MANIFEST.md new file mode 100644 index 000000000000..27f70f963cda --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/SAMPLE-MANIFEST.md @@ -0,0 +1,26 @@ +# Lua2Cpg OpenWrt-Derived Firmware Lua Fixture Corpus + +This committed fixture corpus contains Lua files extracted from the +`usr/lib/lua` tree of a real-device OpenWrt-derived firmware image. It is used +by `lua2cpg` tests as a self-contained reviewer corpus for Lua 5.1 source file +inventory, Lua 5.1 bytecode decoding, CPG generation, and native taint evidence +export. + +The outer resource name and test descriptions are vendor-neutral. The original +relative paths and file bytes below `usr/lib/lua` are preserved. + +## Contents + +- Scope: `usr/lib/lua` +- Lua source files: 42 +- Lua bytecode files: 42 +- Total `.lua` and `.luac` files: 84 + +## Notes + +- The `.lua` files exercise source file inventory in the CPG. +- The `.luac` files exercise the bytecode-based semantic and taint-analysis + pipeline. +- This corpus is consumed entirely from `lua2cpg` test resources. +- This manifest does not assert licensing or redistribution facts beyond the + presence of the committed test files. diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/datconf.so b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/datconf.so new file mode 100755 index 0000000000000000000000000000000000000000..22b7b855eb7f59d97ab7a8d4e54cecef3a4d6993 GIT binary patch literal 16530 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 LzPc~<=&Jt@DpD}N literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/iwinfo.so b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/iwinfo.so new file mode 100755 index 0000000000000000000000000000000000000000..afe1a7c61ad71cfa2978a848ebe99de1e3d8e503 GIT binary patch literal 24579 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 S{*|aYNB*xQZtBHU+WrSNLc;q1 literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/l1dat_parser.lua b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/l1dat_parser.lua new file mode 100755 index 000000000000..4763028cb279 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/l1dat_parser.lua @@ -0,0 +1,349 @@ +#!/usr/bin/env lua + +--[[ + * A lua library to manipulate mtk's wifi driver. used in luci-app-mtk. + * + * Copyright (C) 2016 MTK + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. +]] + +local l1dat_parser = { + L1_DAT_PATH = "/etc/wireless/l1profile.dat", + IF_RINDEX = "ifname_ridx", + DEV_RINDEX = "devname_ridx", + MAX_NUM_APCLI = 1, + MAX_NUM_WDS = 4, + MAX_NUM_MESH = 1, + MAX_NUM_EXTIF = 16, + MAX_NUM_DBDC_BAND = 2, +} + +local l1cfg_options = { + ext_ifname="", + apcli_ifname="apcli", + wds_ifname="wds", + mesh_ifname="mesh" + } + +function l1dat_parser.__trim(s) + if s then return (s:gsub("^%s*(.-)%s*$", "%1")) end +end + +function l1dat_parser.__cfg2list(str) + -- delimeter == ";" + local i = 1 + local list = {} + for k in string.gmatch(str, "([^;]+)") do + list[i] = k + i = i + 1 + end + return list +end + +function l1dat_parser.token_get(str, n, v) + -- n starts from 1 + -- v is the backup in case token n is nil + if not str then return v end + local tmp = l1dat_parser.__cfg2list(str) + return tmp[tonumber(n)] or v +end + +function l1dat_parser.add_default_value(l1cfg) + for k, v in ipairs(l1cfg) do + + for opt, default in pairs(l1cfg_options) do + if ( opt == "ext_ifname" ) then + v[opt] = v[opt] or v["main_ifname"].."_" + else + v[opt] = v[opt] or default..k.."_" + end + end + end + + return l1cfg +end + +function l1dat_parser.get_value_by_idx(devidx, mainidx, subidx, key) + --print("Enter l1dat_parser.get_value_by_idx("..devidx..","..mainidx..", "..subidx..", "..key..")
") + if not devidx or not mainidx or not key then return end + + local devs = l1dat_parser.load_l1_profile(l1dat_parser.L1_DAT_PATH) + if not devs then return end + + local dev_ridx = l1dat_parser.DEV_RINDEX + local sidx = subidx or 1 + local devname1 = devidx.."."..mainidx + local devname2 = devidx.."."..mainidx.."."..sidx + + --print("devnam1=", devname1, "devname2=", devname2, "
") + return devs[dev_ridx][devname2] and devs[dev_ridx][devname2][key] + or devs[dev_ridx][devname1] and devs[dev_ridx][devname1][key] +end + +-- path to zone is 1 to 1 mapping +function l1dat_parser.l1_path_to_zone(path) + --print("Enter l1dat_parser.l1_path_to_zone("..path..")
") + if not path then return end + + local devs = l1dat_parser.load_l1_profile(l1dat_parser.L1_DAT_PATH) + if not devs then return end + + for _, dev in pairs(devs[l1dat_parser.IF_RINDEX]) do + if dev.profile_path == path then + return dev.nvram_zone + end + end + + return +end + +-- zone to path is 1 to n mapping +function l1dat_parser.l1_zone_to_path(zone) + if not zone then return end + + local devs = l1dat_parser.load_l1_profile(l1dat_parser.L1_DAT_PATH) + if not devs then return end + + local plist = {} + for _, dev in pairs(devs[l1dat_parser.IF_RINDEX]) do + if dev.nvram_zone == zone then + if not next(plist) then + table.insert(plist,dev.profile_path) + else + local plist_str = table.concat(plist) + if not plist_str:match(dev.profile_path) then + table.insert(plist,dev.profile_path) + end + end + end + end + + return next(plist) and plist or nil +end + +function l1dat_parser.l1_ifname_to_datpath(ifname) + if not ifname then return end + + local devs = l1dat_parser.load_l1_profile(l1dat_parser.L1_DAT_PATH) + if not devs then return end + + local ridx = l1dat_parser.IF_RINDEX + return devs[ridx][ifname] and devs[ridx][ifname].profile_path +end + +function l1dat_parser.l1_ifname_to_zone(ifname) + if not ifname then return end + + local devs = l1dat_parser.load_l1_profile(l1dat_parser.L1_DAT_PATH) + if not devs then return end + + local ridx = l1dat_parser.IF_RINDEX + return devs[ridx][ifname] and devs[ridx][ifname].nvram_zone +end + +function l1dat_parser.l1_zone_to_ifname(zone) + if not zone then return end + + local devs = l1dat_parser.load_l1_profile(l1dat_parser.L1_DAT_PATH) + if not devs then return end + + local zone_dev + for _, dev in pairs(devs[l1dat_parser.DEV_RINDEX]) do + if dev.nvram_zone == zone then + zone_dev = dev + end + end + + if not zone_dev then + return nil + else + return zone_dev.main_ifname, zone_dev.ext_ifname, zone_dev.apcli_ifname, zone_dev.wds_ifname, zone_dev.mesh_ifname + end +end + +-- input: L1 profile path. +-- output A table, devs, contains +-- 1. devs[%d] = table of each INDEX# in the L1 profile +-- 2. devs.ifname_ridx[ifname] +-- = table of each ifname and point to relevant contain in dev[$d] +-- 3. devs.devname_ridx[devname] similar to devs.ifnameridx, but use devname. +-- devname = INDEX#_value.mainidx(.subidx) +-- Using *_ridx do not need to handle name=k1;k2 case of DBDC card. +function l1dat_parser.load_l1_profile(path) + local devs = setmetatable({}, {__index= + function(tbl, key) + local util = require("luci.util") + --print("metatable function:", util.serialize_data(tbl), key) + --print("-----------------------------------------------") + if ( string.match(key, "^%d+")) then + tbl[key] = {} + return tbl[key] + end + end + }) + local nixio = require("nixio") + local chipset_num = {} + local dir = io.popen("ls /etc/wireless/") + if not dir then return end + local fd = io.open(path, "r") + if not fd then return end + + -- convert l1 profile into lua table + for line in fd:lines() do + line = l1dat_parser.__trim(line) + if string.byte(line) ~= string.byte("#") then + local i = string.find(line, "=") + if i then + local k, v, k1, k2 + k = l1dat_parser.__trim( string.sub(line, 1, i-1) ) + v = l1dat_parser.__trim( string.sub(line, i+1) ) + k1, k2 = string.match(k, "INDEX(%d+)_(.+)") + if k1 then + k1 = tonumber(k1) + 1 + if devs[k1][k2] then + nixio.syslog("warning", "skip repeated key"..line) + end + devs[k1][k2] = v or "" + else + k1 = string.match(k, "INDEX(%d+)") + k1 = tonumber(k1) + 1 + devs[k1]["INDEX"] = v + + chipset_num[v] = (not chipset_num[v] and 1) or chipset_num[v] + 1 + devs[k1]["mainidx"] = chipset_num[v] + end + else + nixio.syslog("warning", "skip line without '=' "..line) + end + else + nixio.syslog("warning", "skip comment line "..line) + end + end + + l1dat_parser.add_default_value(devs) + --local util = require("luci.util") + --local seen2 = {} + -- print("Before setup ridx", util.serialize_data(devs, seen2)) + + -- Force to setup reverse indice for quick search. + -- Benifit: + -- 1. O(1) search with ifname, devname + -- 2. Seperate DBDC name=k1;k2 format in the L1 profile into each + -- ifname, devname. + local dbdc_if = {} + local ridx = l1dat_parser.IF_RINDEX + local dridx = l1dat_parser.DEV_RINDEX + local band_num = l1dat_parser.MAX_NUM_DBDC_BAND + local k, v, dev, i , j, last + local devname + devs[ridx] = {} + devs[dridx] = {} + for _, dev in ipairs(devs) do + dbdc_if[band_num] = l1dat_parser.token_get(dev.main_ifname, band_num, nil) + if dbdc_if[band_num] then + for i = 1, band_num - 1 do + dbdc_if[i] = l1dat_parser.token_get(dev.main_ifname, i, nil) + end + for i = 1, band_num do + devs[ridx][dbdc_if[i]] = {} + devs[ridx][dbdc_if[i]]["subidx"] = i + + for k, v in pairs(dev) do + if k == "INDEX" or k == "EEPROM_offset" or k == "EEPROM_size" + or k == "mainidx" then + devs[ridx][dbdc_if[i]][k] = v + else + devs[ridx][dbdc_if[i]][k] = l1dat_parser.token_get(v, i, "") + end + end + devname = dev.INDEX.."."..dev.mainidx.."."..devs[ridx][dbdc_if[i]]["subidx"] + devs[dridx][devname] = devs[ridx][dbdc_if[i]] + end + + local apcli_if, wds_if, ext_if, mesh_if = {}, {}, {}, {} + + for i = 1, band_num do + ext_if[i] = l1dat_parser.token_get(dev.ext_ifname, i, nil) + apcli_if[i] = l1dat_parser.token_get(dev.apcli_ifname, i, nil) + wds_if[i] = l1dat_parser.token_get(dev.wds_ifname, i, nil) + mesh_if[i] = l1dat_parser.token_get(dev.mesh_ifname, i, nil) + end + + for i = 1, l1dat_parser.MAX_NUM_EXTIF - 1 do -- ifname idx is from 0 + for j = 1, band_num do + devs[ridx][ext_if[j]..i] = devs[ridx][dbdc_if[j]] + end + end + + for i = 0, l1dat_parser.MAX_NUM_APCLI - 1 do + for j = 1, band_num do + devs[ridx][apcli_if[j]..i] = devs[ridx][dbdc_if[j]] + end + end + + for i = 0, l1dat_parser.MAX_NUM_WDS - 1 do + for j = 1, band_num do + devs[ridx][wds_if[j]..i] = devs[ridx][dbdc_if[j]] + end + end + + for i = 0, l1dat_parser.MAX_NUM_MESH - 1 do + for j = 1, band_num do + if mesh_if[j] then + devs[ridx][mesh_if[j]..i] = devs[ridx][dbdc_if[j]] + end + end + end + + else + devs[ridx][dev.main_ifname] = dev + + devname = dev.INDEX.."."..dev.mainidx + devs[dridx][devname] = dev + + for i = 1, l1dat_parser.MAX_NUM_EXTIF - 1 do -- ifname idx is from 0 + devs[ridx][dev.ext_ifname..i] = dev + end + + for i = 0, l1dat_parser.MAX_NUM_APCLI - 1 do -- ifname idx is from 0 + devs[ridx][dev.apcli_ifname..i] = dev + end + + for i = 0, l1dat_parser.MAX_NUM_WDS - 1 do -- ifname idx is from 0 + devs[ridx][dev.wds_ifname..i] = dev + end + + for i = 0, l1dat_parser.MAX_NUM_MESH - 1 do -- ifname idx is from 0 + devs[ridx][dev.mesh_ifname..i] = dev + end + end + end + + fd:close() + return devs +end + +function l1dat_parser.creat_link_for_nvram( ) + local devs = l1dat_parser.load_l1_profile(l1dat_parser.L1_DAT_PATH) + for devname, dev in pairs(devs.devname_ridx) do + local dev = devs.devname_ridx[devname] + profile = dev.profile_path + os.execute("mkdir -p /tmp/mtk/wifi/") + if dev.nvram_zone == "dev1" then + os.execute("ln -sf " ..profile.." /tmp/mtk/wifi/2860") + elseif dev.nvram_zone == "dev2" then + os.execute("ln -sf " ..profile.." /tmp/mtk/wifi/rtdev") + elseif dev.nvram_zone == "dev3" then + os.execute("ln -sf " ..profile.." /tmp/mtk/wifi/wifi3") + end + end +end +return l1dat_parser diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/l1dat_parser.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/l1dat_parser.luac new file mode 100644 index 0000000000000000000000000000000000000000..b09ee80a4c323d219d8d900cac69e3bcc069bcaf GIT binary patch literal 14709 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 literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/ltn12.lua b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/ltn12.lua new file mode 100644 index 000000000000..afa735dc2cec --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/ltn12.lua @@ -0,0 +1,319 @@ +----------------------------------------------------------------------------- +-- LTN12 - Filters, sources, sinks and pumps. +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module +----------------------------------------------------------------------------- +local string = require("string") +local table = require("table") +local unpack = unpack or table.unpack +local base = _G +local _M = {} +if module then -- heuristic for exporting a global package table + ltn12 = _M +end +local filter,source,sink,pump = {},{},{},{} + +_M.filter = filter +_M.source = source +_M.sink = sink +_M.pump = pump + +local unpack = unpack or table.unpack +local select = base.select + +-- 2048 seems to be better in windows... +_M.BLOCKSIZE = 2048 +_M._VERSION = "LTN12 1.0.3" + +----------------------------------------------------------------------------- +-- Filter stuff +----------------------------------------------------------------------------- +-- returns a high level filter that cycles a low-level filter +function filter.cycle(low, ctx, extra) + base.assert(low) + return function(chunk) + local ret + ret, ctx = low(ctx, chunk, extra) + return ret + end +end + +-- chains a bunch of filters together +-- (thanks to Wim Couwenberg) +function filter.chain(...) + local arg = {...} + local n = base.select('#',...) + local top, index = 1, 1 + local retry = "" + return function(chunk) + retry = chunk and retry + while true do + if index == top then + chunk = arg[index](chunk) + if chunk == "" or top == n then return chunk + elseif chunk then index = index + 1 + else + top = top+1 + index = top + end + else + chunk = arg[index](chunk or "") + if chunk == "" then + index = index - 1 + chunk = retry + elseif chunk then + if index == n then return chunk + else index = index + 1 end + else base.error("filter returned inappropriate nil") end + end + end + end +end + +----------------------------------------------------------------------------- +-- Source stuff +----------------------------------------------------------------------------- +-- create an empty source +local function empty() + return nil +end + +function source.empty() + return empty +end + +-- returns a source that just outputs an error +function source.error(err) + return function() + return nil, err + end +end + +-- creates a file source +function source.file(handle, io_err) + if handle then + return function() + local chunk = handle:read(_M.BLOCKSIZE) + if not chunk then handle:close() end + return chunk + end + else return source.error(io_err or "unable to open file") end +end + +-- turns a fancy source into a simple source +function source.simplify(src) + base.assert(src) + return function() + local chunk, err_or_new = src() + src = err_or_new or src + if not chunk then return nil, err_or_new + else return chunk end + end +end + +-- creates string source +function source.string(s) + if s then + local i = 1 + return function() + local chunk = string.sub(s, i, i+_M.BLOCKSIZE-1) + i = i + _M.BLOCKSIZE + if chunk ~= "" then return chunk + else return nil end + end + else return source.empty() end +end + +-- creates table source +function source.table(t) + base.assert('table' == type(t)) + local i = 0 + return function() + i = i + 1 + return t[i] + end +end + +-- creates rewindable source +function source.rewind(src) + base.assert(src) + local t = {} + return function(chunk) + if not chunk then + chunk = table.remove(t) + if not chunk then return src() + else return chunk end + else + table.insert(t, chunk) + end + end +end + +-- chains a source with one or several filter(s) +function source.chain(src, f, ...) + if ... then f=filter.chain(f, ...) end + base.assert(src and f) + local last_in, last_out = "", "" + local state = "feeding" + local err + return function() + if not last_out then + base.error('source is empty!', 2) + end + while true do + if state == "feeding" then + last_in, err = src() + if err then return nil, err end + last_out = f(last_in) + if not last_out then + if last_in then + base.error('filter returned inappropriate nil') + else + return nil + end + elseif last_out ~= "" then + state = "eating" + if last_in then last_in = "" end + return last_out + end + else + last_out = f(last_in) + if last_out == "" then + if last_in == "" then + state = "feeding" + else + base.error('filter returned ""') + end + elseif not last_out then + if last_in then + base.error('filter returned inappropriate nil') + else + return nil + end + else + return last_out + end + end + end + end +end + +-- creates a source that produces contents of several sources, one after the +-- other, as if they were concatenated +-- (thanks to Wim Couwenberg) +function source.cat(...) + local arg = {...} + local src = table.remove(arg, 1) + return function() + while src do + local chunk, err = src() + if chunk then return chunk end + if err then return nil, err end + src = table.remove(arg, 1) + end + end +end + +----------------------------------------------------------------------------- +-- Sink stuff +----------------------------------------------------------------------------- +-- creates a sink that stores into a table +function sink.table(t) + t = t or {} + local f = function(chunk, err) + if chunk then table.insert(t, chunk) end + return 1 + end + return f, t +end + +-- turns a fancy sink into a simple sink +function sink.simplify(snk) + base.assert(snk) + return function(chunk, err) + local ret, err_or_new = snk(chunk, err) + if not ret then return nil, err_or_new end + snk = err_or_new or snk + return 1 + end +end + +-- creates a file sink +function sink.file(handle, io_err) + if handle then + return function(chunk, err) + if not chunk then + handle:close() + return 1 + else return handle:write(chunk) end + end + else return sink.error(io_err or "unable to open file") end +end + +-- creates a sink that discards data +local function null() + return 1 +end + +function sink.null() + return null +end + +-- creates a sink that just returns an error +function sink.error(err) + return function() + return nil, err + end +end + +-- chains a sink with one or several filter(s) +function sink.chain(f, snk, ...) + if ... then + local args = { f, snk, ... } + snk = table.remove(args, #args) + f = filter.chain(unpack(args)) + end + base.assert(f and snk) + return function(chunk, err) + if chunk ~= "" then + local filtered = f(chunk) + local done = chunk and "" + while true do + local ret, snkerr = snk(filtered, err) + if not ret then return nil, snkerr end + if filtered == done then return 1 end + filtered = f(done) + end + else return 1 end + end +end + +----------------------------------------------------------------------------- +-- Pump stuff +----------------------------------------------------------------------------- +-- pumps one chunk from the source to the sink +function pump.step(src, snk) + local chunk, src_err = src() + local ret, snk_err = snk(chunk, src_err) + if chunk and ret then return 1 + else return nil, src_err or snk_err end +end + +-- pumps all data from a source to a sink, using a step function +function pump.all(src, snk, step) + base.assert(src and snk) + step = step or pump.step + while true do + local ret, err = step(src, snk) + if not ret then + if err then return nil, err + else return 1 end + end + end +end + +return _M diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/ltn12.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/ltn12.luac new file mode 100644 index 0000000000000000000000000000000000000000..8ac213298f7db036f34360c76291a243093cff07 GIT binary patch literal 11299 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 literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/cacheloader.lua b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/cacheloader.lua new file mode 100644 index 000000000000..7ef971df8dae --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/cacheloader.lua @@ -0,0 +1,12 @@ +-- Copyright 2008 Steven Barth +-- Copyright 2008 Jo-Philipp Wich +-- Licensed to the public under the Apache License 2.0. + +local config = require "luci.config" +local ccache = require "luci.ccache" + +module "luci.cacheloader" + +if config.ccache and config.ccache.enable == "1" then + ccache.cache_ondemand() +end diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/cacheloader.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/cacheloader.luac new file mode 100644 index 0000000000000000000000000000000000000000..a4549a694788f8da05fa1ab672e0d77886070fcb GIT binary patch literal 439 zcmZ`#%W48K6g|5d4G8qPuRo7bIp<9he6WWFHsKN0fey?U(i5nGA~1 z3nw|8oSU1Imv^T>AY>#XOwg7x+8k_F>cm#7%;_NW){`?@9|*AmUOmJc@skHO#LpMt z2Lj&;qE<1!=xnZpXI}eQEH1H_EBG3}NT&vbt; zHjjjS3eLPtTt=N;h_>?@ +-- Copyright 2008 Jo-Philipp Wich +-- Licensed to the public under the Apache License 2.0. + +local io = require "io" +local fs = require "nixio.fs" +local util = require "luci.util" +local nixio = require "nixio" +local debug = require "debug" +local string = require "string" +local package = require "package" + +local type, loadfile = type, loadfile + + +module "luci.ccache" + +function cache_ondemand(...) + if debug.getinfo(1, 'S').source ~= "=?" then + cache_enable(...) + end +end + +function cache_enable(cachepath, mode) + cachepath = cachepath or "/tmp/luci-modulecache" + mode = mode or "r--r--r--" + + local loader = package.loaders[2] + local uid = nixio.getuid() + + if not fs.stat(cachepath) then + fs.mkdir(cachepath) + end + + local function _encode_filename(name) + local encoded = "" + for i=1, #name do + encoded = encoded .. ("%2X" % string.byte(name, i)) + end + return encoded + end + + local function _load_sane(file) + local stat = fs.stat(file) + if stat and stat.uid == uid and stat.modestr == mode then + return loadfile(file) + end + end + + local function _write_sane(file, func) + if nixio.getuid() == uid then + local fp = io.open(file, "w") + if fp then + fp:write(util.get_bytecode(func)) + fp:close() + fs.chmod(file, mode) + end + end + end + + package.loaders[2] = function(mod) + local encoded = cachepath .. "/" .. _encode_filename(mod) + local modcons = _load_sane(encoded) + + if modcons then + return modcons + end + + -- No cachefile + modcons = loader(mod) + if type(modcons) == "function" then + _write_sane(encoded, modcons) + end + return modcons + end +end diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/ccache.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/ccache.luac new file mode 100644 index 0000000000000000000000000000000000000000..542c00e150091d3dfa78f1f72504c02eb6a21465 GIT binary patch literal 3602 zcma)9+iny`5Urja8w0tJn;}3Fq?p7gu^o}C6cJ`E15v_D6rn`O%4*mh+auO9o4p8l zN)G~mL_{tR$$!iugB0s$NcA9fV(r@n`%Q zc}9vBG@D^_BU)?^!cxP|J35cQQ$RfsIc>#Uf}B~ARmk@t4|9fujDFgeu zk<%IMA!nJ9Rmk@t4~Iq`Kz?LAnOgSb(p^uohn}o1i)ndS1e;#*YSLPEa!thID~o~U ztnakg3gg<9<(j;Xx#u`BQ_ggw?+0-=5+9$s7N_DLF6AV?9jA+HJ@Jo}n)aX>FAn-~ zTm0#&(RkR3ZVcANKUB{4`rSCeSoiCM&70vm_M0jV`gb}Q>5lC*Y^}v@Xmo+iv^C&Z zJ?~D?blw;L`En6AY@|sm+6g!_WFzb~ zgS@|GZVo$G0~-&zT4^KOuzH;3wYi6wE*c!`h)r91vi66$n=v4yi%&POE%i4$OC-ud z!Jd*WO5*OqLh)}LLh>%^_B4bNjQEG(4q{MB`P%D;{W04&Z?@ttFEVOxLOKT^xsMz} zk2^b7{Y4!Hzvc$qvc=_wd5yFTJnQXb*8Dj%*|pr%ezE`j@Ey!~F|G}VmaSwDD5iU! zJ^$_aa5wJsBWKB-=YX>lcnjmFf%dFnya72r1+XRIX0b3cPIEa*nrSO)2~S78rs(`y z+C38^8E>C+$7jLWHP?gyOS`jxtZ36vbgYDws(N{0JzI% z=BxlrYKwl=in)${HuvmSkjpl-$8#~W2*;M?SWGb%dk6FCpzQ()SYL#NQ+1GXeE4=mU#>OLRt60;Qq&SfU!`)19Lu$`5feS(s)Vo*!X z(6h$MDoVyqAo3)rvqw1*@3F-rhp@uV+X<{=y-Q~e^JEI9ohT7ssc~zZjkmfn_&=)t z_^!c>AWO$a&34+W4maQhoK}Q)eE$G^0R1FBgpT;=0DKHR@yR}*Ul|;Y#3-C6tI9#r z#1MrU7=^OqsybCnP=d`p)w_bMAK*dPUEIU1dT>JZjt9j$A{QWh2}~QArb|`;MAe0y zVcM}`uH}|=K*+88Ia7pNEV6|!gbOF_EaYtxmtn{Fc#tFG#KHzTD}kefWtO?HGp3`! zE{%T{ticogI888J#OX8i`{5$=iaKOs8Mr)wD;Q7WbLgu00%JOY4)XyvV*9bd4bEKHHu{#WP+=M(-YktC-_`_j(LU zR~q-Qm-TYOMJ#SqYy4RK=qc(B^!$m^V4n!kOZ;{}Z_kx;2(yV%cTM=BKDO~DNmdNTIhNkJbgcxeUSTjE#+{EbEM-C>OW3v?!JxL4&e z6yL6>P>pzEs`P5ETJbX~cRP^ouWF0mwfqf2?ID1z^eInD^<+$5DfPOabLH_;$$i;I K{~^%{D*pw#d9eHd literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/config.lua b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/config.lua new file mode 100644 index 000000000000..d01153f4f564 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/config.lua @@ -0,0 +1,18 @@ +-- Copyright 2008 Steven Barth +-- Licensed to the public under the Apache License 2.0. + +local util = require "luci.util" +module("luci.config", + function(m) + if pcall(require, "luci.model.uci") then + local config = util.threadlocal() + setmetatable(m, { + __index = function(tbl, key) + if not config[key] then + config[key] = luci.model.uci.cursor():get_all("luci", key) + end + return config[key] + end + }) + end + end) diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/config.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/config.luac new file mode 100644 index 0000000000000000000000000000000000000000..64a2cbcc44b553a8dd4f4d820441e3d179b77a52 GIT binary patch literal 918 zcma)4OHLa>5Um~>^GQN}QZ_*x!DkptF)KD~BP44WH)fPEPR27*vMUfq;tB|Hmhl2_ zas`$gg!g);v0_-Hq}SD5RbBO}yS^S&$)Om#X*cDqG!mzivy*Jp&8wufP(^kN>pIYj zMRrw8Uq#Mqkw@4lN3ohcQL)$~T_c72Rb_<}yWJY{D$Tl8ndM?PTGMzkta5||0zRZ6 zA*PL?>HwbrW28gf2WGLzTv1%re8>5VM*0nUzpiPM88Xy3Z}&uoKAFl^@BHExWK-Bl zn0YN1%Fi^(!R-bA_l;XTIWTs47o99gm#2kGhWP~7#lCBUr>-2kGAWZ|_7`#v2HDxr z{r;ynJ`YQnNH#u0UjkRLn47riiRXJ@5!Ys+KwX;$RrxD((?efsR322K>sp`h-XR_P zTgN@ig+l(zZha$SZQ~Q~3*VAf#dK0EX^&hv;M-70@y!bOegBIc#0IeK2nZy~9%P}4!u S0dx|~OoaW5i)(O(0eJ!uVQm)x literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/admin/index.lua b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/admin/index.lua new file mode 100644 index 000000000000..736d0cdccff3 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/admin/index.lua @@ -0,0 +1,196 @@ +-- Copyright 2008 Steven Barth +-- Licensed to the public under the Apache License 2.0. + +module("luci.controller.admin.index", package.seeall) + +function action_logout() + local dsp = require "luci.dispatcher" + local utl = require "luci.util" + local sid = dsp.context.authsession + + if sid then + utl.ubus("session", "destroy", { ubus_rpc_session = sid }) + + luci.http.header("Set-Cookie", "sysauth=%s; expires=%s; path=%s" %{ + '', 'Thu, 01 Jan 1970 01:00:00 GMT', dsp.build_url() + }) + end + + luci.http.redirect(dsp.build_url()) +end + +function action_translations(lang) + local i18n = require "luci.i18n" + local http = require "luci.http" + local fs = require "nixio".fs + + if lang and #lang > 0 then + lang = i18n.setlanguage(lang) + if lang then + local s = fs.stat("%s/base.%s.lmo" %{ i18n.i18ndir, lang }) + if s then + http.header("Cache-Control", "public, max-age=31536000") + http.header("ETag", "%x-%x-%x" %{ s["ino"], s["size"], s["mtime"] }) + end + end + end + + http.prepare_content("application/javascript; charset=utf-8") + http.write("window.TR=") + http.write_json(i18n.dump()) +end + +local function ubus_reply(id, data, code, errmsg) + local reply = { jsonrpc = "2.0", id = id } + if errmsg then + reply.error = { + code = code, + message = errmsg + } + elseif type(code) == "table" then + reply.result = code + else + reply.result = { code, data } + end + + return reply +end + +local ubus_types = { + nil, + "array", + "object", + "string", + nil, -- INT64 + "number", + nil, -- INT16, + "boolean", + "double" +} + +local function ubus_access(sid, obj, fun) + local res, code = luci.util.ubus("session", "access", { + ubus_rpc_session = sid, + scope = "ubus", + object = obj, + ["function"] = fun + }) + + return (type(res) == "table" and res.access == true) +end + +local function ubus_request(req) + if type(req) ~= "table" or type(req.method) ~= "string" or req.jsonrpc ~= "2.0" or req.id == nil then + return ubus_reply(nil, nil, -32600, "Invalid request") + + elseif req.method == "call" then + if type(req.params) ~= "table" or #req.params < 3 then + return ubus_reply(nil, nil, -32600, "Invalid parameters") + end + + local sid, obj, fun, arg = + req.params[1], req.params[2], req.params[3], req.params[4] or {} + if type(arg) ~= "table" or arg.ubus_rpc_session ~= nil then + return ubus_reply(req.id, nil, -32602, "Invalid parameters") + end + + if sid == "00000000000000000000000000000000" and luci.dispatcher.context.authsession then + sid = luci.dispatcher.context.authsession + end + + if not ubus_access(sid, obj, fun) then + return ubus_reply(req.id, nil, -32002, "Access denied") + end + + arg.ubus_rpc_session = sid + + local res, code = luci.util.ubus(obj, fun, arg) + return ubus_reply(req.id, res, code or 0) + + elseif req.method == "list" then + if req.params == nil or (type(req.params) == "table" and #req.params == 0) then + local objs = luci.util.ubus() + return ubus_reply(req.id, nil, objs) + + elseif type(req.params) == "table" then + local n, rv = nil, {} + for n = 1, #req.params do + if type(req.params[n]) ~= "string" then + return ubus_reply(req.id, nil, -32602, "Invalid parameters") + end + + local sig = luci.util.ubus(req.params[n]) + if sig and type(sig) == "table" then + rv[req.params[n]] = {} + + local m, p + for m, p in pairs(sig) do + if type(p) == "table" then + rv[req.params[n]][m] = {} + + local pn, pt + for pn, pt in pairs(p) do + rv[req.params[n]][m][pn] = ubus_types[pt] or "unknown" + end + end + end + end + end + return ubus_reply(req.id, nil, rv) + + else + return ubus_reply(req.id, nil, -32602, "Invalid parameters") + end + end + + return ubus_reply(req.id, nil, -32601, "Method not found") +end + +function action_ubus() + local parser = require "luci.jsonc".new() + + luci.http.context.request:setfilehandler(function(_, s) + if not s then + return nil + end + + local ok, err = parser:parse(s) + return (not err or nil) + end) + + luci.http.context.request:content() + + local json = parser:get() + if json == nil or type(json) ~= "table" then + luci.http.prepare_content("application/json") + luci.http.write_json(ubus_reply(nil, nil, -32700, "Parse error")) + return + end + + local response + if #json == 0 then + response = ubus_request(json) + else + response = {} + + local _, request + for _, request in ipairs(json) do + response[_] = ubus_request(request) + end + end + + luci.http.prepare_content("application/json") + luci.http.write_json(response) +end + +function action_menu() + local dsp = require "luci.dispatcher" + local utl = require "luci.util" + local http = require "luci.http" + + local acls = utl.ubus("session", "access", { ubus_rpc_session = http.getcookie("sysauth") }) + local menu = dsp.menu_json(acls or {}) or {} + + http.prepare_content("application/json") + http.write_json(menu) +end diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/admin/index.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/admin/index.luac new file mode 100644 index 0000000000000000000000000000000000000000..45abfc744ff6c7bb8e468e69fd554606fdca093a GIT binary patch literal 8867 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 literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/admin/uci.lua b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/admin/uci.lua new file mode 100644 index 000000000000..7aad10d58a28 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/admin/uci.lua @@ -0,0 +1,70 @@ +-- Copyright 2008 Steven Barth +-- Copyright 2010-2019 Jo-Philipp Wich +-- Licensed to the public under the Apache License 2.0. + +module("luci.controller.admin.uci", package.seeall) + +local function ubus_state_to_http(errstr) + local map = { + ["Invalid command"] = 400, + ["Invalid argument"] = 400, + ["Method not found"] = 404, + ["Entry not found"] = 404, + ["No data"] = 204, + ["Permission denied"] = 403, + ["Timeout"] = 504, + ["Not supported"] = 500, + ["Unknown error"] = 500, + ["Connection failed"] = 503 + } + + local code = map[errstr] or 200 + local msg = errstr or "OK" + + luci.http.status(code, msg) + + if code ~= 204 then + luci.http.prepare_content("text/plain") + luci.http.write(msg) + end +end + +function action_apply_rollback() + local uci = require "luci.model.uci" + local token, errstr = uci:apply(true) + if token then + luci.http.prepare_content("application/json") + luci.http.write_json({ token = token }) + else + ubus_state_to_http(errstr) + end +end + +function action_apply_unchecked() + local uci = require "luci.model.uci" + local _, errstr = uci:apply(false) + ubus_state_to_http(errstr) +end + +function action_confirm() + local uci = require "luci.model.uci" + local token = luci.http.formvalue("token") + local _, errstr = uci:confirm(token) + ubus_state_to_http(errstr) +end + +function action_revert() + local uci = require "luci.model.uci" + local changes = uci:changes() + + -- Collect files to be reverted + local _, errstr, r, tbl + for r, tbl in pairs(changes) do + _, errstr = uci:revert(r) + if errstr then + break + end + end + + ubus_state_to_http(errstr or "OK") +end diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/admin/uci.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/admin/uci.luac new file mode 100644 index 0000000000000000000000000000000000000000..2e63ef18326471b1e9bdbe593e437e21eef720a8 GIT binary patch literal 3085 zcmb7G?QYye6uq-?(o&#&hmS_p0!2^(Dc=GlV4NzcmO=pugoG-s+}%lH;v5dzQo>!G^_@Fk*Y}>e;}c)x!8fA3qux<@NG785 zUbr0PLDl4=HTM+$=K)`fT(=^7g;q=2s#CD_VWXTAC4EZ`x`tkqs+_aX(MLH~C^;!s zjzag2lqQiu($6C!-V0Sbq6fVs&e9}`Ou8KO2VuMnqrlCH41?ZQuz@*ljgc`y6p6Q3 z4G4N!n8e*+IE=QtT;~#Ii1+NQ?mX^on%UemJonrUUVgAGD|DvPrWtB2|ab zk&|pFIr0h~pWBvnw6?%*N5;f2ox;D`(JR+F#me=LKD!6p=*aCGdx*$yS_ID9UG2%; zN*`NK=Et5asMXejzuMADl*Mn!x9a=CSBtu3>;B!@=i!(4 zIQ}S|_;L`1{dO-I41%~Xa})En_MVt%^@t$d$Ok6Qr0)6BWSdF99Vc0PJ<03I8k61f zt4QB=V?>?03rV{lWI?r`4Yl`Vwcpn!9fYG1^49K~I7B`sKD~uyUE;qD2PVlgH+x6d zsks1WM)`1Nq*8BsHi=_XlBK;Kgi$?pxTC9xz{5(Dug;6d zV?ZHG_gkA;Hf*pl3$lE)AM0UihCyn&By}0chbL=DW`54j45J{7#apOsSJE&urA{94 z#d$Q30i5*+@F+k=lC6w46UHDuGX)+7hv%Tr0uLQ=Vg{CAn+7@V0q)=-oTDs-LF5#` zF3KPnmhRH#BGya#ru6y1z|ltOJ8eyAqj4HQ-I%A}T*TM#$BH^{wcHXb7YnO?fPJxO z**^-AJ2F;x^jN7~CHJ(b+E$F!F&}aCD$;gRJ^Dyox>tMt=_Q01EleHf!iXZ`h)BiWAO^6bdg8N`5IHbw@ZJ zrkf?*^3+T^L|%t-2Eg!nvi<&yOPTT8oY2Tz>7%O;|1)!3ihI4$z?;w=mZ3Y)w^{KZ zb6NnUTSkVK8L^=Gkg<3hLqC`dUmypYu~@YtYegYzGL~Jfo8};^ z-@&a(09yAm!Ba%bR3@DHkeT=pE35$+ew=l(nF-b*M*phV*7dPS!3-#+7NPvv@C z@7^t|3OI|BGyfOnqaNEuE%x_a==$%SYgqU$?sMA5zLJ~OUCp2&hL zWcn2tXxdu +-- Copyright 2008 Jo-Philipp Wich +-- Licensed to the public under the Apache License 2.0. +local luci = {} +luci.util = require "luci.util" +luci.http = require "luci.http" + +module("luci.controller.hwnat", package.seeall) +function read_pipe(pipe) + local fp = io.popen(pipe) + local txt = fp:read("*a") + fp:close() + return txt +end + +function index() + if not (nixio.fs.access("/etc/config/hwnat") or nixio.fs.access("/etc/config/hwnat")) then + return + end + + entry({"admin", "mtk", "hwnat"}, cbi("hwnat"), _("NAT Accelerate")) + entry({"admin", "mtk", "hwnat_binding_status"}, call("hwnat_binding_status"), nil).leaf = true +end + +function hwnat_binding_status() + local result = luci.util.execi("hwnat -g") + local data = {} + local t = {} + local proto_num = {} + local i = 0 + + luci.http.prepare_content("application/json") + + if not result then + luci.http.write('[]') + return + end + + for line in result do + if i ~= 0 and line:match(":") then + t = line:split(" ") + proto_num = t[1]:split("=") + data[#data+1] = { + type_ = proto_num[1], + foe_entry = proto_num[2], + src_info = t[3], + new_info = t[5] + } + end + i = i + 1 + end + + luci.http.write_json(data) +end \ No newline at end of file diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/hwnat.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/hwnat.luac new file mode 100644 index 0000000000000000000000000000000000000000..96cde61a2a4c09075d50fd5d00c05ebef68a4d2b GIT binary patch literal 2466 zcmbVOZEqVz5T4zf(*(k+z(5-+3y^3FjS&(EAr;PUlZd9OgjA_gDY7o-wR7}*cbv~k z`iXTAFAaa8;D?I85Pke3qnFZxuoj*QIINfcx{$&UV~jmyL$BP#Aew243U`K{c-YIj5^zv&5yDbZuC z#a}d~CoYNK;mzgZeLFT-5LZbgaiXHClALDcc*Uz6{V*OXL?LtPysdd~J{Ej5j28gT zmzTA5HxS0M3SbU;$y@*awUneq$jB3D&p2q{HN#0V$K5Q zNHgaUF_DK{)`vgyIxiu|^3bDTkIP?feOOV(^L;hM`>XJ0X{yX`f^xl}-z?~6siVNd zhgDFoI|y)yws?c=L1imuTARNekR$U2s6!4X$8x)Mr_n+J;I@~6r2omv^5Xf1l6g)ZBy8UG&bghi#b0Ey-=rf`tHP)b$3y3wcE~gA3pn9 z+`|#osiwDP(8&d()1;k^_I0r}o53&FO`y9YHQj0-ItLQ%vwbKY&GvseZvTN%{(3yS z!Q-Rj_BxLTU+H~krFC6;Esp=s4`qJiBdLWh^W)l-r<}LWl6!l~2Zy*Du^4_euJ)@| zRnE&hDqmTaBvpx*stz|%@RuCs2aA^^Vc>gN5J%1LhB1h2m)cDO=AykU-+Wt{243dx zR80Q7vJ4Ro(JjXtHnG3IEOpl^@9bfsa1Csq>BXv(UoN|-Tkdq2`kf%^#WvtlWs219 zQ>NEkhr1z**+a+di(i>k=TE@}=vINZfr~)n6ubi+(Jb&0#*YDBpT|#50beSn3^#R_R1HUAhJH$P4X`bw>t!A;T&}c7 zn12e`%^p*d##!8nMg!uHK3lABU{+y`<=v%ToHqI@;>H%Im+4K|J_Gm@#bWIIxmA_T zd}gh!`07_yn`{_Fism{-xeNZYU|JEk5B8o1?`|HvgOlL(xmtV;c^_Oo#+i$uotS=! Gf&K+$?>yB2 literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/ipsec.lua b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/ipsec.lua new file mode 100755 index 000000000000..e44ef5344910 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/ipsec.lua @@ -0,0 +1,72 @@ +local luci = {} +luci.util = require "luci.util" +luci.http = require "luci.http" +local uci = require "luci.model.uci" + +module("luci.controller.ipsec", package.seeall) + +function index() + entry({"admin", "network", "ipsec"}, cbi("ipsec"), _("IP Security")) + entry({"admin", "network", "ipsec", "vpn_status"}, call("ipsec_vpn_status"), nil).leaf = true + entry({"admin", "network", "ipsec", "vpn_connect"}, call("ipsec_vpn_connect"), nil).leaf = true + entry({"admin", "network", "ipsec", "vpn_disconnect"}, call("ipsec_vpn_disconnect"), nil).leaf = true +end + +function ipsec_vpn_status() + local handle = io.popen(" ipsec status 2>/dev/null") + local result = handle:read("*all") + handle:close() + local obj ={} + + luci.http.prepare_content("application/json") + if result == "" then + obj.status = "Disconnected" + obj.msg = "Disconnected/Command not found" + luci.http.write_json(obj) + return + end + + a = string.match(result, "(%d+) up") + if (tonumber(a) == 0) then + b = string.match(result, "(%d) connecting") + if (tonumber(b) == 0) then + obj.status = "Disconnected" + obj.msg = "Disconnected" + luci.http.write_json(obj) + return + end + obj.status = "Connected" + obj.msg = b.." connecting" + luci.http.write_json(obj) + return + end + for line in result:gmatch("([^\n]*)\n?") do + if (string.find(line, "ESTABLISHED") or string.find(line, "DELETING")) then + obj.status = "Connected" + obj.msg = string.match(line, ": (.*),") + luci.http.write_json(obj) + end + end +end + +function ipsec_vpn_connect() + local l_gw_name = "" + local curs = uci.cursor() + curs:foreach("ipsec", "remote", function(s) l_gw_name = s[".name"] end) + l_subnet = curs:get("ipsec", "TUNNEL", "local_subnet") + l_wan = curs:get("network","wan" ,"device") + + luci.util.execi("iptables -t nat -I POSTROUTING -o "..l_wan.." -s "..l_subnet.." -j ACCEPT") + luci.util.execi("ipsec down "..l_gw_name.."-TUNNEL") + luci.util.execi("ipsec up "..l_gw_name.."-TUNNEL") + ipsec_vpn_status() +end + +function ipsec_vpn_disconnect() + local l_gw_name = "" + local curs = uci.cursor() + curs:foreach("ipsec", "remote", function(s) l_gw_name = s[".name"] end) + + luci.util.execi("ipsec down "..l_gw_name.."-TUNNEL") + ipsec_vpn_status() +end diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/ipsec.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/ipsec.luac new file mode 100644 index 0000000000000000000000000000000000000000..0fe99ce6ab62a8e53c0ffde17461c045de5ecf07 GIT binary patch literal 4587 zcmb_fOLyBu6uu)XPFo)3nF1|EypxtBobrCPQXHoZg zuQyv(b;53&sem8K&@1>o29$N8PC#@$AaPWuAnub$`#~q}>q);G2#P{MLqegVF{Nzi zf!M?tYk@+W7>Cd6cwqLgib55FP{n)O17O6Zx zZg#s`pGa(^#|__a_8BCigB3i^|bg^+!NVI{?+`^`bj-VZ=Pr)wnAtmo) zL;`-KbV;E|S7N_=823~_%F%GDJJ3fIaz2HF`E+tV;lnjLPpiXe*Bbm&03@G zWSinx{)+3}%Jk``sThJ=brBn`B=1xq_3`v|_Nq2-RohbPJ)n+Q4{1-^KlN?Qt%#!TU>?O_OJL5?b^-NUFqC21 z=>+00gJ>iWyLD5k4gnnr6~qGHWMAWc;`>XYUk}A9B0$@nsIc9+$2zfNkoa3?zkhz_ zIPuA$7VyKZ?q*fkbek5c^kkwTEv`x};XzG1cYd;F-JW#X%$~AcR@RXWm=1uol*TQVn z)V$GJCY2Y=Oy*&yEqXrzxb}qSwDcraFc1dURG>~uw^ox6yw!_ocS`Ek&kCJ zTpyb(xy7!u+KaeLWt6wf9hnmg*DhZ!&ygxw(spqz>bb~o*p(G?(A5kpj$5piL*f9a zS96@6-09^|)*Xv)>Reyk>P_EVw5^sl?rV+AWqG9%`*ULR74X9x@?+)SKZea9 zd;<9(dB$ib1h#q}?Z_<(8x_=TMC}TMc zj6-IubcajRoCYl8_Af73 Bl)eA} literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/mtkwifi.lua b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/mtkwifi.lua new file mode 100755 index 000000000000..6048eecd6788 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/mtkwifi.lua @@ -0,0 +1,2738 @@ +-- This module is a demo to configure MTK' proprietary WiFi driver. +-- Basic idea is to bypass uci and edit wireless profile (mt76xx.dat) directly. +-- LuCI's WiFi configuration is more logical and elegent, but it's quite tricky to +-- translate uci into MTK's WiFi profile (like we did in "uci2dat"). +-- And you will get your hands dirty. +-- +-- Hua Shao + +package.path = '/lib/wifi/?.lua;'..package.path +module("luci.controller.mtkwifi", package.seeall) +local onboardingType = 0; +local ioctl_help = require "ioctl_helper" +local map_help +if pcall(require, "map_helper") then + map_help = require "map_helper" +end +local http = require("luci.http") +local mtkwifi = require("mtkwifi") + +local logDisable = 1 +function debug_write(...) + -- luci.http.write(...) + if logDisable == 1 then + return + end + local syslog_msg = ""; + local ff = io.open("/tmp/dbgmsg", "a") + local nargs = select('#',...) + + for n=1, nargs do + local v = select(n,...) + if (type(v) == "string" or type(v) == "number") then + ff:write(v.." ") + syslog_msg = syslog_msg..v.." "; + elseif (type(v) == "boolean") then + if v then + ff:write("true ") + syslog_msg = syslog_msg.."true "; + else + ff:write("false ") + syslog_msg = syslog_msg.."false "; + end + elseif (type(v) == "nil") then + ff:write("nil ") + syslog_msg = syslog_msg.."nil "; + else + ff:write(" ") + syslog_msg = syslog_msg.." "; + end + end + ff:write("\n") + ff:close() + nixio.syslog("debug", syslog_msg) +end + +function index() + -- if not nixio.fs.access("/etc/wireless") then + -- return + -- end + + entry({"admin", "mtk"}, firstchild(), _("MTK"), 80) + entry({"admin", "mtk", "test"}, call("test")) + entry({"admin", "mtk", "wifi"}, template("admin_mtk/mtk_wifi_overview"), _("WiFi configuration"), 1) + entry({"admin", "mtk", "wifi", "chip_cfg_view"}, template("admin_mtk/mtk_wifi_chip_cfg")).leaf = true + entry({"admin", "mtk", "wifi", "chip_cfg"}, call("chip_cfg")).leaf = true + entry({"admin", "mtk", "wifi", "dev_cfg_view"}, template("admin_mtk/mtk_wifi_dev_cfg")).leaf = true + entry({"admin", "mtk", "wifi", "dev_cfg"}, call("dev_cfg")).leaf = true + entry({"admin", "mtk", "wifi", "dev_cfg_raw"}, call("dev_cfg_raw")).leaf = true + entry({"admin", "mtk", "wifi", "vif_cfg_view"}, template("admin_mtk/mtk_wifi_vif_cfg")).leaf = true + entry({"admin", "mtk", "wifi", "vif_cfg"}, call("vif_cfg")).leaf = true + entry({"admin", "mtk", "wifi", "vif_add_view"}, template("admin_mtk/mtk_wifi_vif_cfg")).leaf = true + entry({"admin", "mtk", "wifi", "vif_add"}, call("vif_cfg")).leaf = true + entry({"admin", "mtk", "wifi", "vif_del"}, call("vif_del")).leaf = true + entry({"admin", "mtk", "wifi", "vif_disable"}, call("vif_disable")).leaf = true + entry({"admin", "mtk", "wifi", "vif_enable"}, call("vif_enable")).leaf = true + entry({"admin", "mtk", "wifi", "get_station_list"}, call("get_station_list")) + entry({"admin", "mtk", "wifi", "get_country_region_list"}, call("get_country_region_list")).leaf = true + entry({"admin", "mtk", "wifi", "get_channel_list"}, call("get_channel_list")) + entry({"admin", "mtk", "wifi", "get_HT_ext_channel_list"}, call("get_HT_ext_channel_list")) + entry({"admin", "mtk", "wifi", "get_5G_2nd_80Mhz_channel_list"}, call("get_5G_2nd_80Mhz_channel_list")) + entry({"admin", "mtk", "wifi", "reset"}, call("reset_wifi")).leaf = true + entry({"admin", "mtk", "wifi", "reload"}, call("reload_wifi")).leaf = true + entry({"admin", "mtk", "wifi", "get_raw_profile"}, call("get_raw_profile")) + entry({"admin", "mtk", "wifi", "apcli_cfg_view"}, template("admin_mtk/mtk_wifi_apcli")).leaf = true + entry({"admin", "mtk", "wifi", "apcli_cfg"}, call("apcli_cfg")).leaf = true + entry({"admin", "mtk", "wifi", "apcli_disconnect"}, call("apcli_disconnect")).leaf = true + entry({"admin", "mtk", "wifi", "apcli_connect"}, call("apcli_connect")).leaf = true + entry({"admin", "mtk", "netmode", "net_cfg"}, call("net_cfg")) + entry({"admin", "mtk", "console"}, template("admin_mtk/mtk_web_console"), _("Web Console"), 4) + entry({"admin", "mtk", "webcmd"}, call("webcmd")) + -- entry({"admin", "mtk", "man"}, template("admin_mtk/mtk_wifi_man"), _("M.A.N"), 3) + -- entry({"admin", "mtk", "man", "cfg"}, call("man_cfg")) + entry({"admin", "mtk", "wifi", "get_wps_info"}, call("get_WPS_Info")).leaf = true + entry({"admin", "mtk", "wifi", "get_wifi_pin"}, call("get_wifi_pin")).leaf = true + entry({"admin", "mtk", "wifi", "set_wifi_gen_pin"}, call("set_wifi_gen_pin")).leaf = true + entry({"admin", "mtk", "wifi", "set_wifi_wps_oob"}, call("set_wifi_wps_oob")).leaf = true + entry({"admin", "mtk", "wifi", "set_wifi_do_wps"}, call("set_wifi_do_wps")).leaf = true + entry({"admin", "mtk", "wifi", "get_wps_security"}, call("get_wps_security")).leaf = true + entry({"admin", "mtk", "wifi", "apcli_get_wps_status"}, call("apcli_get_wps_status")).leaf = true; + entry({"admin", "mtk", "wifi", "apcli_do_enr_pin_wps"}, call("apcli_do_enr_pin_wps")).leaf = true; + entry({"admin", "mtk", "wifi", "apcli_do_enr_pbc_wps"}, call("apcli_do_enr_pbc_wps")).leaf = true; + entry({"admin", "mtk", "wifi", "apcli_cancel_wps"}, call("apcli_cancel_wps")).leaf = true; + entry({"admin", "mtk", "wifi", "apcli_wps_gen_pincode"}, call("apcli_wps_gen_pincode")).leaf = true; + entry({"admin", "mtk", "wifi", "apcli_wps_get_pincode"}, call("apcli_wps_get_pincode")).leaf = true; + entry({"admin", "mtk", "wifi", "apcli_scan"}, call("apcli_scan")).leaf = true; + entry({"admin", "mtk", "wifi", "sta_info"}, call("sta_info")).leaf = true; + entry({"admin", "mtk", "wifi", "get_apcli_conn_info"}, call("get_apcli_conn_info")).leaf = true; + entry({"admin", "mtk", "wifi", "apply_power_boost_settings"}, call("apply_power_boost_settings")).leaf = true; + entry({"admin", "mtk", "wifi", "apply_reboot"}, template("admin_mtk/mtk_wifi_apply_reboot")).leaf = true; + entry({"admin", "mtk", "wifi", "reboot"}, call("exec_reboot")).leaf = true; + entry({"admin", "mtk", "wifi", "get_bssid_num"}, call("get_bssid_num")).leaf = true; + entry({"admin", "mtk", "wifi", "loading"}, template("admin_mtk/mtk_wifi_loading")).leaf = true; + entry({"admin", "mtk", "wifi", "get_apply_status"}, call("get_apply_status")).leaf = true; + entry({"admin", "mtk", "wifi", "reset_to_defaults"}, call("reset_to_defaults")).leaf = true; + local mtkwifi = require("mtkwifi") + -- local profiles = mtkwifi.search_dev_and_profile() + -- for devname,profile in pairs(profiles) do + -- local cfgs = mtkwifi.load_profile(profile) + -- if cfgs["VOW_Airtime_Fairness_En"] then + -- entry({"admin", "mtk", "vow"}, template("admin_mtk/mtk_vow"), _("VoW / ATF / ATC"), 4) + -- break + -- end + -- end + + -- Define map_help again here as same defination at top does not come under scope of luci library. + local map_help + if pcall(require, "map_helper") then + map_help = require "map_helper" + end + if map_help then + entry({"admin", "mtk", "multi_ap", "reset_to_default_easymesh"}, call("reset_to_default_easymesh")).leaf = true; + entry({"admin", "mtk", "multi_ap"}, template("admin_mtk/mtk_wifi_multi_ap"), _("EasyMesh"), 5); + entry({"admin", "mtk", "multi_ap", "map_cfg"}, call("map_cfg")).leaf = true; + entry({"admin", "mtk", "multi_ap", "get_device_role"}, call("get_device_role")).leaf = true; + entry({"admin", "mtk", "multi_ap", "trigger_mandate_steering_on_agent"}, call("trigger_mandate_steering_on_agent")).leaf = true; + entry({"admin", "mtk", "multi_ap", "trigger_back_haul_steering_on_agent"}, call("trigger_back_haul_steering_on_agent")).leaf = true; + entry({"admin", "mtk", "multi_ap", "trigger_wps_fh_agent"}, call("trigger_wps_fh_agent")).leaf = true; + entry({"admin", "mtk", "multi_ap", "display_runtime_topology"}, template("admin_mtk/mtk_wifi_map_runtime_topology")).leaf = true; + entry({"admin", "mtk", "multi_ap", "get_runtime_topology"}, call("get_runtime_topology")).leaf = true; + entry({"admin", "mtk", "multi_ap", "display_data_element"}, template("admin_mtk/mtk_wifi_map_data_element")).leaf = true; + entry({"admin", "mtk", "multi_ap", "display_channel_scan_result"}, template("admin_mtk/mtk_wifi_map_channel_scan_result")).leaf = true; + entry({"admin", "mtk", "multi_ap", "display_channel_planning_score"}, template("admin_mtk/mtk_wifi_map_channel_planning_score")).leaf = true; + entry({"admin", "mtk", "multi_ap", "trigger_multi_ap_on_boarding"}, call("trigger_multi_ap_on_boarding")).leaf = true; + entry({"admin", "mtk", "multi_ap", "display_client_capabilities"}, template("admin_mtk/mtk_wifi_map_client_capabilities")).leaf = true; + entry({"admin", "mtk", "multi_ap", "get_client_capabilities"}, call("get_client_capabilities")).leaf = true; + entry({"admin", "mtk", "multi_ap", "display_ap_capabilities"}, template("admin_mtk/mtk_wifi_map_ap_capabilities")).leaf = true; + entry({"admin", "mtk", "multi_ap", "trigger_uplink_ap_selection"}, call("trigger_uplink_ap_selection")).leaf = true; + entry({"admin", "mtk", "multi_ap", "get_bh_connection_status"}, call("get_bh_connection_status")).leaf = true; + entry({"admin", "mtk", "multi_ap", "get_sta_steering_progress"}, call("get_sta_steering_progress")).leaf = true; + entry({"admin", "mtk", "multi_ap", "get_al_mac"}, call("get_al_mac")).leaf = true; + entry({"admin", "mtk", "multi_ap", "apply_wifi_bh_priority"}, call("apply_wifi_bh_priority")).leaf = true; + entry({"admin", "mtk", "multi_ap", "apply_ap_steer_rssi_th"}, call("apply_ap_steer_rssi_th")).leaf = true; + entry({"admin", "mtk", "multi_ap", "apply_channel_utilization_th"}, call("apply_channel_utilization_th")).leaf = true; + entry({"admin", "mtk", "multi_ap", "get_sta_bh_interface"}, call("get_sta_bh_interface")).leaf = true; + entry({"admin", "mtk", "multi_ap", "get_ap_bh_inf_list"}, call("get_ap_bh_inf_list")).leaf = true; + entry({"admin", "mtk", "multi_ap", "get_ap_fh_inf_list"}, call("get_ap_fh_inf_list")).leaf = true; + entry({"admin", "mtk", "multi_ap", "display_fh_status_bss"}, template("admin_mtk/mtk_wifi_map_bssinfo")).leaf = true; + entry({"admin", "mtk", "multi_ap", "display_bh_link_metrics_ctrler"}, template("admin_mtk/mtk_wifi_map_bh_link_metrics")).leaf = true; + entry({"admin", "mtk", "multi_ap", "easymesh_bss_config_renew"}, template("admin_mtk/mtk_wifi_map_bss_cfg_renew")).leaf = true; + entry({"admin", "mtk", "multi_ap", "easymesh_bss_cfg"}, call("easymesh_bss_cfg")).leaf = true; + entry({"admin", "mtk", "multi_ap", "validate_add_easymesh_bss_req"}, call("validate_add_easymesh_bss_req")).leaf = true; + entry({"admin", "mtk", "multi_ap", "remove_easymesh_bss_cfg_req"}, call("remove_easymesh_bss_cfg_req")).leaf = true; + entry({"admin", "mtk", "multi_ap", "apply_easymesh_bss_cfg"}, call("apply_easymesh_bss_cfg")).leaf = true; + entry({"admin", "mtk", "multi_ap", "apply_force_ch_switch"}, call("apply_force_ch_switch")).leaf = true; + entry({"admin", "mtk", "multi_ap", "apply_user_preferred_channel"}, call("apply_user_preferred_channel")).leaf = true; + entry({"admin", "mtk", "multi_ap", "trigger_channel_planning_r2"}, call("trigger_channel_planning_r2")).leaf = true; + entry({"admin", "mtk", "multi_ap", "trigger_de_dump"}, call("trigger_de_dump")).leaf = true; + entry({"admin", "mtk", "multi_ap", "get_data_element"}, call("get_data_element")).leaf = true; + entry({"admin", "mtk", "multi_ap", "trigger_channel_scan"}, call("trigger_channel_scan")).leaf = true; + entry({"admin", "mtk", "multi_ap", "get_channel_stats"}, call("get_channel_stats")).leaf = true; + entry({"admin", "mtk", "multi_ap", "get_channel_planning_score"}, call("get_channel_planning_score")).leaf = true; + entry({"admin", "mtk", "multi_ap", "get_user_preferred_channel"}, call("get_user_preferred_channel")).leaf = true; + entry({"admin", "mtk", "multi_ap", "get_sp_rule_list"}, call("get_sp_rule_list")).leaf = true; + entry({"admin", "mtk", "multi_ap", "del_sp_rule"}, call("del_sp_rule")).leaf = true; + entry({"admin", "mtk", "multi_ap", "sp_rule_reorder"}, call("sp_rule_reorder")).leaf = true; + entry({"admin", "mtk", "multi_ap", "sp_rule_move"}, call("sp_rule_move")).leaf = true; + entry({"admin", "mtk", "multi_ap", "add_sp_rule"}, call("sp_rule_add")).leaf = true; + entry({"admin", "mtk", "multi_ap", "sp_config_done"}, call("sp_config_done")).leaf = true; + entry({"admin", "mtk", "multi_ap", "submit_dpp_uri"}, call("submit_dpp_uri")).leaf = true; + entry({"admin", "mtk", "multi_ap", "display_bootstrapping_uri"}, template("admin_mtk/mtk_wifi_map_display_bootstrapping_uri")).leaf = true; + entry({"admin", "mtk", "multi_ap", "start_dpp_onboarding"}, call("start_dpp_onboarding")).leaf = true; + entry({"admin", "mtk", "multi_ap", "generate_dpp_uri"}, call("generate_dpp_uri")).leaf = true; + entry({"admin", "mtk", "multi_ap", "retrive_dpp_uri"}, call("retrive_dpp_uri")).leaf = true; + end +end + +function test() + http.write_json(http.formvalue()) +end + +function exec_reboot() + os.execute("rm -f /tmp/mtk/wifi/reboot_required >/dev/null 2>&1") + os.execute("sync >/dev/null 2>&1") + os.execute("reboot >/dev/null 2>&1") +end + +function get_apply_status() + local ret = {} + + if mtkwifi.is_child_active() then + ret["status"] = "ON_PROGRESS" + elseif mtkwifi.exists("/tmp/mtk/wifi/reboot_required") then + -- If the "wifi restart" command can not re-install the driver; then, it will create + -- "/tmp/mtk/wifi/reboot_required" file to indicate LuCI that the settings will be applied + -- only after reboot of the device. + -- Redirect "Reboot Device" web-page to get consent from the user to reboot the device. + ret["status"] = "REBOOT" + else + ret["status"] = "DONE" + end + http.write_json(ret) +end + +function __mtkwifi_save_profile(cfgs, path, isProfileSettingsAppliedToDriver) + -- Create the applied settings backup file before saving the new profile settings only if it does not exist. + if not mtkwifi.exists(mtkwifi.__profile_applied_settings_path(path)) then + os.execute("cp -f "..path.." "..mtkwifi.__profile_applied_settings_path(path)) + end + if isProfileSettingsAppliedToDriver then + -- It means the some context based profile settings to be saved in DAT file is already applied to the driver. + -- Find the profile settings which are not applied to the driver before saving the new profile settings + local diff = mtkwifi.diff_profile(path) + mtkwifi.save_profile(cfgs, path) + -- If there are any settings which are not applied to the driver, then do NOT copy and WebUI will display the "need reload to apply changes" message + -- Otherwise, copy the new profile settings and WebUI will NOT display the "need reload to apply changes" message + if next(diff) == nil then + os.execute("cp -f "..path.." "..mtkwifi.__profile_applied_settings_path(path)) + end + else + mtkwifi.save_profile(cfgs, path) + end +end + +local __mtkwifi_reload = function (devname) + local wifi_restart = false + local wifi_reload = false + local profiles = mtkwifi.search_dev_and_profile() + + for dev,profile in pairs(profiles) do + if not devname or devname == dev then + local diff = mtkwifi.diff_profile(profile) + local diff_easy = mtkwifi.diff_profile(mtkwifi.__write_easymesh_profile_path(), mtkwifi.__profile_applied_settings_path(mtkwifi.__write_easymesh_profile_path())) + if not next(diff) and not next(diff_easy) then return end + __process_settings_before_apply(dev, profile, diff) + + if diff.BssidNum or diff.WHNAT or diff.E2pAccessMode or diff.HT_RxStream or diff.HT_TxStream or diff.HE_LDPC or diff.WdsEnable then + -- Addition or deletion of a vif requires re-installation of the driver. + -- Change in WHNAT setting also requires re-installation of the driver. + -- Driver will be re-installed by "wifi restart" command. + wifi_restart = true + else + wifi_reload = true + end + + end + end + + if wifi_restart then + os.execute("wifi restart "..(devname or "")) + debug_write("wifi restart "..(devname or "")) + elseif wifi_reload then + os.execute("wifi reload "..(devname or "")) + debug_write("wifi reload "..(devname or "")) + end + + for dev,profile in pairs(profiles) do + if not devname or devname == dev then + -- keep a backup for this commit + -- it will be used in mtkwifi.diff_profile() + os.execute("cp -f "..profile.." "..mtkwifi.__profile_applied_settings_path(profile)) + debug_write("cp -f "..profile.." "..mtkwifi.__profile_applied_settings_path(profile)) + end + end + + if map_help then + local easymesh_applied_path = mtkwifi.__profile_applied_settings_path(mtkwifi.__read_easymesh_profile_path()) + os.execute("cp -f "..mtkwifi.__read_easymesh_profile_path().." "..easymesh_applied_path) + end +end + +function __process_settings_before_apply(devname, profile, diff) + local devs = mtkwifi.get_all_devs() + local cfgs = mtkwifi.load_profile(profile) + __apply_wifi_wpsconf(devs, devname, cfgs, diff) +end + +function chip_cfg(devname) + local profiles = mtkwifi.search_dev_and_profile() + assert(profiles[devname]) + local cfgs = mtkwifi.load_profile(profiles[devname]) + local devs = mtkwifi.get_all_devs() + local dbdc_cfgs = {} + local dev = {} + dev = devs and devs[devname] + + for k,v in pairs(http.formvalue()) do + if type(v) ~= type("") and type(v) ~= type(0) then + nixio.syslog("err", "chip_cfg, invalid value type for "..k..","..type(v)) + elseif string.byte(k) == string.byte("_") then + nixio.syslog("err", "chip_cfg, special: "..k.."="..v) + else + if dev.dbdc == true then + dbdc_cfgs[k] = v or "" + else + cfgs[k] = v or "" + end + end + end + + -- VOW + -- ATC should actually be scattered into each SSID, but I'm just lazy. + if cfgs.VOW_Airtime_Fairness_En then + for i = 1,tonumber(cfgs.BssidNum) do + __atc_tp = http.formvalue("__atc_vif"..i.."_tp") or "0" + __atc_min_tp = http.formvalue("__atc_vif"..i.."_min_tp") or "0" + __atc_max_tp = http.formvalue("__atc_vif"..i.."_max_tp") or "0" + __atc_at = http.formvalue("__atc_vif"..i.."_at") or "0" + __atc_min_at = http.formvalue("__atc_vif"..i.."_min_at") or "0" + __atc_max_at = http.formvalue("__atc_vif"..i.."_max_at") or "0" + + nixio.syslog("info", "ATC.__atc_tp ="..i..__atc_tp ); + nixio.syslog("info", "ATC.__atc_min_tp ="..i..__atc_min_tp ); + nixio.syslog("info", "ATC.__atc_max_tp ="..i..__atc_max_tp ); + nixio.syslog("info", "ATC.__atc_at ="..i..__atc_at ); + nixio.syslog("info", "ATC.__atc_min_at ="..i..__atc_min_at ); + nixio.syslog("info", "ATC.__atc_max_at ="..i..__atc_max_at ); + + dbdc_cfgs.VOW_Rate_Ctrl_En = mtkwifi.token_set(cfgs.VOW_Rate_Ctrl_En, i, __atc_tp) + dbdc_cfgs.VOW_Group_Min_Rate = mtkwifi.token_set(cfgs.VOW_Group_Min_Rate, i, __atc_min_tp) + dbdc_cfgs.VOW_Group_Max_Rate = mtkwifi.token_set(cfgs.VOW_Group_Max_Rate, i, __atc_max_tp) + + dbdc_cfgs.VOW_Airtime_Ctrl_En = mtkwifi.token_set(cfgs.VOW_Airtime_Ctrl_En, i, __atc_at) + dbdc_cfgs.VOW_Group_Min_Ratio = mtkwifi.token_set(cfgs.VOW_Group_Min_Ratio, i, __atc_min_at) + dbdc_cfgs.VOW_Group_Max_Ratiio = mtkwifi.token_set(cfgs.VOW_Group_Max_Ratio, i, __atc_max_at) + + cfgs.VOW_Rate_Ctrl_En = mtkwifi.token_set(cfgs.VOW_Rate_Ctrl_En, i, __atc_tp) + cfgs.VOW_Group_Min_Rate = mtkwifi.token_set(cfgs.VOW_Group_Min_Rate, i, __atc_min_tp) + cfgs.VOW_Group_Max_Rate = mtkwifi.token_set(cfgs.VOW_Group_Max_Rate, i, __atc_max_tp) + + cfgs.VOW_Airtime_Ctrl_En = mtkwifi.token_set(cfgs.VOW_Airtime_Ctrl_En, i, __atc_at) + cfgs.VOW_Group_Min_Ratio = mtkwifi.token_set(cfgs.VOW_Group_Min_Ratio, i, __atc_min_at) + cfgs.VOW_Group_Max_Ratio = mtkwifi.token_set(cfgs.VOW_Group_Max_Ratio, i, __atc_max_at) + + end + + dbdc_cfgs.VOW_RX_En = http.formvalue("VOW_RX_En") or "0" + cfgs.VOW_RX_En = http.formvalue("VOW_RX_En") or "0" + end + + if dev.dbdc == true then + for devname, profile in pairs(profiles) do + __mtkwifi_save_profile(dbdc_cfgs, profile, false) + end + else + __mtkwifi_save_profile(cfgs, profiles[devname], false) + end + + if http.formvalue("__apply") then + mtkwifi.__run_in_child_env(__mtkwifi_reload, devname) + local url_to_visit_after_reload = luci.dispatcher.build_url("admin", "mtk", "wifi", "chip_cfg_view",devname) + luci.http.redirect(luci.dispatcher.build_url("admin", "mtk", "wifi", "loading",url_to_visit_after_reload)) + else + luci.http.redirect(luci.dispatcher.build_url("admin", "mtk", "wifi", "chip_cfg_view",devname)) + end + +end + +function dev_cfg(devname) + local profiles = mtkwifi.search_dev_and_profile() + assert(profiles[devname]) + local cfgs = mtkwifi.load_profile(profiles[devname]) + + for k,v in pairs(http.formvalue()) do + if type(v) ~= type("") and type(v) ~= type(0) then + nixio.syslog("err", "dev_cfg, invalid value type for "..k..","..type(v)) + elseif string.byte(k) == string.byte("_") then + nixio.syslog("err", "dev_cfg, special: "..k.."="..v) + else + cfgs[k] = v or "" + end + end + + if cfgs.Channel == "0" then -- Auto Channel Select + cfgs.AutoChannelSelect = "3" + else + cfgs.AutoChannelSelect = "0" + end + + if http.formvalue("__bw") == "20" then + cfgs.HT_BW = 0 + cfgs.VHT_BW = 0 + elseif http.formvalue("__bw") == "40" then + cfgs.HT_BW = 1 + cfgs.VHT_BW = 0 + cfgs.HT_BSSCoexistence = 0 + elseif http.formvalue("__bw") == "60" then + cfgs.HT_BW = 1 + cfgs.VHT_BW = 0 + cfgs.HT_BSSCoexistence = 1 + elseif http.formvalue("__bw") == "80" then + cfgs.HT_BW = 1 + cfgs.VHT_BW = 1 + elseif http.formvalue("__bw") == "160" then + cfgs.HT_BW = 1 + cfgs.VHT_BW = 2 + elseif http.formvalue("__bw") == "161" then + cfgs.HT_BW = 1 + cfgs.VHT_BW = 3 + cfgs.VHT_Sec80_Channel = http.formvalue("VHT_Sec80_Channel") or "" + end + + if mtkwifi.band(string.split(cfgs.WirelessMode,";")[1]) == "5G" or mtkwifi.band(cfgs.WirelessMode) == "6G" then + cfgs.CountryRegionABand = http.formvalue("__cr"); + else + cfgs.CountryRegion = http.formvalue("__cr"); + end + + if http.formvalue("TxPower") then + local txpower = tonumber(http.formvalue("TxPower")) + if txpower < 100 then + cfgs.PERCENTAGEenable=1 + else + cfgs.PERCENTAGEenable=0 + end + end + + local IndividualTWTSupport = tonumber(http.formvalue("IndividualTWTSupport")) + if IndividualTWTSupport == 0 then + cfgs.TWTResponder=0 + cfgs.TWTRequired=0 + elseif IndividualTWTSupport == 1 then + cfgs.TWTResponder=1 + cfgs.TWTRequired=0 + else + cfgs.TWTResponder=1 + cfgs.TWTRequired=1 + end + + local mimo = http.formvalue("__mimo") + if mimo == "0" then + cfgs.ETxBfEnCond=1 + cfgs.MUTxRxEnable=0 + cfgs.ITxBfEn=0 + elseif mimo == "1" then + cfgs.ETxBfEnCond=0 + cfgs.MUTxRxEnable=0 + cfgs.ITxBfEn=1 + elseif mimo == "2" then + cfgs.ETxBfEnCond=1 + cfgs.MUTxRxEnable=0 + cfgs.ITxBfEn=1 + elseif mimo == "3" then + cfgs.ETxBfEnCond=1 + if tonumber(cfgs.ApCliEnable) == 1 then + cfgs.MUTxRxEnable=3 + else + cfgs.MUTxRxEnable=1 + end + cfgs.ITxBfEn=0 + elseif mimo == "4" then + cfgs.ETxBfEnCond=1 + if tonumber(cfgs.ApCliEnable) == 1 then + cfgs.MUTxRxEnable=3 + else + cfgs.MUTxRxEnable=1 + end + cfgs.ITxBfEn=1 + else + cfgs.ETxBfEnCond=0 + cfgs.MUTxRxEnable=0 + cfgs.ITxBfEn=0 + end + +-- if cfgs.ApCliEnable == "1" then +-- cfgs.Channel = http.formvalue("__apcli_channel") +-- end + + -- WDS + -- http.write_json(http.formvalue()) + __mtkwifi_save_profile(cfgs, profiles[devname], false) + + if http.formvalue("__apply") then + mtkwifi.__run_in_child_env(__mtkwifi_reload, devname) + local url_to_visit_after_reload = luci.dispatcher.build_url("admin", "mtk", "wifi", "dev_cfg_view",devname) + luci.http.redirect(luci.dispatcher.build_url("admin", "mtk", "wifi", "loading",url_to_visit_after_reload)) + else + luci.http.redirect(luci.dispatcher.build_url("admin", "mtk", "wifi", "dev_cfg_view",devname)) + end +end + +function dev_cfg_raw(devname) + -- http.write_json(http.formvalue()) + local profiles = mtkwifi.search_dev_and_profile() + assert(profiles[devname]) + + local raw = http.formvalue("raw") + raw = string.gsub(raw, "\r\n", "\n") + local cfgs = mtkwifi.load_profile(nil, raw) + __mtkwifi_save_profile(cfgs, profiles[devname], false) + + luci.http.redirect(luci.dispatcher.build_url("admin", "mtk", "wifi", "dev_cfg_view", devname)) +end + +function __delete_mbss_para(cfgs, vif_idx) + debug_write(vif_idx) + cfgs["WPAPSK"..vif_idx]="" + cfgs["Key1Type"]=mtkwifi.token_set(cfgs["Key1Type"],vif_idx,"") + cfgs["Key2Type"]=mtkwifi.token_set(cfgs["Key2Type"],vif_idx,"") + cfgs["Key3Type"]=mtkwifi.token_set(cfgs["Key3Type"],vif_idx,"") + cfgs["Key4Type"]=mtkwifi.token_set(cfgs["Key4Type"],vif_idx,"") + cfgs["RADIUS_Server"]=mtkwifi.token_set(cfgs["RADIUS_Server"],vif_idx,"") + cfgs["RADIUS_Port"]=mtkwifi.token_set(cfgs["RADIUS_Port"],vif_idx,"") + cfgs["RADIUS_Key"..vif_idx]="" + cfgs["DefaultKeyID"]=mtkwifi.token_set(cfgs["DefaultKeyID"],vif_idx,"") + cfgs["IEEE8021X"]=mtkwifi.token_set(cfgs["IEEE8021X"],vif_idx,"") + cfgs["WscConfMode"]=mtkwifi.token_set(cfgs["WscConfMode"],vif_idx,"") + cfgs["PreAuth"]=mtkwifi.token_set(cfgs["PreAuth"],vif_idx,"") + cfgs["HT_STBC"] = mtkwifi.token_set(cfgs["HT_STBC"],vif_idx,"") + cfgs["HT_LDPC"] = mtkwifi.token_set(cfgs["HT_LDPC"],vif_idx,"") + cfgs["VHT_STBC"] = mtkwifi.token_set(cfgs["VHT_STBC"],vif_idx,"") + cfgs["VHT_LDPC"] = mtkwifi.token_set(cfgs["VHT_LDPC"],vif_idx,"") + cfgs["HideSSID"]=mtkwifi.token_set(cfgs["HideSSID"],vif_idx,"") + cfgs["NoForwarding"]=mtkwifi.token_set(cfgs["NoForwarding"],vif_idx,"") + cfgs["WmmCapable"]=mtkwifi.token_set(cfgs["WmmCapable"],vif_idx,"") + cfgs["TxRate"]=mtkwifi.token_set(cfgs["TxRate"],vif_idx,"") + cfgs["RekeyInterval"]=mtkwifi.token_set(cfgs["RekeyInterval"],vif_idx,"") + cfgs["AuthMode"]=mtkwifi.token_set(cfgs["AuthMode"],vif_idx,"") + cfgs["EncrypType"]=mtkwifi.token_set(cfgs["EncrypType"],vif_idx,"") + cfgs["session_timeout_interval"]=mtkwifi.token_set(cfgs["session_timeout_interval"],vif_idx,"") + cfgs["WscModeOption"]=mtkwifi.token_set(cfgs["WscModeOption"],vif_idx,"") + cfgs["RekeyMethod"]=mtkwifi.token_set(cfgs["RekeyMethod"],vif_idx,"") + cfgs["PMFMFPC"] = mtkwifi.token_set(cfgs["PMFMFPC"],vif_idx,"") + cfgs["PMFMFPR"] = mtkwifi.token_set(cfgs["PMFMFPR"],vif_idx,"") + cfgs["PMFSHA256"] = mtkwifi.token_set(cfgs["PMFSHA256"],vif_idx,"") + cfgs["PMKCachePeriod"] = mtkwifi.token_set(cfgs["PMKCachePeriod"],vif_idx,"") + cfgs["Wapiifname"] = mtkwifi.token_set(cfgs["Wapiifname"],vif_idx,"") + cfgs["RRMEnable"] = mtkwifi.token_set(cfgs["RRMEnable"],vif_idx,"") + cfgs["DLSCapable"] = mtkwifi.token_set(cfgs["DLSCapable"],vif_idx,"") + cfgs["APSDCapable"] = mtkwifi.token_set(cfgs["APSDCapable"],vif_idx,"") + cfgs["FragThreshold"] = mtkwifi.token_set(cfgs["FragThreshold"],vif_idx,"") + cfgs["RTSThreshold"] = mtkwifi.token_set(cfgs["RTSThreshold"],vif_idx,"") + cfgs["VHT_SGI"] = mtkwifi.token_set(cfgs["VHT_SGI"],vif_idx,"") + cfgs["VHT_BW_SIGNAL"] = mtkwifi.token_set(cfgs["VHT_BW_SIGNAL"],vif_idx,"") + cfgs["HT_PROTECT"] = mtkwifi.token_set(cfgs["HT_PROTECT"],vif_idx,"") + cfgs["HT_GI"] = mtkwifi.token_set(cfgs["HT_GI"],vif_idx,"") + cfgs["HT_OpMode"] = mtkwifi.token_set(cfgs["HT_OpMode"],vif_idx,"") + cfgs["HT_TxStream"] = mtkwifi.token_set(cfgs["HT_TxStream"],vif_idx,"") + cfgs["HT_RxStream"] = mtkwifi.token_set(cfgs["HT_RxStream"],vif_idx,"") + cfgs["HT_AMSDU"] = mtkwifi.token_set(cfgs["HT_AMSDU"],vif_idx,"") + cfgs["HT_AutoBA"] = mtkwifi.token_set(cfgs["HT_AutoBA"],vif_idx,"") + cfgs["IgmpSnEnable"] = mtkwifi.token_set(cfgs["IgmpSnEnable"],vif_idx,"") + cfgs["WirelessMode"] = mtkwifi.token_set(cfgs["WirelessMode"],vif_idx,"") + cfgs["WdsEnable"] = mtkwifi.token_set(cfgs["WdsEnable"],vif_idx,"") + cfgs["MuOfdmaDlEnable"] = mtkwifi.token_set(cfgs["MuOfdmaDlEnable"],vif_idx,"") + cfgs["MuOfdmaUlEnable"] = mtkwifi.token_set(cfgs["MuOfdmaUlEnable"],vif_idx,"") + cfgs["MuMimoDlEnable"] = mtkwifi.token_set(cfgs["MuMimoDlEnable"],vif_idx,"") + cfgs["MuMimoUlEnable"] = mtkwifi.token_set(cfgs["MuMimoUlEnable"],vif_idx,"") + +end + +function vif_del(dev, vif) + debug_write("vif_del("..dev..vif..")") + local devname,vifname = dev, vif + debug_write("devname="..devname) + debug_write("vifname="..vifname) + local devs = mtkwifi.get_all_devs() + local idx = devs[devname]["vifs"][vifname].vifidx -- or tonumber(string.match(vifname, "%d+")) + 1 + debug_write("idx="..idx, devname, vifname) + local profile = devs[devname].profile + assert(profile) + if idx and tonumber(idx) >= 0 then + local cfgs = mtkwifi.load_profile(profile) + __delete_mbss_para(cfgs, idx) + if cfgs then + debug_write("ssid"..idx.."="..cfgs["SSID"..idx].."
") + cfgs["SSID"..idx] = "" + debug_write("ssid"..idx.."="..cfgs["SSID"..idx].."
") + debug_write("wpapsk"..idx.."="..cfgs["WPAPSK"..idx].."
") + cfgs["WPAPSK"..idx] = "" + local ssidlist = {} + local j = 1 + for i = 1,16 do + if cfgs["SSID"..i] ~= "" then + ssidlist[j] = cfgs["SSID"..i] + j = j + 1 + end + end + for i,v in ipairs(ssidlist) do + debug_write("ssidlist"..i.."="..v) + end + debug_write("cfgs.BssidNum="..cfgs.BssidNum.." #ssidlist="..#ssidlist) + assert(tonumber(cfgs.BssidNum) == #ssidlist + 1, "BssidNum="..cfgs.BssidNum.." SSIDlist="..#ssidlist..", BssidNum count does not match with SSIDlist count.") + cfgs.BssidNum = #ssidlist + for i = 1,16 do + if i <= cfgs.BssidNum then + cfgs["SSID"..i] = ssidlist[i] + elseif cfgs["SSID"..i] then + cfgs["SSID"..i] = "" + end + end + + __mtkwifi_save_profile(cfgs, profile, false) + else + debug_write(profile.." cannot be found!") + end + end + luci.http.redirect(luci.dispatcher.build_url("admin", "mtk", "wifi")) +end + +function vif_disable(iface) + os.execute("ifconfig "..iface.." down") + luci.http.redirect(luci.dispatcher.build_url("admin", "mtk", "wifi")) +end + +function vif_enable(iface) + os.execute("ifconfig "..iface.." up") + luci.http.redirect(luci.dispatcher.build_url("admin", "mtk", "wifi")) +end + + +--[[ +-- security config in mtk wifi is quite complicated! +-- cfgs listed below are attached with vif and combined like "0;0;0;0". They need specicial treatment. + TxRate, WmmCapable, NoForwarding, + HideSSID, IEEE8021X, PreAuth, + AuthMode, EncrypType, RekeyMethod, + RekeyInterval, PMKCachePeriod, + DefaultKeyId, Key{n}Type, HT_EXTCHA, + RADIUS_Server, RADIUS_Port, +]] + +local function conf_wep_keys(cfgs,vifidx) + cfgs.DefaultKeyID = mtkwifi.token_set(cfgs.DefaultKeyID, vifidx, http.formvalue("__DefaultKeyID") or 1) + cfgs["Key1Str"..vifidx] = http.formvalue("Key1Str"..vifidx) + cfgs["Key2Str"..vifidx] = http.formvalue("Key2Str"..vifidx) + cfgs["Key3Str"..vifidx] = http.formvalue("Key3Str"..vifidx) + cfgs["Key4Str"..vifidx] = http.formvalue("Key4Str"..vifidx) + + cfgs["Key1Type"]=mtkwifi.token_set(cfgs["Key1Type"],vifidx, http.formvalue("WEP1Type"..vifidx)) + cfgs["Key2Type"]=mtkwifi.token_set(cfgs["Key2Type"],vifidx, http.formvalue("WEP2Type"..vifidx)) + cfgs["Key3Type"]=mtkwifi.token_set(cfgs["Key3Type"],vifidx, http.formvalue("WEP3Type"..vifidx)) + cfgs["Key4Type"]=mtkwifi.token_set(cfgs["Key4Type"],vifidx, http.formvalue("WEP4Type"..vifidx)) + + return cfgs +end + +local function __security_cfg(cfgs, vif_idx) + debug_write("__security_cfg, before, HideSSID="..tostring(cfgs.HideSSID)) + debug_write("__security_cfg, before, NoForwarding="..tostring(cfgs.NoForwarding)) + debug_write("__security_cfg, before, WmmCapable="..tostring(cfgs.WmmCapable)) + debug_write("__security_cfg, before, TxRate="..tostring(cfgs.TxRate)) + debug_write("__security_cfg, before, RekeyInterval="..tostring(cfgs.RekeyInterval)) + debug_write("__security_cfg, before, AuthMode="..tostring(cfgs.AuthMode)) + debug_write("__security_cfg, before, EncrypType="..tostring(cfgs.EncrypType)) + debug_write("__security_cfg, before, WscModeOption="..tostring(cfgs.WscModeOption)) + debug_write("__security_cfg, before, RekeyMethod="..tostring(cfgs.RekeyMethod)) + debug_write("__security_cfg, before, IEEE8021X="..tostring(cfgs.IEEE8021X)) + debug_write("__security_cfg, before, DefaultKeyID="..tostring(cfgs.DefaultKeyID)) + debug_write("__security_cfg, before, PMFMFPC="..tostring(cfgs.PMFMFPC)) + debug_write("__security_cfg, before, PMFMFPR="..tostring(cfgs.PMFMFPR)) + debug_write("__security_cfg, before, PMFSHA256="..tostring(cfgs.PMFSHA256)) + debug_write("__security_cfg, before, RADIUS_Server="..tostring(cfgs.RADIUS_Server)) + debug_write("__security_cfg, before, RADIUS_Port="..tostring(cfgs.RADIUS_Port)) + debug_write("__security_cfg, before, session_timeout_interval="..tostring(cfgs.session_timeout_interval)) + debug_write("__security_cfg, before, PMKCachePeriod="..tostring(cfgs.PMKCachePeriod)) + debug_write("__security_cfg, before, PreAuth="..tostring(cfgs.PreAuth)) + debug_write("__security_cfg, before, Wapiifname="..tostring(cfgs.Wapiifname)) + + -- Reset/Clear all necessary settings here. Later, these settings will be set as per AuthMode. + cfgs.RekeyMethod = mtkwifi.token_set(cfgs.RekeyMethod, vif_idx, "DISABLE") + cfgs.IEEE8021X = mtkwifi.token_set(cfgs.IEEE8021X, vif_idx, "0") + cfgs.PMFMFPC = mtkwifi.token_set(cfgs.PMFMFPC, vif_idx, "0") + cfgs.PMFMFPR = mtkwifi.token_set(cfgs.PMFMFPR, vif_idx, "0") + cfgs.PMFSHA256 = mtkwifi.token_set(cfgs.PMFSHA256, vif_idx, "0") + + -- Update the settings which are not dependent on AuthMode + cfgs.HideSSID = mtkwifi.token_set(cfgs.HideSSID, vif_idx, http.formvalue("__hidessid") or "0") + cfgs.NoForwarding = mtkwifi.token_set(cfgs.NoForwarding, vif_idx, http.formvalue("__noforwarding") or "0") + cfgs.WmmCapable = mtkwifi.token_set(cfgs.WmmCapable, vif_idx, http.formvalue("__wmmcapable") or "0") + cfgs.TxRate = mtkwifi.token_set(cfgs.TxRate, vif_idx, http.formvalue("__txrate") or "0"); + cfgs.RekeyInterval = mtkwifi.token_set(cfgs.RekeyInterval, vif_idx, http.formvalue("__rekeyinterval") or "0"); + + local __authmode = http.formvalue("__authmode") or "Disable" + cfgs.AuthMode = mtkwifi.token_set(cfgs.AuthMode, vif_idx, __authmode) + + if __authmode == "Disable" then + cfgs.AuthMode = mtkwifi.token_set(cfgs.AuthMode, vif_idx, "OPEN") + cfgs.EncrypType = mtkwifi.token_set(cfgs.EncrypType, vif_idx, "NONE") + + elseif __authmode == "OPEN" or __authmode == "SHARED" or __authmode == "WEPAUTO" then + cfgs.WscModeOption = "0" + cfgs.EncrypType = mtkwifi.token_set(cfgs.EncrypType, vif_idx, "WEP") + cfgs = conf_wep_keys(cfgs,vif_idx) + + elseif __authmode == "Enhanced Open" then + cfgs.AuthMode = mtkwifi.token_set(cfgs.AuthMode, vif_idx, "OWE") + cfgs.EncrypType = mtkwifi.token_set(cfgs.EncrypType, vif_idx, "AES") + cfgs.PMFMFPC = mtkwifi.token_set(cfgs.PMFMFPC, vif_idx, "1") + cfgs.PMFMFPR = mtkwifi.token_set(cfgs.PMFMFPR, vif_idx, "1") + cfgs.PMFSHA256 = mtkwifi.token_set(cfgs.PMFSHA256, vif_idx, "0") + + elseif __authmode == "WPAPSK" then + cfgs.EncrypType = mtkwifi.token_set(cfgs.EncrypType, vif_idx, http.formvalue("__encrypttype") or "AES") + cfgs.RekeyMethod = mtkwifi.token_set(cfgs.RekeyMethod, vif_idx, "TIME") + + elseif __authmode == "WPAPSKWPA2PSK" then + cfgs.EncrypType = mtkwifi.token_set(cfgs.EncrypType, vif_idx, http.formvalue("__encrypttype") or "AES") + cfgs.RekeyMethod = mtkwifi.token_set(cfgs.RekeyMethod, vif_idx, "TIME") + cfgs.WpaMixPairCipher = "WPA_TKIP_WPA2_AES" + + elseif __authmode == "WPA2PSK" then + cfgs.EncrypType = mtkwifi.token_set(cfgs.EncrypType, vif_idx, http.formvalue("__encrypttype") or "AES") + cfgs.RekeyMethod = mtkwifi.token_set(cfgs.RekeyMethod, vif_idx, "TIME") + -- for DOT11W_PMF_SUPPORT + cfgs.PMFMFPC = mtkwifi.token_set(cfgs.PMFMFPC, vif_idx, http.formvalue("__pmfmfpc") or "0") + cfgs.PMFMFPR = mtkwifi.token_set(cfgs.PMFMFPR, vif_idx, http.formvalue("__pmfmfpr") or "0") + cfgs.PMFSHA256 = mtkwifi.token_set(cfgs.PMFSHA256, vif_idx, http.formvalue("__pmfsha256") or "0") + + elseif __authmode == "WPA3PSK" then + cfgs.EncrypType = mtkwifi.token_set(cfgs.EncrypType, vif_idx, "AES") + cfgs.RekeyMethod = mtkwifi.token_set(cfgs.RekeyMethod, vif_idx, "TIME") + -- for DOT11W_PMF_SUPPORT + cfgs.PMFMFPC = mtkwifi.token_set(cfgs.PMFMFPC, vif_idx, "1") + cfgs.PMFMFPR = mtkwifi.token_set(cfgs.PMFMFPR, vif_idx, "1") + cfgs.PMFSHA256 = mtkwifi.token_set(cfgs.PMFSHA256, vif_idx, "0") + + elseif __authmode == "WPA2PSKWPA3PSK" then + cfgs.EncrypType = mtkwifi.token_set(cfgs.EncrypType, vif_idx, "AES") + cfgs.RekeyMethod = mtkwifi.token_set(cfgs.RekeyMethod, vif_idx, "TIME") + -- for DOT11W_PMF_SUPPORT + cfgs.PMFMFPC = mtkwifi.token_set(cfgs.PMFMFPC, vif_idx, "1") + cfgs.PMFMFPR = mtkwifi.token_set(cfgs.PMFMFPR, vif_idx, "0") + cfgs.PMFSHA256 = mtkwifi.token_set(cfgs.PMFSHA256, vif_idx, "0") + + elseif __authmode == "WPA2" then + cfgs.EncrypType = mtkwifi.token_set(cfgs.EncrypType, vif_idx, http.formvalue("__encrypttype") or "AES") + cfgs.RekeyMethod = mtkwifi.token_set(cfgs.RekeyMethod, vif_idx, "TIME") + cfgs.RADIUS_Server = mtkwifi.token_set(cfgs.RADIUS_Server, vif_idx, http.formvalue("__radius_server") or "0") + cfgs.RADIUS_Port = mtkwifi.token_set(cfgs.RADIUS_Port, vif_idx, http.formvalue("__radius_port") or "0") + cfgs.session_timeout_interval = mtkwifi.token_set(cfgs.session_timeout_interval, vif_idx, http.formvalue("__session_timeout_interval") or "0") + cfgs.PMKCachePeriod = mtkwifi.token_set(cfgs.PMKCachePeriod, vif_idx, http.formvalue("__pmkcacheperiod") or "0") + cfgs.PreAuth = mtkwifi.token_set(cfgs.PreAuth, vif_idx, http.formvalue("__preauth") or "0") + -- for DOT11W_PMF_SUPPORT + cfgs.PMFMFPC = mtkwifi.token_set(cfgs.PMFMFPC, vif_idx, http.formvalue("__pmfmfpc") or "0") + cfgs.PMFMFPR = mtkwifi.token_set(cfgs.PMFMFPR, vif_idx, http.formvalue("__pmfmfpr") or "0") + cfgs.PMFSHA256 = mtkwifi.token_set(cfgs.PMFSHA256, vif_idx, http.formvalue("__pmfsha256") or "0") + + elseif __authmode == "WPA3" then + cfgs.EncrypType = mtkwifi.token_set(cfgs.EncrypType, vif_idx, "AES") + cfgs.RekeyMethod = mtkwifi.token_set(cfgs.RekeyMethod, vif_idx, "TIME") + cfgs.RADIUS_Server = mtkwifi.token_set(cfgs.RADIUS_Server, vif_idx, http.formvalue("__radius_server") or "0") + cfgs.RADIUS_Port = mtkwifi.token_set(cfgs.RADIUS_Port, vif_idx, http.formvalue("__radius_port") or "0") + cfgs.session_timeout_interval = mtkwifi.token_set(cfgs.session_timeout_interval, vif_idx, http.formvalue("__session_timeout_interval") or "0") + cfgs.PMKCachePeriod = mtkwifi.token_set(cfgs.PMKCachePeriod, vif_idx, http.formvalue("__pmkcacheperiod") or "0") + cfgs.PreAuth = mtkwifi.token_set(cfgs.PreAuth, vif_idx, http.formvalue("__preauth") or "0") + -- for DOT11W_PMF_SUPPORT + cfgs.PMFMFPC = mtkwifi.token_set(cfgs.PMFMFPC, vif_idx, "1") + cfgs.PMFMFPR = mtkwifi.token_set(cfgs.PMFMFPR, vif_idx, "1") + cfgs.PMFSHA256 = mtkwifi.token_set(cfgs.PMFSHA256, vif_idx, "0") + + elseif __authmode == "WPA3-192-bit" then + cfgs.AuthMode = mtkwifi.token_set(cfgs.AuthMode, vif_idx, "WPA3-192") + cfgs.EncrypType = mtkwifi.token_set(cfgs.EncrypType, vif_idx, "GCMP256") + cfgs.RekeyMethod = mtkwifi.token_set(cfgs.RekeyMethod, vif_idx, "TIME") + cfgs.RADIUS_Server = mtkwifi.token_set(cfgs.RADIUS_Server, vif_idx, http.formvalue("__radius_server") or "0") + cfgs.RADIUS_Port = mtkwifi.token_set(cfgs.RADIUS_Port, vif_idx, http.formvalue("__radius_port") or "0") + cfgs.session_timeout_interval = mtkwifi.token_set(cfgs.session_timeout_interval, vif_idx, http.formvalue("__session_timeout_interval") or "0") + cfgs.PMKCachePeriod = mtkwifi.token_set(cfgs.PMKCachePeriod, vif_idx, http.formvalue("__pmkcacheperiod") or "0") + cfgs.PreAuth = mtkwifi.token_set(cfgs.PreAuth, vif_idx, http.formvalue("__preauth") or "0") + -- for DOT11W_PMF_SUPPORT + cfgs.PMFMFPC = mtkwifi.token_set(cfgs.PMFMFPC, vif_idx, "1") + cfgs.PMFMFPR = mtkwifi.token_set(cfgs.PMFMFPR, vif_idx, "1") + cfgs.PMFSHA256 = mtkwifi.token_set(cfgs.PMFSHA256, vif_idx, "0") + + elseif __authmode == "WPA1WPA2" then + cfgs.EncrypType = mtkwifi.token_set(cfgs.EncrypType, vif_idx, http.formvalue("__encrypttype") or "AES") + cfgs.RekeyMethod = mtkwifi.token_set(cfgs.RekeyMethod, vif_idx, "TIME") + cfgs.RADIUS_Server = mtkwifi.token_set(cfgs.RADIUS_Server, vif_idx, http.formvalue("__radius_server") or "0") + cfgs.RADIUS_Port = mtkwifi.token_set(cfgs.RADIUS_Port, vif_idx, http.formvalue("__radius_port") or "1812") + cfgs.session_timeout_interval = mtkwifi.token_set(cfgs.session_timeout_interval, vif_idx, http.formvalue("__session_timeout_interval") or "0") + cfgs.PMKCachePeriod = mtkwifi.token_set(cfgs.PMKCachePeriod, vif_idx, http.formvalue("__pmkcacheperiod") or "0") + cfgs.PreAuth = mtkwifi.token_set(cfgs.PreAuth, vif_idx, http.formvalue("__preauth") or "0") + + elseif __authmode == "IEEE8021X" then + cfgs.AuthMode = mtkwifi.token_set(cfgs.AuthMode, vif_idx, "OPEN") + cfgs.EncrypType = mtkwifi.token_set(cfgs.EncrypType, vif_idx, http.formvalue("__8021x_wep") and "WEP" or "NONE") + cfgs.IEEE8021X = mtkwifi.token_set(cfgs.IEEE8021X, vif_idx, "1") + cfgs.RADIUS_Server = mtkwifi.token_set(cfgs.RADIUS_Server, vif_idx, http.formvalue("__radius_server") or "0") + cfgs.RADIUS_Port = mtkwifi.token_set(cfgs.RADIUS_Port, vif_idx, http.formvalue("__radius_port") or "0") + cfgs.session_timeout_interval = mtkwifi.token_set(cfgs.session_timeout_interval, vif_idx, http.formvalue("__session_timeout_interval") or "0") + + elseif __authmode == "WAICERT" then + cfgs.EncrypType = mtkwifi.token_set(cfgs.EncrypType, vif_idx, "SMS4") + cfgs.Wapiifname = mtkwifi.token_set(cfgs.Wapiifname, vif_idx, "br-lan") + -- cfgs.wapicert_asipaddr + -- cfgs.WapiAsPort + -- cfgs.wapicert_ascert + -- cfgs.wapicert_usercert + + elseif __authmode == "WAIPSK" then + cfgs.EncrypType = mtkwifi.token_set(cfgs.EncrypType, vif_idx, "SMS4") + -- cfgs.wapipsk_keytype + -- cfgs.wapipsk_prekey + end + + debug_write("__security_cfg, after, HideSSID="..tostring(cfgs.HideSSID)) + debug_write("__security_cfg, after, NoForwarding="..tostring(cfgs.NoForwarding)) + debug_write("__security_cfg, after, WmmCapable="..tostring(cfgs.WmmCapable)) + debug_write("__security_cfg, after, TxRate="..tostring(cfgs.TxRate)) + debug_write("__security_cfg, after, RekeyInterval="..tostring(cfgs.RekeyInterval)) + debug_write("__security_cfg, after, AuthMode="..tostring(cfgs.AuthMode)) + debug_write("__security_cfg, after, EncrypType="..tostring(cfgs.EncrypType)) + debug_write("__security_cfg, after, WscModeOption="..tostring(cfgs.WscModeOption)) + debug_write("__security_cfg, after, RekeyMethod="..tostring(cfgs.RekeyMethod)) + debug_write("__security_cfg, after, IEEE8021X="..tostring(cfgs.IEEE8021X)) + debug_write("__security_cfg, after, DefaultKeyID="..tostring(cfgs.DefaultKeyID)) + debug_write("__security_cfg, after, PMFMFPC="..tostring(cfgs.PMFMFPC)) + debug_write("__security_cfg, after, PMFMFPR="..tostring(cfgs.PMFMFPR)) + debug_write("__security_cfg, after, PMFSHA256="..tostring(cfgs.PMFSHA256)) + debug_write("__security_cfg, after, RADIUS_Server="..tostring(cfgs.RADIUS_Server)) + debug_write("__security_cfg, after, RADIUS_Port="..tostring(cfgs.RADIUS_Port)) + debug_write("__security_cfg, after, session_timeout_interval="..tostring(cfgs.session_timeout_interval)) + debug_write("__security_cfg, after, PMKCachePeriod="..tostring(cfgs.PMKCachePeriod)) + debug_write("__security_cfg, after, PreAuth="..tostring(cfgs.PreAuth)) + debug_write("__security_cfg, after, Wapiifname="..tostring(cfgs.Wapiifname)) +end + +function initialize_multiBssParameters(cfgs,vif_idx) + cfgs["WPAPSK"..vif_idx]="fixture1" + cfgs["Key1Type"]=mtkwifi.token_set(cfgs["Key1Type"],vif_idx,"0") + cfgs["Key2Type"]=mtkwifi.token_set(cfgs["Key2Type"],vif_idx,"0") + cfgs["Key3Type"]=mtkwifi.token_set(cfgs["Key3Type"],vif_idx,"0") + cfgs["Key4Type"]=mtkwifi.token_set(cfgs["Key4Type"],vif_idx,"0") + cfgs["RADIUS_Server"]=mtkwifi.token_set(cfgs["RADIUS_Server"],vif_idx,"0") + cfgs["RADIUS_Port"]=mtkwifi.token_set(cfgs["RADIUS_Port"],vif_idx,"1812") + cfgs["RADIUS_Key"..vif_idx]="ralink" + cfgs["DefaultKeyID"]=mtkwifi.token_set(cfgs["DefaultKeyID"],vif_idx,"1") + cfgs["IEEE8021X"]=mtkwifi.token_set(cfgs["IEEE8021X"],vif_idx,"0") + cfgs["WscConfMode"]=mtkwifi.token_set(cfgs["WscConfMode"],vif_idx,"0") + cfgs["PreAuth"]=mtkwifi.token_set(cfgs["PreAuth"],vif_idx,"0") + return cfgs +end + +function __wps_ap_pbc_start_all(ifname) + os.execute("iwpriv "..ifname.." set WscMode=2"); + os.execute("iwpriv "..ifname.." set WscGetConf=1"); +end + +function __wps_ap_pin_start_all(ifname, pincode) + os.execute("iwpriv "..ifname.." set WscMode=1") + os.execute("iwpriv "..ifname.." set WscPinCode="..pincode) + os.execute("iwpriv "..ifname.." set WscGetConf=1") +end + +local __restart_miniupnpd = function (devName,ifName) + if pcall(require, "wifi_services") then + -- OpenWRT + assert(type(devName) == type("")) + assert(type(ifName) == type("")) + local wifi_service = require("wifi_services") + debug_write("Call miniupnpd_chk() of wifi_services module") + miniupnpd_chk(devName,ifName,wifi_service) + else + -- LSDK + debug_write("Execute miniupnpd.sh script!") + os.execute("miniupnpd.sh init") + end +end + +local __restart_hotspot_daemon = function () + os.execute("killall hs") + os.execute("rm -rf /tmp/hotspot*") + -- As this function is executed in child environment, there is no need to spawn it using fork-exec method. + os.execute("hs -d 1 -v 2 -f/etc_ro/hotspot_ap.conf") +end + +local __restart_8021x = function (devName,ifName) + if pcall(require, "wifi_services") then + -- OpenWRT + assert(type(devName) == type("")) + assert(type(ifName) == type("")) + local ifPrefix = string.match(ifName,"([a-z]+)") + assert(type(ifPrefix) == type("")) + local wifi_service = require("wifi_services") + debug_write("Call d8021xd_chk() of wifi_services module") + d8021xd_chk(devName,ifPrefix,ifPrefix.."0",true) + else + -- LSDK + debug_write("Call mtkwifi.restart_8021x()") + mtkwifi.restart_8021x(devName) + end +end + +--Landen: CP functions from wireless for Ajax, reloading page is not required when DBDC ssid changed. +local __restart_all_daemons = function (devName,ifName) + __restart_8021x(devName,ifName) + __restart_hotspot_daemon() + __restart_miniupnpd(devName,ifName) +end + +function __apply_wifi_wpsconf(devs, devname, cfgs, diff) + local saved = cfgs.WscConfMode and cfgs.WscConfMode:gsub(";-(%d);-","%1") or "" + local applied = diff.WscConfMode and diff["WscConfMode"][2]:gsub(";-(%d);-","%1") or "" + local num_ifs = tonumber(cfgs.BssidNum) or 0 + + for idx=1, num_ifs do + local ifname = devs[devname]["vifs"][idx]["vifname"] + if mtkwifi.__any_wsc_enabled(saved:sub(idx,idx)) == 1 then + cfgs.WscConfStatus = mtkwifi.token_set(cfgs.WscConfStatus, idx, "2") + else + cfgs.WscConfStatus = mtkwifi.token_set(cfgs.WscConfStatus, idx, "1") + end + if (diff.WscConfMode) and saved:sub(idx,idx) ~= applied:sub(idx,idx) then + cfgs = mtkwifi.__restart_if_wps(devname, ifname, cfgs) + end + end + + -- __mtkwifi_save_profile() is called outside the loop because it is a high time consuming function. + __mtkwifi_save_profile(cfgs, devs[devname]["profile"], false) + + if diff.WscConfMode then + for idx=1, num_ifs do + local ifname = devs[devname]["vifs"][idx]["vifname"] + if saved:sub(idx,idx) ~= applied:sub(idx,idx) then + __restart_miniupnpd(devname, ifname) + end + end + end +end + +function __set_wifi_wpsconf(cfgs, wsc_enable, vif_idx) + debug_write("__set_wifi_wpsconf : wsc_enable = ",wsc_enable) + if(wsc_enable == "1") then + cfgs["WscConfMode"] = mtkwifi.token_set(cfgs["WscConfMode"], vif_idx, "7") + else + cfgs["WscConfMode"] = mtkwifi.token_set(cfgs["WscConfMode"], vif_idx, "0") + end + if(((http.formvalue("__authmode")=="OPEN") and + (http.formvalue("__encrypttype") == "WEP")) or + (http.formvalue("__hidessid") == "1")) then + cfgs.WscConfMode = mtkwifi.token_set(cfgs.WscConfMode, vif_idx, "0") + end + debug_write("__set_wifi_wpsconf : WscConfMode = ",cfgs["WscConfMode"]) +end + +function __update_mbss_para(cfgs, vif_idx) + debug_write(vif_idx) + cfgs.HT_STBC = mtkwifi.token_set(cfgs.HT_STBC, vif_idx, http.formvalue("__ht_stbc") or "0") + cfgs.HT_LDPC = mtkwifi.token_set(cfgs.HT_LDPC, vif_idx, http.formvalue("__ht_ldpc") or "0") + cfgs.VHT_STBC = mtkwifi.token_set(cfgs.VHT_STBC, vif_idx, http.formvalue("__vht_stbc") or "0") + cfgs.VHT_LDPC = mtkwifi.token_set(cfgs.VHT_LDPC, vif_idx, http.formvalue("__vht_ldpc") or "0") + cfgs.DLSCapable = mtkwifi.token_set(cfgs.DLSCapable, vif_idx, http.formvalue("__dls_capable") or "0") + cfgs.APSDCapable = mtkwifi.token_set(cfgs.APSDCapable, vif_idx, http.formvalue("__apsd_capable") or "0") + cfgs.FragThreshold = mtkwifi.token_set(cfgs.FragThreshold, vif_idx, http.formvalue("__frag_threshold") or "0") + cfgs.RTSThreshold = mtkwifi.token_set(cfgs.RTSThreshold, vif_idx, http.formvalue("__rts_threshold") or "0") + cfgs.VHT_SGI = mtkwifi.token_set(cfgs.VHT_SGI, vif_idx, http.formvalue("__vht_sgi") or "0") + cfgs.VHT_BW_SIGNAL = mtkwifi.token_set(cfgs.VHT_BW_SIGNAL, vif_idx, http.formvalue("__vht_bw_signal") or "0") + cfgs.HT_PROTECT = mtkwifi.token_set(cfgs.HT_PROTECT, vif_idx, http.formvalue("__ht_protect") or "0") + cfgs.HT_GI = mtkwifi.token_set(cfgs.HT_GI, vif_idx, http.formvalue("__ht_gi") or "0") + cfgs.HT_OpMode = mtkwifi.token_set(cfgs.HT_OpMode, vif_idx, http.formvalue("__ht_opmode") or "0") + cfgs.HT_AMSDU = mtkwifi.token_set(cfgs.HT_AMSDU, vif_idx, http.formvalue("__ht_amsdu") or "0") + cfgs.HT_AutoBA = mtkwifi.token_set(cfgs.HT_AutoBA, vif_idx, http.formvalue("__ht_autoba") or "0") + cfgs.IgmpSnEnable = mtkwifi.token_set(cfgs.IgmpSnEnable, vif_idx, http.formvalue("__igmp_snenable") or "0") + cfgs.WirelessMode = mtkwifi.token_set(cfgs.WirelessMode, vif_idx, http.formvalue("__wirelessmode") or "0") + cfgs.WdsEnable = mtkwifi.token_set(cfgs.WdsEnable, vif_idx, http.formvalue("__wdsenable") or "0") + cfgs.MuOfdmaDlEnable = mtkwifi.token_set(cfgs.MuOfdmaDlEnable, vif_idx, http.formvalue("__muofdma_dlenable") or "0") + cfgs.MuOfdmaUlEnable = mtkwifi.token_set(cfgs.MuOfdmaUlEnable, vif_idx, http.formvalue("__muofdma_ulenable") or "0") + cfgs.MuMimoDlEnable = mtkwifi.token_set(cfgs.MuMimoDlEnable, vif_idx, http.formvalue("__mumimo_dlenable") or "0") + cfgs.MuMimoUlEnable = mtkwifi.token_set(cfgs.MuMimoUlEnable, vif_idx, http.formvalue("__mumimo_ulenable") or "0") + +end + +function vif_cfg(dev, vif) + local devname, vifname = dev, vif + if not devname then devname = vif end + debug_write("devname="..devname) + debug_write("vifname="..(vifname or "")) + local devs = mtkwifi.get_all_devs() + local profile = devs[devname].profile + assert(profile) + + --local ssid_index; + --ssid_index = devs[devname]["vifs"][vifname].vifidx + + local cfgs = mtkwifi.load_profile(profile) + + for k,v in pairs(http.formvalue()) do + if type(v) == type("") or type(v) == type(0) then + nixio.syslog("debug", "post."..k.."="..tostring(v)) + else + nixio.syslog("debug", "post."..k.." invalid, type="..type(v)) + end + end + + -- sometimes vif_idx start from 0, like AccessPolicy0 + -- sometimes it starts from 1, like WPAPSK1. nice! + local vif_idx + local to_url + if http.formvalue("__action") == "vif_cfg_view" then + vif_idx = devs[devname]["vifs"][vifname].vifidx + debug_write("vif_idx=", vif_idx, devname, vifname) + to_url = luci.dispatcher.build_url("admin", "mtk", "wifi", "vif_cfg_view", devname, vifname) + elseif http.formvalue("__action") == "vif_add_view" then + cfgs.BssidNum = tonumber(cfgs.BssidNum) + 1 + vif_idx = tonumber(cfgs.BssidNum) + to_url = luci.dispatcher.build_url("admin", "mtk", "wifi") + -- initializing ; separated parameters for the new interface + cfgs = initialize_multiBssParameters(cfgs, vif_idx) + end + assert(vif_idx) + assert(to_url) + -- "__" should not be the prefix of a name if user wants to copy form value data directly to the dat file variable + for k,v in pairs(http.formvalue()) do + if type(v) ~= type("") and type(v) ~= type(0) then + nixio.syslog("err", "vif_cfg, invalid value type for "..k..","..type(v)) + elseif string.byte(k) ~= string.byte("_") then + debug_write("vif_cfg: Copying",k,v) + cfgs[k] = v or "" + end + end + + -- WDS + -- Update WdsXKey if respective WdsEncrypType is NONE + for i=0,3 do + if (cfgs["Wds"..i.."Key"] and cfgs["Wds"..i.."Key"] ~= "") and + ((not mtkwifi.token_get(cfgs["WdsEncrypType"],i+1,nil)) or + ("NONE" == mtkwifi.token_get(cfgs["WdsEncrypType"],i+1,nil))) then + cfgs["Wds"..i.."Key"] = "" + end + end + + cfgs["AccessPolicy"..vif_idx-1] = http.formvalue("__accesspolicy") + local t = mtkwifi.parse_mac(http.formvalue("__maclist")) + cfgs["AccessControlList"..vif_idx-1] = table.concat(t, ";") + + __security_cfg(cfgs, vif_idx) + __update_mbss_para(cfgs, vif_idx) + __set_wifi_wpsconf(cfgs, http.formvalue("WPSRadio"), vif_idx) + + __mtkwifi_save_profile(cfgs, profile, false) + if http.formvalue("__apply") then + mtkwifi.__run_in_child_env(__mtkwifi_reload, devname) + luci.http.redirect(luci.dispatcher.build_url("admin", "mtk", "wifi", "loading",to_url)) + else + luci.http.redirect(to_url) + end +end + +function get_WPS_Info(devname, ifname) + local devs = mtkwifi.get_all_devs() + local ssid_index = devs[devname]["vifs"][ifname].vifidx + local profile = devs[devname].profile + assert(profile) + + local cfgs = mtkwifi.load_profile(profile) + + -- Create the applied settings backup file if it does not exist. + if not mtkwifi.exists(mtkwifi.__profile_applied_settings_path(profile)) then + os.execute("cp -f "..profile.." "..mtkwifi.__profile_applied_settings_path(profile)) + end + local applied_cfgs = mtkwifi.load_profile(mtkwifi.__profile_applied_settings_path(profile)) + + local WPS_details = {} + WPS_details = c_getCurrentWscProfile(ifname) + + if type(WPS_details) ~= "table" then + WPS_details["DRIVER_RSP"] = "NO" + else + WPS_details["DRIVER_RSP"] = "YES" + local isCfgsChanged = false -- To indicate that the settings have been changed by External Registrar. + local isBasicTabUpdateRequired = false + + if type(WPS_details["SSID"]) == "string" then + if applied_cfgs["SSID"..ssid_index] ~= WPS_details["SSID"] then + cfgs["SSID"..ssid_index] = WPS_details["SSID"] + isCfgsChanged = true + isBasicTabUpdateRequired = true + end + else + WPS_details["SSID"] = cfgs["SSID"..ssid_index] + end + + if type(WPS_details["AuthMode"]) == "string" then + local auth_mode_ioctl = WPS_details["AuthMode"]:gsub("%W",""):upper() + local auth_mode_applied = mtkwifi.token_get(applied_cfgs.AuthMode, ssid_index, "") + if auth_mode_applied ~= auth_mode_ioctl then + cfgs.AuthMode = mtkwifi.token_set(cfgs.AuthMode, ssid_index, auth_mode_ioctl) + isCfgsChanged = true + isBasicTabUpdateRequired = true + end + else + WPS_details["AuthMode"] = mtkwifi.token_get(cfgs.AuthMode, ssid_index, "") + end + + if type(WPS_details["EncType"]) == "string" then + local enc_type_ioctl = WPS_details["EncType"]:upper() + local enc_type_applied = mtkwifi.token_get(applied_cfgs.EncrypType, ssid_index, "") + if enc_type_applied ~= enc_type_ioctl then + cfgs.EncrypType = mtkwifi.token_set(cfgs.EncrypType, ssid_index, enc_type_ioctl) + isCfgsChanged = true + isBasicTabUpdateRequired = true + end + else + WPS_details["EncType"] = mtkwifi.token_get(cfgs.EncrypType, ssid_index, "") + end + + if type(WPS_details["WscWPAKey"]) == "string" then + if applied_cfgs["WPAPSK"..ssid_index] ~= WPS_details["WscWPAKey"] then + cfgs["WPAPSK"..ssid_index] = WPS_details["WscWPAKey"] + isCfgsChanged = true + isBasicTabUpdateRequired = true + end + else + WPS_details["WscWPAKey"] = cfgs["WPAPSK"..ssid_index] + end + + if type(WPS_details["DefKey"]) == "number" then + local def_key_applied = tonumber(mtkwifi.token_get(applied_cfgs.DefaultKeyID, ssid_index, "")) + if def_key_applied ~= WPS_details["DefKey"] then + cfgs.DefaultKeyID = mtkwifi.token_set(cfgs.DefaultKeyID, ssid_index, WPS_details["DefKey"]) + isCfgsChanged = true + end + else + WPS_details["DefKey"] = tonumber(mtkwifi.token_get(cfgs.DefaultKeyID, ssid_index, 0)) or "" + end + + if type(WPS_details["Conf"]) == "number" then + local wsc_conf_status_applied = tonumber(mtkwifi.token_get(applied_cfgs.WscConfStatus, ssid_index, "")) + if wsc_conf_status_applied ~= WPS_details["Conf"] then + cfgs.WscConfStatus = mtkwifi.token_set(cfgs.WscConfStatus, ssid_index, WPS_details["Conf"]) + isCfgsChanged = true + end + else + WPS_details["Conf"] = mtkwifi.token_get(cfgs.WscConfStatus, ssid_index, "") + end + + WPS_details["IS_BASIC_TAB_UPDATE_REQUIRED"] = isBasicTabUpdateRequired + + if isCfgsChanged then + -- Driver updates the *.dat file for following scenarios, + -- 1. When WPS Conf Status is not configured i.e. WscConfStatus is not set as 2, + -- and connection with a station is established i.e. where station acts as an External Registrar. + -- 2. When below settings are changed through External Registrar irrespective of WPS Conf Status + -- Update mtkwifi.__profile_applied_settings_path(profile) file with the + -- new settings to avoid display of "reload to apply changes" message. + applied_cfgs["WPAPSK"] = cfgs["WPAPSK"] + applied_cfgs["SSID"] = cfgs["SSID"] + applied_cfgs["SSID"..ssid_index] = cfgs["SSID"..ssid_index] + applied_cfgs["AuthMode"] = cfgs["AuthMode"] + applied_cfgs["EncrypType"] = cfgs["EncrypType"] + applied_cfgs["WPAPSK"..ssid_index] = cfgs["WPAPSK"..ssid_index] + applied_cfgs["DefaultKeyID"] = cfgs["DefaultKeyID"] + applied_cfgs["WscConfStatus"] = cfgs["WscConfStatus"] + mtkwifi.save_profile(applied_cfgs, mtkwifi.__profile_applied_settings_path(profile)) + end + end + http.write_json(WPS_details) +end + +function get_wifi_pin(ifname) + local pin = "" + pin = c_getApPin(ifname) + http.write_json(pin) +end + +function set_wifi_gen_pin(ifname,devname) + local devs = mtkwifi.get_all_devs() + local ssid_index = devs[devname]["vifs"][ifname].vifidx + local profile = devs[devname].profile + assert(profile) + + local cfgs = mtkwifi.load_profile(profile) + + os.execute("iwpriv "..ifname.." set WscGenPinCode") + + pin = c_getApPin(ifname) + cfgs["WscVendorPinCode"]=pin["genpincode"] + + --existing c code... done nothing for this segment as it read flash data and write to related .dat file. + -- no concept of nvram zones here + --if (nvram == RT2860_NVRAM) + -- do_system("ralink_init make_wireless_config rt2860"); + --else + -- do_system("ralink_init make_wireless_config rtdev"); + __mtkwifi_save_profile(cfgs, profile, true) + http.write_json(pin) +end + +function set_wifi_wps_oob(devname, ifname) + local SSID, mac = "" + local ssid_index = 0 + local devs = mtkwifi.get_all_devs() + local profile = devs[devname].profile + assert(profile) + + local cfgs = mtkwifi.load_profile(profile) + + ssid_index = devs[devname]["vifs"][ifname].vifidx + mac = c_get_macaddr(ifname) + + if (mac["macaddr"] ~= "") then + SSID = "RalinkInitAP"..(ssid_index-1).."_"..mac["macaddr"] + else + SSID = "RalinkInitAP"..(ssid_index-1).."_unknown" + end + + cfgs["SSID"..ssid_index]=SSID + cfgs.WscConfStatus = mtkwifi.token_set(cfgs.WscConfStatus, ssid_index, "1") + cfgs.AuthMode = mtkwifi.token_set(cfgs.AuthMode, ssid_index, "WPA2PSK") + cfgs.EncrypType = mtkwifi.token_set(cfgs.EncrypType, ssid_index, "AES") + cfgs.DefaultKeyID = mtkwifi.token_set(cfgs.DefaultKeyID, ssid_index, "2") + + cfgs["WPAPSK"..ssid_index]="fixture1" + cfgs["WPAPSK"]="" + cfgs.IEEE8021X = mtkwifi.token_set(cfgs.IEEE8021X, ssid_index, "0") + + os.execute("iwpriv "..ifname.." set SSID="..SSID ) + debug_write("iwpriv "..ifname.." set SSID="..SSID ) + os.execute("iwpriv "..ifname.." set AuthMode=WPA2PSK") + debug_write("iwpriv "..ifname.." set AuthMode=WPA2PSK") + os.execute("iwpriv "..ifname.." set EncrypType=AES") + debug_write("iwpriv "..ifname.." set EncrypType=AES") + os.execute("iwpriv "..ifname.." set WPAPSK=fixture1") + debug_write("iwpriv "..ifname.." set WPAPSK=fixture1") + os.execute("iwpriv "..ifname.." set SSID="..SSID) + debug_write("iwpriv "..ifname.." set SSID="..SSID) + + cfgs = mtkwifi.__restart_if_wps(devname, ifname, cfgs) + __mtkwifi_save_profile(cfgs, profile, true) + + mtkwifi.__run_in_child_env(__restart_all_daemons, devname, ifname) + + os.execute("iwpriv "..ifname.." set WscConfStatus=1") + debug_write("iwpriv "..ifname.." set WscConfStatus=1") + + local url_to_visit_after_reload = luci.dispatcher.build_url("admin", "mtk", "wifi", "vif_cfg_view", devname, ifname) + luci.http.redirect(luci.dispatcher.build_url("admin", "mtk", "wifi", "loading",url_to_visit_after_reload)) +end + +function set_wifi_do_wps(ifname, devname, wsc_pin_code_w) + local devs = mtkwifi.get_all_devs() + local ssid_index = devs[devname]["vifs"][ifname].vifidx + local profile = devs[devname].profile + local wsc_mode = 0 + local wsc_conf_mode + assert(profile) + + local cfgs = mtkwifi.load_profile(profile) + + if(wsc_pin_code_w == "nopin") then + wsc_mode=2 + else + wsc_mode=1 + end + + wsc_conf_mode = mtkwifi.token_get(cfgs["WscConfMode"], ssid_index, nil) + + if (wsc_conf_mode == 0) then + print("{\"wps_start\":\"WPS_NOT_ENABLED\"}") + DBG_MSG("WPS is not enabled before do PBC/PIN.\n") + return + end + + if (wsc_mode == 1) then + __wps_ap_pin_start_all(ifname, wsc_pin_code_w) + + elseif (wsc_mode == 2) then + __wps_ap_pbc_start_all(ifname) + else + http.write_json("{\"wps_start\":\"NG\"}") + return + end + cfgs["WscStartIF"] = ifname + + -- execute wps_action.lua file to send signal for current interface + os.execute("lua wps_action.lua "..ifname) + + http.write_json("{\"wps_start\":\"OK\"}") +end + +function get_wps_security(ifname, devname) + local devs = mtkwifi.get_all_devs() + local ssid_index = devs[devname]["vifs"][ifname].vifidx + local profile = devs[devname].profile + assert(profile) + local output = {} + local cfgs = mtkwifi.load_profile(profile) + + output["AuthMode"] = mtkwifi.token_get(cfgs.AuthMode,ssid_index) + output["IEEE8021X"] = mtkwifi.token_get(cfgs.IEEE8021X,ssid_index) + + http.write_json(output) +end + +function apcli_get_wps_status(ifname, devname) + local output = {} + local ssid_index = 0 + local devs = mtkwifi.get_all_devs() + local profile = devs[devname].profile + assert(profile) + + -- apcli interface has a different structure as compared to other vifs + ssid_index = devs[devname][ifname].vifidx + output = c_apcli_get_wps_status(ifname) + if (output.wps_port_secured == "YES") then + local cfgs = mtkwifi.load_profile(profile) + cfgs.ApCliSsid = mtkwifi.token_set(cfgs.ApCliSsid, ssid_index, output.enr_SSID) + cfgs.ApCliEnable = mtkwifi.token_set(cfgs.ApCliEnable, ssid_index, "1") + cfgs.ApCliAuthMode = mtkwifi.token_set(cfgs.ApCliAuthMode, ssid_index, output.enr_AuthMode) + cfgs.ApCliEncrypType = mtkwifi.token_set(cfgs.ApCliEncrypType, ssid_index, output.enr_EncrypType) + cfgs.ApCliDefaultKeyID = mtkwifi.token_set(cfgs.ApCliDefaultKeyID, ssid_index, output.enr_DefaultKeyID) + cfgs.Channel = mtkwifi.read_pipe("iwconfig "..ifname.." | grep Channel | cut -d = -f 2 | cut -d \" \" -f 1") + debug_write("iwconfig "..ifname.." | grep Channel | cut -d = -f 2 | cut -d \" \" -f 1") + + if(output.enr_EncrypType == "WEP") then + for i = 1, 4 do + cfgs["ApCliKey"..i.."Type"] = mtkwifi.token_set(cfgs["ApCliKey"..i.."Type"], ssid_index, output["Key"..i.."Type"]) + end + if(ssid_index == "0") then + cfgs["ApCliKey"..output.enr_DefaultKeyID.."Str"] = output.enr_KeyStr + else + cfgs["ApCliKey"..output.enr_DefaultKeyID.."Str"..ssid_index] = output.enr_KeyStr + end + elseif(output.enr_EncrypType == "TKIP") or (output.enr_EncrypType == "AES") or (output.enr_EncrypType == "TKIPAES") then + if(output.enr_AuthMode ~= "WPAPSKWPA2PSK") then + cfgs["ApCliWPAPSK"] = output.enr_WPAPSK + end + end + __mtkwifi_save_profile(cfgs, profile, true) + end + http.write_json(output); +end + +function string.tohex(str) + return (str:gsub('.', function (c) + return string.format('%02X', string.byte(c)) + end)) +end + +function unencode_ssid(raw_ssid) + local c + local output = "" + local convertNext = 0 + for c in raw_ssid:gmatch"." do + if(convertNext == 0) then + if(c == '+') then + output = output..' ' + elseif(c == '%') then + convertNext = 1 + else + output = output..c + end + else + output = output..string.tohex(c) + convertNext = 0 + end + end + return output +end + +function decode_ssid(raw_ssid) + local output = raw_ssid + output = output:gsub("&", "&") + output = output:gsub("<", "<") + output = output:gsub(">", ">") + output = output:gsub(""", "\"") + output = output:gsub("'", "'") + output = output:gsub(" ", " ") + for codenum in raw_ssid:gmatch("&#(%d+);") do + output = output:gsub("&#"..codenum..";", string.char(tonumber(codenum))) + end + return output +end + +function apcli_do_enr_pin_wps(ifname, devname, raw_ssid) + local target_ap_ssid = "" + local ret_value = {} + if(raw_ssid == "") then + ret_value["apcli_do_enr_pin_wps"] = "GET_SSID_NG" + end + ret_value["raw_ssid"] = raw_ssid + target_ap_ssid = decode_ssid(raw_ssid) + target_ap_ssid = ''..mtkwifi.__handleSpecialChars(target_ap_ssid) + ret_value["target_ap_ssid"] = target_ap_ssid + if(target_ap_ssid == "") then + ret_value["apcli_do_enr_pin_wps"] = "GET_SSID_NG" + else + ret_value["apcli_do_enr_pin_wps"] = "OK" + end + os.execute("ifconfig "..ifname.." up") + debug_write("ifconfig "..ifname.." up") + os.execute("brctl addif br0 "..ifname) + debug_write("brctl addif br0 "..ifname) + os.execute("brctl addif br-lan "..ifname) + debug_write("brctl addif br-lan "..ifname) + os.execute("iwpriv "..ifname.." set ApCliAutoConnect=1") + os.execute("iwpriv "..ifname.." set ApCliEnable=1") + debug_write("iwpriv "..ifname.." set ApCliEnable=1") + --os.execute("iwpriv "..ifname.." set WscConfMode=0") + os.execute("iwpriv "..ifname.." set WscConfMode=1") + debug_write("iwpriv "..ifname.." set WscConfMode=1") + os.execute("iwpriv "..ifname.." set WscMode=1") + debug_write("iwpriv "..ifname.." set WscMode=1") + os.execute("iwpriv "..ifname.." set ApCliWscSsid=\""..target_ap_ssid.."\"") + debug_write("iwpriv "..ifname.." set ApCliWscSsid=\""..target_ap_ssid.."\"") + os.execute("iwpriv "..ifname.." set WscGetConf=1") + debug_write("iwpriv "..ifname.." set WscGetConf=1") + -- check interface value to correlate with nvram as values will be like apclixxx + os.execute("wps_action.lua "..ifname) + http.write_json(ret_value) +end + +function apcli_do_enr_pbc_wps(ifname, devname) + local ret_value = {} + + --os.execute("iwpriv "..ifname.." set ApCliAutoConnect=1") + --os.execute("iwpriv "..ifname.." set ApCliEnable=1") + --os.execute("ifconfig "..ifname.." up") + --os.execute("brctl addif br0 "..ifname) + --os.execute("iwpriv "..ifname.." set WscConfMode=0") + os.execute("iwpriv "..ifname.." set WscConfMode=1") + os.execute("iwpriv "..ifname.." set WscMode=2") + os.execute("iwpriv "..ifname.." set WscGetConf=1") + -- check interface value to correlate with nvram as values will be like apclixxx + os.execute("wps_action.lua "..ifname) + + --debug_write("iwpriv "..ifname.." set ApCliEnable=1") + --debug_write("brctl addif br0 "..ifname) + --debug_write("ifconfig "..ifname.." up") + debug_write("iwpriv "..ifname.." set WscConfMode=1") + debug_write("iwpriv "..ifname.." set WscMode=2") + debug_write("iwpriv "..ifname.." set WscGetConf=1") + ret_value["apcli_do_enr_pbc_wps"] = "OK" + http.write_json(ret_value) +end + +function apcli_cancel_wps(ifname) + local ret_value = {} + os.execute("iwpriv "..ifname.." set WscStop=1") + os.execute("miniupnpd.sh init") + -- check interface value to correlate with nvram as values will be like apclixxx + os.execute("wps_action.lua "..ifname) + ret_value["apcli_cancel_wps"] = "OK" + http.write_json(ret_value) +end + +function apcli_wps_gen_pincode(ifname) + local ret_value = {} + os.execute("iwpriv "..ifname.." set WscGenPinCode") + ret_value["apcli_wps_gen_pincode"] = "OK" + http.write_json(ret_value) +end + +function apcli_wps_get_pincode(ifname) + local output = c_apcli_wps_get_pincode(ifname) + http.write_json(output) +end + +function get_apcli_conn_info(ifname) + local rsp = {} + if not ifname then + rsp["conn_state"]="Disconnected" + else + local flags = tonumber(mtkwifi.read_pipe("cat /sys/class/net/"..ifname.."/flags 2>/dev/null")) or 0 + rsp["infc_state"] = flags%2 == 1 and "up" or "down" + local iwapcli = mtkwifi.read_pipe("iwconfig "..ifname.." | grep ESSID 2>/dev/null") + local ssid = string.match(iwapcli, "ESSID:\"(.*)\"") + iwapcli = mtkwifi.read_pipe("iwconfig "..ifname.." | grep 'Access Point' 2>/dev/null") + local bssid = string.match(iwapcli, "%x%x:%x%x:%x%x:%x%x:%x%x:%x%x") + if not ssid or ssid == "" then + rsp["conn_state"]= "Disconnected" + else + rsp["conn_state"] = "Connected" + rsp["ssid"] = ssid + rsp["bssid"] = bssid or "N/A" + end + end + http.write_json(rsp) +end + +function sta_info(ifname) + local output = {} + local stalist = c_StaInfo(ifname) + + local count = 0 + for _ in pairs(stalist) do count = count + 1 end + + for i=0, count - 1 do + table.insert(output, stalist[i]) + end + http.write_json(output) +end + +function apcli_scan(ifname) + local aplist = mtkwifi.scan_ap(ifname) + local convert=""; + for i=1, #aplist do + convert = c_convert_string_display(aplist[i]["ssid"]) + aplist[i]["original_ssid"] = aplist[i]["ssid"] + aplist[i]["ssid"] = convert["output"] + end + http.write_json(aplist) +end + +function get_station_list() + http.write("get_station_list") +end + +function reset_wifi(devname) + if devname then + os.execute("cp -f /rom/etc/wireless/"..devname.."/ /etc/wireless/") + else + os.execute("cp -rf /rom/etc/wireless /etc/") + end + return luci.http.redirect(luci.dispatcher.build_url("admin", "mtk", "wifi")) +end + +function reload_wifi(devname) + profiles = mtkwifi.search_dev_and_profile() + path = profiles[devname] + mtkwifi.__run_in_child_env(__mtkwifi_reload, devname) + local url_to_visit_after_reload = luci.dispatcher.build_url("admin", "mtk", "wifi") + luci.http.redirect(luci.dispatcher.build_url("admin", "mtk", "wifi", "loading",url_to_visit_after_reload)) +end + +function get_raw_profile() + local sid = http.formvalue("sid") + http.write_json("get_raw_profile") +end + +function get_country_region_list() + local mode = http.formvalue("mode") + local cr_list; + + if mtkwifi.band(mode) == "5G" then + cr_list = mtkwifi.CountryRegionList_5G_All + elseif mtkwifi.band(mode) == "6G" then + cr_list = mtkwifi.CountryRegionList_6G_All + else + cr_list = mtkwifi.CountryRegionList_2G_All + end + + http.write_json(cr_list) +end + +function remove_ch_by_region(ch_list, region) + for i = #ch_list,2,-1 do + if not ch_list[i].region[region] then + table.remove(ch_list, i) + end + end +end + +function get_channel_list() + local mode = http.formvalue("mode") + local region = tonumber(http.formvalue("country_region")) or 1 + local ch_list + + if mtkwifi.band(mode) == "5G" then + ch_list = mtkwifi.ChannelList_5G_All + elseif mtkwifi.band(mode) == "6G" then + ch_list = mtkwifi.ChannelList_6G_All + else + ch_list = mtkwifi.ChannelList_2G_All + end + + remove_ch_by_region(ch_list, region) + http.write_json(ch_list) +end + +function get_HT_ext_channel_list() + local mode = http.formvalue("mode") + local ch_cur = tonumber(http.formvalue("ch_cur")) + local region = tonumber(http.formvalue("country_region")) or 1 + local ext_ch_list = {} + + if mtkwifi.band(mode) == "6G" then -- 6G Channel + local ch_list = mtkwifi.ChannelList_6G_All + local ext_ch_idx = -1 + local len = 0 + + for k, v in ipairs(ch_list) do + len = len + 1 + if v.channel == ch_cur then + ext_ch_idx = (k % 2 == 0) and k + 1 or k - 1 + end + end + + if ext_ch_idx > 0 and ext_ch_idx < len and ch_list[ext_ch_idx].region[region] then + ext_ch_list[1] = {} + ext_ch_list[1].val = ext_ch_idx % 2 + ext_ch_list[1].text = ch_list[ext_ch_idx].text + end + + elseif mtkwifi.band(mode) == "2.4G" then -- 2.4G Channel + local ch_list = mtkwifi.ChannelList_2G_All + local below_ch = ch_cur - 4 + local above_ch = ch_cur + 4 + local i = 1 + + if below_ch > 0 and ch_list[below_ch + 1].region[region] then + ext_ch_list[i] = {} + ext_ch_list[i].val = 0 + ext_ch_list[i].text = ch_list[below_ch + 1].text + i = i + 1 + end + + if above_ch <= 14 and ch_list[above_ch + 1].region[region] then + ext_ch_list[i] = {} + ext_ch_list[i].val = 1 + ext_ch_list[i].text = ch_list[above_ch + 1].text + end + else -- 5G Channel + local ch_list = mtkwifi.ChannelList_5G_All + local ext_ch_idx = -1 + local len = 0 + + for k, v in ipairs(ch_list) do + len = len + 1 + if v.channel == ch_cur then + ext_ch_idx = (k % 2 == 0) and k + 1 or k - 1 + end + end + + if ext_ch_idx > 0 and ext_ch_idx < len and ch_list[ext_ch_idx].region[region] then + ext_ch_list[1] = {} + ext_ch_list[1].val = ext_ch_idx % 2 + ext_ch_list[1].text = ch_list[ext_ch_idx].text + end + end + + http.write_json(ext_ch_list) +end + +function get_5G_2nd_80Mhz_channel_list() + local ch_cur = tonumber(http.formvalue("ch_cur")) + local region = tonumber(http.formvalue("country_region")) + local ch_list = mtkwifi.ChannelList_5G_2nd_80MHZ_ALL + local ch_list_5g = mtkwifi.ChannelList_5G_All + local i, j, test_ch, test_idx + local bw80_1st_idx = -1 + + -- remove adjacent freqencies starting from list tail. + for i = #ch_list,1,-1 do + for j = 0,3 do + if ch_list[i].channel == -1 then + break + end + + test_ch = ch_list[i].channel + j * 4 + test_idx = ch_list[i].chidx + j + + if test_ch == ch_cur then + if i + 1 <= #ch_list and ch_list[i + 1] then + table.remove(ch_list, i + 1) + end + table.remove(ch_list, i) + bw80_1st_idx = i + break + end + + if i == (bw80_1st_idx - 1) or (not ch_list_5g[test_idx].region[region]) then + table.remove(ch_list, i) + break + end + end + end + + -- remove unused channel. + for i = #ch_list,1,-1 do + if ch_list[i].channel == -1 then + table.remove(ch_list, i) + end + end + http.write_json(ch_list) +end + +function webcmd() + local cmd = http.formvalue("cmd") + if cmd then + local result = mtkwifi.read_pipe(tostring(cmd).." 2>&1") + result = result:gsub("<", "<") + http.write(tostring(result)) + else + http.write_json(http.formvalue()) + end +end + +function net_cfg() + http.write_json(http.formvalue()) +end + +function apcli_cfg(dev, vif) + local devname = dev + debug_write(devname) + local profiles = mtkwifi.search_dev_and_profile() + debug_write(profiles[devname]) + assert(profiles[devname]) + + local cfgs = mtkwifi.load_profile(profiles[devname]) + + for k,v in pairs(http.formvalue()) do + if type(v) ~= type("") and type(v) ~= type(0) then + nixio.syslog("err", "apcli_cfg, invalid value type for "..k..","..type(v)) + elseif string.byte(k) ~= string.byte("_") then + cfgs[k] = v or "" + end + end + + if cfgs['ApCliEnable'] == '1' then + os.execute("brctl addif br-lan "..vif) + end + + -- http.write_json(http.formvalue()) + + -- Mediatek Adaptive Network + --[=[ moved to a separated page + if cfgs.ApCliEzEnable then + cfgs.EzEnable = cfgs.ApCliEzEnable + cfgs.ApMWDS = cfgs.ApCliMWDS + cfgs.EzConfStatus = cfgs.ApCliEzConfStatus + cfgs.EzOpenGroupID = cfgs.ApCliEzOpenGroupID + if http.formvalue("__group_id_mode") == "0" then + cfgs.EzGroupID = cfgs.ApCliEzGroupID + cfgs.EzGenGroupID = "" + cfgs.ApCliEzGenGroupID = "" + else + cfgs.EzGroupID = "" + cfgs.ApCliEzGroupID = "" + cfgs.EzGenGroupID = cfgs.ApCliEzGenGroupID + end + -- if dbdc + -- os.execute("app_ez &") + -- os.execute("ManDaemon ") + end + ]=] + __mtkwifi_save_profile(cfgs, profiles[devname], false) + + -- M.A.N Push parameters + -- They are not part of wifi profile, we save it into /etc/man.conf. + + --[=[ moved to a separated page + local man_ssid = http.formvalue("__man_ssid_"..vifname) + local man_pass = http.formvalue("__man_pass_"..vifname) + local man_auth = http.formvalue("__man_auth_"..vifname) or "" + + if man_ssid and man_pass then + local fp = io.open("/etc/man."..vifname..".conf", "w+") + fp:write("__man_ssid_"..vifname.."="..man_ssid.."\n") + fp:write("__man_pass_"..vifname.."="..man_pass.."\n") + fp:write("__man_auth_"..vifname.."="..man_auth.."\n") + fp:close() + end + ]=] + + -- commented, do not connect by default + --[=[ + os.execute("iwpriv apcli0 set ApCliEnable=0") + os.execute("iwpriv apcli0 set Channel="..cfgs.Channel) + os.execute("iwpriv apcli0 set ApCliAuthMode="..cfgs.ApCliAuthMode) + os.execute("iwpriv apcli0 set ApCliEncrypType="..cfgs.ApCliEncrypType) + if cfgs.ApCliAuthMode == "WEP" then + os.execute("#iwpriv apcli0 set ApCliDefaultKeyID="..cfgs.ApCliDefaultKeyID) + os.execute("#iwpriv apcli0 set ApCliKey1="..cfgs.ApCliKey1Str) + elseif cfgs.ApCliAuthMode == "WPAPSK" + or cfgs.ApCliAuthMode == "WPA2PSK" + or cfgs.ApCliAuthMode == "WPAPSKWPA2PSK" then + os.execute("iwpriv apcli0 set ApCliWPAPSK="..cfgs.ApCliWPAPSK) + end + -- os.execute("iwpriv apcli0 set ApCliWirelessMode=") + os.execute("iwpriv apcli0 set ApCliSsid="..cfgs.ApCliSsid) + os.execute("iwpriv apcli0 set ApCliEnable=1") + ]=] + if http.formvalue("__apply") then + mtkwifi.__run_in_child_env(__mtkwifi_reload, devname) + local url_to_visit_after_reload = luci.dispatcher.build_url("admin", "mtk", "wifi", "apcli_cfg_view", dev, vif) + luci.http.redirect(luci.dispatcher.build_url("admin", "mtk", "wifi", "loading",url_to_visit_after_reload)) + else + luci.http.redirect(luci.dispatcher.build_url("admin", "mtk", "wifi", "apcli_cfg_view", dev, vif)) + end +end + +function apcli_connect(dev, vif) + -- dev_vif can be + -- 1. mt7620.apcli0 # simple case + -- 2. mt7615e.1.apclix0 # multi-card + -- 3. mt7615e.1.2G.apclix0 # multi-card & multi-profile + local devname,vifname = dev, vif + debug_write("devname=", dev, "vifname=", vif) + local profiles = mtkwifi.search_dev_and_profile() + debug_write(profiles[devname]) + assert(profiles[devname]) + local cfgs = mtkwifi.load_profile(profiles[devname]) + cfgs.ApCliEnable = "1" + __mtkwifi_save_profile(cfgs, profiles[devname], true) + os.execute("ifconfig "..vifname.." up") + os.execute("brctl addif br-lan "..vifname) + os.execute("iwpriv "..vifname.." set MACRepeaterEn="..cfgs.MACRepeaterEn) + os.execute("iwpriv "..vifname.." set ApCliEnable=0") + os.execute("iwpriv "..vifname.." set Channel="..cfgs.Channel) + os.execute("iwpriv "..vifname.." set ApCliAuthMode="..cfgs.ApCliAuthMode) + os.execute("iwpriv "..vifname.." set ApCliEncrypType="..cfgs.ApCliEncrypType) + if cfgs.ApCliEncrypType == "WEP" then + os.execute("iwpriv "..vifname.." set ApCliDefaultKeyID="..cfgs.ApCliDefaultKeyID) + if (cfgs.ApCliDefaultKeyID == "1") then + os.execute("iwpriv "..vifname.." set ApCliKey1=\""..mtkwifi.__handleSpecialChars(cfgs.ApCliKey1Str).."\"") + elseif (cfgs.ApCliDefaultKeyID == "2") then + os.execute("iwpriv "..vifname.." set ApCliKey2=\""..mtkwifi.__handleSpecialChars(cfgs.ApCliKey2Str).."\"") + elseif (cfgs.ApCliDefaultKeyID == "3") then + os.execute("iwpriv "..vifname.." set ApCliKey3=\""..mtkwifi.__handleSpecialChars(cfgs.ApCliKey3Str).."\"") + elseif (cfgs.ApCliDefaultKeyID == "4") then + os.execute("iwpriv "..vifname.." set ApCliKey4=\""..mtkwifi.__handleSpecialChars(cfgs.ApCliKey4Str).."\"") + end + elseif cfgs.ApCliAuthMode == "WPAPSK" + or cfgs.ApCliAuthMode == "WPA2PSK" + or cfgs.ApCliAuthMode == "WPAPSKWPA2PSK" then + os.execute("iwpriv "..vifname.." set ApCliWPAPSK=\""..mtkwifi.__handleSpecialChars(cfgs.ApCliWPAPSK).."\"") + end + os.execute("iwpriv "..vifname.." set ApCliSsid=\""..mtkwifi.__handleSpecialChars(cfgs.ApCliSsid).."\"") + os.execute("iwpriv "..vifname.." set ApCliEnable=1") + luci.http.redirect(luci.dispatcher.build_url("admin", "mtk", "wifi")) +end + +function apcli_disconnect(dev, vif) + -- dev_vif can be + -- 1. mt7620.apcli0 # simple case + -- 2. mt7615e.1.apclix0 # multi-card + -- 3. mt7615e.1.2G.apclix0 # multi-card & multi-profile + local devname,vifname = dev, vif + debug_write("devname=", dev, "vifname", vif) + debug_write(devname) + debug_write(vifname) + local profiles = mtkwifi.search_dev_and_profile() + debug_write(profiles[devname]) + assert(profiles[devname]) + local cfgs = mtkwifi.load_profile(profiles[devname]) + cfgs.ApCliEnable = "1" + __mtkwifi_save_profile(cfgs, profiles[devname], true) + os.execute("iwpriv "..vifname.." set ApCliEnable=0") + os.execute("ifconfig "..vifname.." down") + os.execute("brctl delif br-lan "..vifname) + luci.http.redirect(luci.dispatcher.build_url("admin", "mtk", "wifi")) +end + +-- Mediatek Adaptive Network +function man_cfg() + local mtkwifi = require("mtkwifi") + local profiles = mtkwifi.search_dev_and_profile() + + for k,v in pairs(http.formvalue()) do + debug_write(k.."="..v) + end + + + for dev,profile in pairs(profiles) do + debug_write(dev.."=2======="..profile) + local cfgs = mtkwifi.load_profile(profile) + + if cfgs.ApCliEzEnable then + + for k,v in pairs(http.formvalue()) do + if type(v) ~= type("") and type(v) ~= type(0) then + nixio.syslog("err", "man_cfg, invalid value type for "..k..","..type(v)) + elseif string.byte(k) ~= string.byte("_") then + cfgs[k] = v or "" + end + end + + debug_write(tostring(http.formvalue("__"..dev.."_ezsetup"))) + cfgs.ApCliEzEnable = http.formvalue("__"..dev.."_ezsetup") or "0" + + -- Yes this is bad. LSDK insists on this. + if cfgs.ApCliEzEnable == "1" then + cfgs.ApCliEnable = "1" + cfgs.ApCliMWDS = "1" + cfgs.ApCliAuthMode = "WPS2PSK" + cfgs.ApCliEncrypType = AES + cfgs.ApCliWPAPSK = "fixture1" + cfgs.AuthMode = "WPA2PSK" + cfgs.EncrypType = "AES" + cfgs.RekeyMethod = "TIME" + cfgs.WPAPSK1 = "" + cfgs.RegroupSupport = "1;1" + end + + if http.formvalue("__group_id_mode") == "0" then + cfgs.EzGroupID = cfgs.ApCliEzGroupID + cfgs.EzGenGroupID = "" + cfgs.ApCliEzGenGroupID = "" + else + cfgs.EzGroupID = "" + cfgs.ApCliEzGroupID = "" + cfgs.EzGenGroupID = cfgs.ApCliEzGenGroupID + end + + cfgs.EzEnable = cfgs.ApCliEzEnable + cfgs.ApMWDS = cfgs.ApCliMWDS + cfgs.EzConfStatus = cfgs.ApCliEzConfStatus + cfgs.EzOpenGroupID = cfgs.ApCliEzOpenGroupID + end + __mtkwifi_save_profile(cfgs, profile, false) + end + + if http.formvalue("__apply") then + mtkwifi.__run_in_child_env(__mtkwifi_reload) + local url_to_visit_after_reload = luci.dispatcher.build_url("admin", "mtk", "man") + luci.http.redirect(luci.dispatcher.build_url("admin", "mtk", "wifi", "loading",url_to_visit_after_reload)) + else + luci.http.redirect(luci.dispatcher.build_url("admin", "mtk", "man")) + end +end + +function apply_power_boost_settings() + local devname = http.formvalue("__devname") + local ret_status = {} + local devs = mtkwifi.get_all_devs() + local dev = {} + for _,v in ipairs(devs) do + if v.devname == devname then + dev = v + break + end + end + if next(dev) == nil then + ret_status["status"]= "Device "..(devname or "").." not found!" + elseif not dev.isPowerBoostSupported then + ret_status["status"]= "Power Boost feature is not supported by "..(devname or "").." Device!" + else + local cfgs = mtkwifi.load_profile(dev.profile) + if type(cfgs) ~= "table" or next(cfgs) == nil then + ret_status["status"]= "Profile settings file not found!" + else + for k,v in pairs(http.formvalue()) do + if type(v) ~= type("") and type(v) ~= type(0) then + debug_write("ERROR: [apply_power_boost_settings] String expected; Got"..type(v).."for"..k.."key") + ret_status["status"]= "Power Boost settings are of incorrect type!" + break + elseif string.byte(k) ~= string.byte("_") then + cfgs[k] = v or "" + end + end + if next(ret_status) == nil then + if type(dev.vifs) ~= "table" or next(dev.vifs) == nil or not cfgs.BssidNum or cfgs.BssidNum == "0" then + ret_status["status"]= "No Wireless Interfaces has been added yet!" + elseif cfgs.PowerUpenable ~= "1" then + ret_status["status"]= "Power Boost feature is not enabled!" + else + local up_vif_name_list = {} + for idx,vif in ipairs(dev.vifs) do + if vif.state == "up" and vif.vifname ~= nil and vif.vifname ~= "" and type(vif.vifname) == "string" then + up_vif_name_list[idx] = vif.vifname + end + end + if next(up_vif_name_list) == nil then + ret_status["status"]= "No Wireless Interfaces is up!" + else + for _,vifname in ipairs(up_vif_name_list) do + os.execute("iwpriv "..vifname.." set TxPowerBoostCtrl=0:"..cfgs.PowerUpCckOfdm) + os.execute("iwpriv "..vifname.." set TxPowerBoostCtrl=1:"..cfgs.PowerUpHT20) + os.execute("iwpriv "..vifname.." set TxPowerBoostCtrl=2:"..cfgs.PowerUpHT40) + os.execute("iwpriv "..vifname.." set TxPowerBoostCtrl=3:"..cfgs.PowerUpVHT20) + os.execute("iwpriv "..vifname.." set TxPowerBoostCtrl=4:"..cfgs.PowerUpVHT40) + os.execute("iwpriv "..vifname.." set TxPowerBoostCtrl=5:"..cfgs.PowerUpVHT80) + os.execute("iwpriv "..vifname.." set TxPowerBoostCtrl=6:"..cfgs.PowerUpVHT160) + os.execute("sleep 1") -- Wait for 1 second to let driver process the above data + end + __mtkwifi_save_profile(cfgs, dev.profile, true) + ret_status["status"]= "SUCCESS" + end + end + end + end + end + http.write_json(ret_status) +end + +function get_bssid_num(devName) + local ret_status = {} + local profiles = mtkwifi.search_dev_and_profile() + for dev,profile in pairs(profiles) do + if devName == dev then + local cfgs = mtkwifi.load_profile(profile) + if type(cfgs) ~= "table" or next(cfgs) == nil then + ret_status["status"]= "Profile settings file not found!" + else + ret_status["status"] = "SUCCESS" + ret_status["bssidNum"] = cfgs.BssidNum + end + break + end + end + if next(ret_status) == nil then + ret_status["status"]= "Device "..(devName or "").." not found!" + end + http.write_json(ret_status) +end + +local exec_reset_to_defaults_cmd = function (devname) + if devname then + os.execute("wifi reset "..devname) + else + os.execute("wifi reset") + end +end + +function reset_to_defaults(devname) + mtkwifi.__run_in_child_env(exec_reset_to_defaults_cmd, devname) + luci.http.redirect(luci.dispatcher.build_url("admin", "mtk", "wifi", "loading",mtkwifi.get_referer_url())) +end + +local exec_reset_to_default_easymesh_cmd = function () + -- OpenWRT + if mtkwifi.exists("/usr/bin/EasyMesh_openwrt.sh") then + os.execute("/usr/bin/EasyMesh_openwrt.sh default") + elseif mtkwifi.exists("/usr/bin/EasyMesh_7622.sh") then + os.execute("/usr/bin/EasyMesh_7622.sh default") + elseif mtkwifi.exists("/usr/bin/EasyMesh_7629.sh") then + os.execute("/usr/bin/EasyMesh_7629.sh default") + end + -- LSDK + if mtkwifi.exists("/sbin/EasyMesh.sh") then + os.execute("EasyMesh.sh default") + end +end + +function reset_to_default_easymesh() + mtkwifi.__run_in_child_env(exec_reset_to_default_easymesh_cmd) + + if mtkwifi.exists("/etc/dpp_cfg.txt") then + local dpp_cfg = mtkwifi.load_profile("/etc/dpp_cfg.txt") + dpp_cfg.allowed_role = "1" + mtkwifi.save_profile(dpp_cfg, "/etc/dpp_cfg.txt") + end + + luci.http.redirect(luci.dispatcher.build_url("admin", "mtk", "wifi", "loading",mtkwifi.get_referer_url())) +end + +function save_easymesh_driver_profile(easymesh_cfgs) + local profiles = mtkwifi.search_dev_and_profile() + local detected_5g = false + -- Following EasyMesh settings must be written to all DAT files of Driver, + -- 1. MapEnable + -- 2. MAP_Turnkey + for _,profile in mtkwifi.__spairs(profiles, function(a,b) return string.upper(a) < string.upper(b) end) do + local driver_cfgs = mtkwifi.load_profile(profile) + driver_cfgs['MapMode'] = easymesh_cfgs['MapMode'] + if http.formvalue("TriBand") == "1" then + if detected_5g == false and mtkwifi.band(string.split(driver_cfgs.WirelessMode,";")[1]) == "5G" then + driver_cfgs['ChannelGrp'] = "0:0:1:1" + detected_5g = true + elseif detected_5g == true and mtkwifi.band(string.split(driver_cfgs.WirelessMode,";")[1]) == "5G" then + driver_cfgs['ChannelGrp'] = "1:1:0:0" + end + elseif http.formvalue("TriBand") == "2" then + if detected_5g == false and mtkwifi.band(string.split(driver_cfgs.WirelessMode,";")[1]) == "5G" then + driver_cfgs['ChannelGrp'] = "1:1:0:0" + detected_5g = true + elseif detected_5g == true and mtkwifi.band(string.split(driver_cfgs.WirelessMode,";")[1]) == "5G" then + driver_cfgs['ChannelGrp'] = "0:0:1:1" + end + end + if driver_cfgs['MapMode'] == "1" then + driver_cfgs['SREnable'] = "0" + driver_cfgs['SRMode'] = "0" + end + if easymesh_cfgs['MeshSREnable'] == "1" then + driver_cfgs['SREnable'] = "1" + driver_cfgs['SRMode'] = "1" + driver_cfgs['MapBalance'] = "1" + driver_cfgs['BSSColorValue'] = "254" + elseif easymesh_cfgs['MeshSREnable'] == "0" then + driver_cfgs['SREnable'] = "0" + driver_cfgs['SRMode'] = "0" + driver_cfgs['MapBalance'] = "0" + driver_cfgs['BSSColorValue'] = "255" + end + __mtkwifi_save_profile(driver_cfgs, profile, false) + end +end + +function map_cfg() + local easymesh_cfgs = mtkwifi.load_profile(mtkwifi.__write_easymesh_profile_path()) + assert(easymesh_cfgs) + + local easymesh_applied_path = mtkwifi.__profile_applied_settings_path(mtkwifi.__write_easymesh_profile_path()) + os.execute("cp -f "..mtkwifi.__write_easymesh_profile_path().." "..easymesh_applied_path) + + for k,v in pairs(http.formvalue()) do + if type(v) ~= type("") and type(v) ~= type(0) then + debug_write("map_cfg: Invalid value type for "..k..","..type(v)) + elseif string.byte(k) ~= string.byte("_") then + debug_write("map_cfg: Copying key:"..k..","..type(v)) + easymesh_cfgs[k] = v or "" + end + end + + local bands = mtkwifi.detect_triband() + if bands ~= 3 then + easymesh_cfgs['BhPriority5GH'] = easymesh_cfgs['BhPriority5GL'] + end + + save_easymesh_driver_profile(easymesh_cfgs) + mtkwifi.save_write_easymesh_profile(easymesh_cfgs) + + if http.formvalue("__apply") then + + if http.formvalue("__ChangeDeviceRole")=="changed" then + os.execute("wappctrl ra0 dpp dpp_reset_dpp_config_file") + end + + if mtkwifi.exists("/etc/dpp_cfg.txt") then + local dpp_cfg = mtkwifi.load_profile("/etc/dpp_cfg.txt") + if http.formvalue("DeviceRole")=="1" then + dpp_cfg.allowed_role="2" + elseif http.formvalue("DeviceRole")== "2" then + dpp_cfg.allowed_role="1" + elseif http.formvalue("DeviceRole")== "0" then + dpp_cfg.allowed_role="0" + end + mtkwifi.save_profile(dpp_cfg, "/etc/dpp_cfg.txt") + end + + if mtkwifi.exists("/usr/bin/map_restart.sh") then + mtkwifi.__run_in_child_env(exec_map_restart) + else + mtkwifi.__run_in_child_env(__mtkwifi_reload) + end + + local url_to_visit_after_reload = luci.dispatcher.build_url("admin", "mtk", "multi_ap") + luci.http.redirect(luci.dispatcher.build_url("admin", "mtk", "wifi", "loading",url_to_visit_after_reload)) + else + luci.http.redirect(luci.dispatcher.build_url("admin", "mtk", "multi_ap")) + end +end + +function exec_map_restart() + if mtkwifi.exists("/usr/bin/map_restart.sh") then + os.execute("/usr/bin/map_restart.sh") + end +end + +function get_device_role() + local devRole = c_get_device_role() + -- Set ApCliEnable as "1" for Device with on-boarded ApCli interface to let + -- UI display connection information of ApCli interface on Wireless Overview web-page. + if tonumber(devRole.mapDevRole) == 2 then + local r = mtkwifi.get_easymesh_on_boarded_iface_info() + if r['status'] == "SUCCESS" then + for profile in string.gmatch(r['profile'],'(.-%.dat);') do + local cfgs = mtkwifi.load_profile(profile) + if cfgs.ApCliEnable ~= "1" or cfgs.ApCliEnable == nil then + cfgs.ApCliEnable = "1" + __mtkwifi_save_profile(cfgs, profile, true) + end + end + end + end + http.write_json(devRole) +end + +function trigger_uplink_ap_selection() + local r = c_trigger_uplink_ap_selection() + http.write_json(r) +end + +function trigger_mandate_steering_on_agent(sta_mac, target_bssid) + sta_mac = sta_mac:sub(1,17) + target_bssid = target_bssid:sub(1,17) + local r = c_trigger_mandate_steering_on_agent(sta_mac, target_bssid) + http.write_json(r) +end + +function trigger_back_haul_steering_on_agent(bh_mac, bh_target_bssid) + bh_mac = bh_mac:sub(1,17) + bh_target_bssid = bh_target_bssid:sub(1,17) + local r = c_trigger_back_haul_steering_on_agent(bh_mac, bh_target_bssid) + http.write_json(r) +end + +function trigger_wps_fh_agent(fh_bss_mac) + fh_bss_mac = fh_bss_mac:sub(1,17) + local r = c_trigger_wps_fh_agent(fh_bss_mac) + http.write_json(r) +end + +function trigger_multi_ap_on_boarding(ifmed) + assert(ifmed) + onboardingType = ifmed + debug_write("trigger_multi_ap_on_boarding: onboardingType:"..ifmed) + local r = c_trigger_multi_ap_on_boarding(ifmed) + http.write_json(r) +end + +function get_runtime_topology() + local r = c_get_runtime_topology() + http.write_json(r) +end + +function get_client_capabilities() + local r = c_get_client_capabilities() + http.write_json(r) +end + +function get_bh_connection_status() + local r = c_get_bh_connection_status() + http.write_json(r) +end + +function get_sta_steering_progress() + local r = {} + local fd = io.open("/tmp/sta_steer_progress","r") + if not fd then + r["status"] = "Failed to open /tmp/sta_steer_progress file in read mode!" + else + r["sta_steering_info"] = fd:read("*all") + fd:close() + r["status"] = "SUCCESS" + end + http.write_json(r) +end + +function get_al_mac(devRole) + local r = mtkwifi.get_easymesh_al_mac(devRole) + http.write_json(r) +end + +function apply_wifi_bh_priority(bhPriority2G, bhPriority5GL, bhPriority5GH) + assert(bhPriority2G) + assert(bhPriority5GL) + assert(bhPriority5GH) + debug_write("apply_wifi_bh_priority:BhPriority2G:"..bhPriority2G..", BhPriority5GL: "..bhPriority5GL..", BhPriority5GH: "..bhPriority5GH) + local r = c_apply_wifi_bh_priority(bhPriority2G, bhPriority5GL, bhPriority5GH) + if r.status == "SUCCESS" then + local read_easymesh_cfgs = mtkwifi.load_profile(mtkwifi.__read_easymesh_profile_path()) + read_easymesh_cfgs['BhPriority2G'] = bhPriority2G + read_easymesh_cfgs['BhPriority5GL'] = bhPriority5GL + read_easymesh_cfgs['BhPriority5GH'] = bhPriority5GH + mtkwifi.save_read_easymesh_profile(read_easymesh_cfgs) + + local write_easymesh_cfgs = mtkwifi.load_profile(mtkwifi.__write_easymesh_profile_path()) + write_easymesh_cfgs['BhPriority2G'] = bhPriority2G + write_easymesh_cfgs['BhPriority5GL'] = bhPriority5GL + write_easymesh_cfgs['BhPriority5GH'] = bhPriority5GH + mtkwifi.save_write_easymesh_profile(write_easymesh_cfgs) + end + http.write_json(r) +end + +function apply_ap_steer_rssi_th(rssi) + assert(rssi) + local r = c_apply_ap_steer_rssi_th(rssi) + if r.status == "SUCCESS" then + local easymesh_cfgs = mtkwifi.load_profile(mtkwifi.__read_easymesh_profile_path()) + if easymesh_cfgs['APSteerRssiTh'] ~= rssi then + easymesh_cfgs['APSteerRssiTh'] = rssi + mtkwifi.save_write_easymesh_profile(easymesh_cfgs) + end + local easymesh_mapd_cfgs = mtkwifi.load_profile(mtkwifi.__easymesh_mapd_profile_path()) + local mapd_rssi = tonumber(rssi) + 94 + if easymesh_mapd_cfgs['LowRSSIAPSteerEdge_RE'] ~= mapd_rssi then + easymesh_mapd_cfgs['LowRSSIAPSteerEdge_RE'] = mapd_rssi + mtkwifi.save_easymesh_mapd_profile(easymesh_mapd_cfgs) + end + end + http.write_json(r) +end + +function apply_force_ch_switch(agent_almac, channel1, channel2, channel3) + agent_almac = agent_almac:sub(1,17) + + if channel1 == nil then + channel1 = "" + end + + if channel2 == nil then + channel2 = "" + end + + if channel3 == nil then + channel3 = "" + end + + debug_write("apply_force_ch_switch() enter, agent_almac: "..agent_almac..", channel1:"..channel1..", channel2:"..channel2..", channe3:"..channel3) + local r = c_apply_force_ch_switch(agent_almac, channel1, channel2, channel3) + debug_write("apply_force_ch_switch() status: "..r.status) + http.write_json(r) +end + +function apply_user_preferred_channel(channel) + assert(channel) + debug_write("apply_user_preferred_channel() enter, channel:"..channel) + local r = c_apply_user_preferred_channel(channel) + debug_write("apply_user_preferred_channel() status: "..r.status) + http.write_json(r) +end + +function trigger_channel_planning_r2(band) + assert(band) + local r = c_trigger_channel_planning_r2(band) + http.write_json(r) +end + +function trigger_de_dump(almac) + assert(almac) + local r = c_trigger_de_dump(almac) + http.write_json(r) +end + +function get_data_element() + local r = c_get_data_element() + http.write_json(r) +end + +function trigger_channel_scan(almac) + assert(almac) + debug_write("trigger_channel_scan() enter, device AlMac:"..almac) + local r = c_trigger_channel_scan(almac) + debug_write("trigger_channel_scan() status: "..r.status) + http.write_json(r) +end + +function get_channel_stats() + local r = c_get_channel_stats() + http.write_json(r) +end + +function get_channel_planning_score() + local r = c_get_channel_planning_score() + http.write_json(r) +end + +function apply_channel_utilization_th(channelUtilTh2G, channelUtilTh5GL, channelUtilTh5GH) + assert(channelUtilTh2G) + assert(channelUtilTh5GL) + assert(channelUtilTh5GH) + local r = c_apply_channel_utilization_th(channelUtilTh2G, channelUtilTh5GL, channelUtilTh5GH) + if r.status == "SUCCESS" then + local easymesh_cfgs = mtkwifi.load_profile(mtkwifi.__read_easymesh_profile_path()) + if easymesh_cfgs['CUOverloadTh_2G'] ~= channelUtilTh2G or + easymesh_cfgs['CUOverloadTh_5G_L'] ~= channelUtilTh5GL or + easymesh_cfgs['CUOverloadTh_5G_H'] ~= channelUtilTh5GH then + easymesh_cfgs['CUOverloadTh_2G'] = channelUtilTh2G + easymesh_cfgs['CUOverloadTh_5G_L'] = channelUtilTh5GL + easymesh_cfgs['CUOverloadTh_5G_H'] = channelUtilTh5GH + mtkwifi.save_write_easymesh_profile(easymesh_cfgs) + end + local easymesh_mapd_cfgs = mtkwifi.load_profile(mtkwifi.__easymesh_mapd_profile_path()) + if easymesh_mapd_cfgs['CUOverloadTh_2G'] ~= channelUtilTh2G or + easymesh_mapd_cfgs['CUOverloadTh_5G_L'] ~= channelUtilTh5GL or + easymesh_mapd_cfgs['CUOverloadTh_5G_H'] ~= channelUtilTh5GH then + easymesh_mapd_cfgs['CUOverloadTh_2G'] = channelUtilTh2G + easymesh_mapd_cfgs['CUOverloadTh_5G_L'] = channelUtilTh5GL + easymesh_mapd_cfgs['CUOverloadTh_5G_H'] = channelUtilTh5GH + mtkwifi.save_easymesh_mapd_profile(easymesh_mapd_cfgs) + end + end + http.write_json(r) +end + +function get_sta_bh_interface() + local r = mtkwifi.get_easymesh_on_boarded_iface_info() + http.write_json(r) +end + +function get_ap_bh_inf_list() + local devs = mtkwifi.get_all_devs() + local r = c_get_ap_bh_inf_list() + if r.status == "SUCCESS" then + r['apBhInfListStr'] = "" + for mac in string.gmatch(r.macList, "(%x%x:%x%x:%x%x:%x%x:%x%x:%x%x);") do + for _, dev in ipairs(devs) do + local bssid_without_lf = dev.apcli and dev.apcli.mac_addr:upper():sub(1,17) or "" + if mac:upper() == bssid_without_lf then + r['apBhInfListStr'] = r['apBhInfListStr']..dev.apcli.vifname..';' + else + for _,vif in ipairs(dev.vifs) do + bssid_without_lf = vif.__bssid:upper():sub(1,17) + if mac:upper() == bssid_without_lf then + r['apBhInfListStr'] = r['apBhInfListStr']..vif.vifname..';' + end + end + end + end + end + end + http.write_json(r) +end + +function get_ap_fh_inf_list() + local devs = mtkwifi.get_all_devs() + local r = c_get_ap_fh_inf_list() + if r.status == "SUCCESS" then + r['apFhInfListStr'] = "" + for mac in string.gmatch(r.macList, "(%x%x:%x%x:%x%x:%x%x:%x%x:%x%x);") do + for _, dev in ipairs(devs) do + local bssid_without_lf = dev.apcli and dev.apcli.mac_addr:upper():sub(1,17) or "" + if mac:upper() == bssid_without_lf then + r['apFhInfListStr'] = r['apFhInfListStr']..dev.apcli.vifname..';' + else + for _,vif in ipairs(dev.vifs) do + bssid_without_lf = vif.__bssid:upper():sub(1,17) + if mac:upper() == bssid_without_lf then + r['apFhInfListStr'] = r['apFhInfListStr']..vif.vifname..';' + end + end + end + end + end + end + http.write_json(r) +end + +function validate_easymesh_bss(r, cfgs, alMac, band) + assert(type(r) == 'table') + assert(type(cfgs) == 'table') + assert(type(alMac) == 'string') + assert(type(band) == 'string') + if not cfgs[alMac] then + r['status'] = 'SUCCESS' + elseif not cfgs[alMac][band] then + r['status'] = 'SUCCESS' + else + local numBss = mtkwifi.get_table_length(cfgs[alMac][band]) + if numBss >= 4 then + r['status'] = 'No more BSS could be added!' + else + r['status'] = 'SUCCESS' + end + end +end + +function validate_add_easymesh_bss_req(alMac, band) + local r = {} + local cfgs = mtkwifi.load_easymesh_bss_cfgs() + if type(alMac) ~= 'string' then + r["status"]= "Invalid AL-MAC Type "..type(alMac).." !" + elseif type(band) ~= 'string' then + r["status"]= "Invalid Band Type "..type(band).." !" + else + if type(cfgs) ~= "table" or next(cfgs) == nil then + cfgs = {} + cfgs['wildCardAlMacCfgs'] = {} + cfgs['distinctAlMacCfgs'] = {} + end + if alMac == 'FF:FF:FF:FF:FF:FF' then + validate_easymesh_bss(r, cfgs['wildCardAlMacCfgs'], alMac, band) + else + validate_easymesh_bss(r, cfgs['distinctAlMacCfgs'], alMac, band) + end + end + if type(r) ~= 'table' or next(r) == nil then + r['status'] = "Unexpected Exception in validate_easymesh_bss()!" + end + http.write_json(r) +end + +function apply_easymesh_bss_cfg(isLocal) + local r = c_apply_bss_config_renew() + if r['status'] == 'SUCCESS' then + local easymesh_bss_cfg_applied_path = mtkwifi.__profile_applied_settings_path(mtkwifi.__easymesh_bss_cfgs_path()) + os.execute("cp -f "..mtkwifi.__easymesh_bss_cfgs_path().." "..easymesh_bss_cfg_applied_path) + end + if isLocal then + return r + else + luci.http.redirect(luci.dispatcher.build_url("admin", "mtk", "multi_ap", "easymesh_bss_config_renew")) + end +end + +function get_easymesh_bss_index(bssInfoTbl, bssInfoInp) + assert(type(bssInfoTbl) == 'table') + assert(type(bssInfoInp) == 'table') + for bssIdx, bssInfo in pairs(bssInfoTbl) do + debug_write("get SSID from wts_bss_info_config = "..bssInfo['ssid']) + bssInfoInp['defPCP'] = "N/A" + bssInfoInp['primVlan'] = "N/A" + if bssInfo['ssid'] == bssInfoInp['ssid'] and + bssInfo['authMode'] == bssInfoInp['authMode'] and + bssInfo['encType'] == bssInfoInp['encType'] and + bssInfo['passPhrase'] == bssInfoInp['passPhrase'] and + bssInfo['isBhBssSupported'] == bssInfoInp['isBhBssSupported'] and + bssInfo['isFhBssSupported'] == bssInfoInp['isFhBssSupported'] and + bssInfo['isHidden'] == bssInfoInp['isHidden'] and + bssInfo['fhVlanId'] == bssInfoInp['fhVlanId'] and + bssInfo['primVlan'] == bssInfoInp['primVlan'] and + bssInfo['defPCP'] == bssInfoInp['defPCP'] then + return bssIdx + end + end + return nil +end + +function update_easymesh_bss(cfgs, bssInfoInp, isEdit) + assert(type(cfgs) == 'table') + assert(type(bssInfoInp) == 'table') + assert(type(isEdit) == 'string') + if not cfgs[bssInfoInp['alMac']] then + cfgs[bssInfoInp['alMac']] = {} + end + if not cfgs[bssInfoInp['alMac']][bssInfoInp['band']] then + cfgs[bssInfoInp['alMac']][bssInfoInp['band']] = {} + end + local bssInfoTbl = cfgs[bssInfoInp['alMac']][bssInfoInp['band']] + local bssInfoIdx + if isEdit == "1" then + local editBssInfo = {} + local tmpEditSSID = http.formvalue('__EDIT_SSID'):gsub("\\", "\\\\") + editBssInfo['ssid'] = tmpEditSSID:gsub("%s+","\\ ") + debug_write("get edited SSID from UI = "..editBssInfo['ssid']) + editBssInfo['authMode'] = http.formvalue('__EDIT_AUTH_MODE') + editBssInfo['encType'] = http.formvalue('__EDIT_ENCRYPTION_TYPE') + local tmpEditPassPhrase = http.formvalue('__EDIT_PASS_PHRASE'):gsub("\\", "\\\\") + editBssInfo['passPhrase'] = tmpEditPassPhrase:gsub("%s+","\\ ") + editBssInfo['isBhBssSupported'] = http.formvalue('__EDIT_BH_SUPPORT') + editBssInfo['isFhBssSupported'] = http.formvalue('__EDIT_FH_SUPPORT') + editBssInfo['isHidden'] = http.formvalue('__EDIT_IS_SSID_HIDDEN') + editBssInfo['fhVlanId'] = http.formvalue('__EDIT_FH_VLAN_ID') + editBssInfo['primVlan'] = http.formvalue('__EDIT_PRIM_VLAN') + editBssInfo['defPCP'] = http.formvalue('__EDIT_DEF_PCP') + bssInfoIdx = get_easymesh_bss_index(bssInfoTbl, editBssInfo) + assert(bssInfoIdx) + assert(type(bssInfoTbl[bssInfoIdx]) == 'table') + else + bssInfoIdx = mtkwifi.get_table_length(bssInfoTbl) + 1 + bssInfoTbl[bssInfoIdx] = {} + end + local bssInfo = bssInfoTbl[bssInfoIdx] + bssInfo['ssid'] = bssInfoInp['ssid'] + debug_write("final SSID write to wts_bss_info_config = "..bssInfo['ssid']) + bssInfo['authMode'] = bssInfoInp['authMode'] + bssInfo['encType'] = bssInfoInp['encType'] + bssInfo['passPhrase'] = bssInfoInp['passPhrase'] and bssInfoInp['passPhrase'] ~= '' and bssInfoInp['passPhrase'] or 'fixture1' + bssInfo['isBhBssSupported'] = bssInfoInp['isBhBssSupported'] + bssInfo['isFhBssSupported'] = bssInfoInp['isFhBssSupported'] + bssInfo['isHidden'] = bssInfoInp['isHidden'] + bssInfo['fhVlanId'] = bssInfoInp['fhVlanId'] + bssInfo['primVlan'] = bssInfoInp['primVlan'] + bssInfo['defPCP'] = bssInfoInp['defPCP'] + +end + +function easymesh_bss_cfg() + local cfgs = mtkwifi.load_easymesh_bss_cfgs() + + local bssInfoInp = {} + for k,v in pairs(http.formvalue()) do + if type(v) ~= type("") and type(v) ~= type(0) then + debug_write("easymesh_bss_cfg: Input BSSINFO are of incorrect type!",k,v) + elseif string.byte(k) ~= string.byte("_") then + bssInfoInp[k] = v + end + end + + if bssInfoInp['primVlan'] ~= "N/A" and bssInfoInp['defPCP'] ~= "N/A" then + for alMac,alMacTbl in pairs(cfgs['wildCardAlMacCfgs']) do + for band,bssInfoTbl in pairs(alMacTbl) do + for _,bssInfo in pairs(bssInfoTbl) do + bssInfo['primVlan'] = "N/A" + bssInfo['defPCP'] = "N/A" + end + end + end + + for alMac,alMacTbl in pairs(cfgs['distinctAlMacCfgs']) do + for band,bssInfoTbl in pairs(alMacTbl) do + for _,bssInfo in pairs(bssInfoTbl) do + bssInfo['primVlan'] = "N/A" + bssInfo['defPCP'] = "N/A" + end + end + end + end + + debug_write("original SSID which user entered = "..bssInfoInp['ssid']) + local tmpSSID = bssInfoInp['ssid']:gsub("\\", "\\\\") + bssInfoInp['ssid'] = tmpSSID:gsub("%s+","\\ ") + debug_write("get SSID from UI = "..bssInfoInp['ssid']) + local tmpPassPhrase = bssInfoInp['passPhrase']:gsub("\\", "\\\\") + bssInfoInp['passPhrase'] = tmpPassPhrase:gsub("%s+","\\ ") + if type(cfgs) ~= "table" or next(cfgs) == nil then + cfgs = {} + cfgs['wildCardAlMacCfgs'] = {} + cfgs['distinctAlMacCfgs'] = {} + end + if bssInfoInp['alMac'] == 'FF:FF:FF:FF:FF:FF' then + update_easymesh_bss(cfgs['wildCardAlMacCfgs'], bssInfoInp, http.formvalue('__IS_EDIT')) + else + update_easymesh_bss(cfgs['distinctAlMacCfgs'], bssInfoInp, http.formvalue('__IS_EDIT')) + end + mtkwifi.save_easymesh_bss_cfgs(cfgs) + if http.formvalue("__apply") then + apply_easymesh_bss_cfg(true) + end + luci.http.redirect(luci.dispatcher.build_url("admin", "mtk", "multi_ap", "easymesh_bss_config_renew")) +end + +function remove_easymesh_bss(r,cfgs,bssInfoInp) + assert(type(r) == 'table') + assert(type(cfgs) == 'table') + assert(type(bssInfoInp) == 'table') + for alMac,alMacTbl in pairs(cfgs) do + if alMac == bssInfoInp['alMac'] then + assert(type(alMacTbl) == 'table') + for band,bssInfoTbl in pairs(alMacTbl) do + if bssInfoInp['primVlan'] ~= "N/A" and bssInfoInp['defPCP'] ~= "N/A" then + for _,bssInfo in pairs(bssInfoTbl) do + bssInfo['primVlan'] = "N/A" + bssInfo['defPCP'] = "N/A" + end + end + if band == bssInfoInp['band'] then + assert(type(bssInfoTbl) == 'table') + local bssIdx = get_easymesh_bss_index(bssInfoTbl, bssInfoInp) + if bssIdx then + local alMacTblLen = mtkwifi.get_table_length(alMacTbl) + local bssInfoTblLen = mtkwifi.get_table_length(bssInfoTbl) + if bssInfoTblLen == 1 then + cfgs[alMac][band] = nil + if alMacTblLen == 1 then + cfgs[alMac] = nil + end + else + table.remove(cfgs[alMac][band], tonumber(bssIdx)) + end + r['status'] = 'SUCCESS' + else + r['status'] = 'ERROR: BSSINFO does not exist!' + end + break + end + end + if next(r) == nil then + r['status'] = 'ERROR: BAND does not exist!' + end + break + end + end + if next(r) == nil then + r['status'] = 'ERROR: AL-MAC does not exist!' + end +end + +function remove_easymesh_bss_cfg_req() + local r = {} + local cfgs = mtkwifi.load_easymesh_bss_cfgs() + if type(cfgs) ~= "table" or next(cfgs) == nil then + r["status"]= mtkwifi.__easymesh_bss_cfgs_path().." file not found!" + else + local bssInfoInp = {} + for k,v in pairs(http.formvalue()) do + if type(v) ~= type("") and type(v) ~= type(0) then + r["status"]= "Input BSSINFO are of incorrect type!" + break + elseif string.byte(k) ~= string.byte("_") then + bssInfoInp[k] = v + end + end + local tmpSSID = bssInfoInp['ssid']:gsub("\\", "\\\\") + bssInfoInp['ssid'] = tmpSSID:gsub("%s+","\\ ") + local tmpPassPhrase = bssInfoInp['passPhrase']:gsub("\\", "\\\\") + bssInfoInp['passPhrase'] = tmpPassPhrase:gsub("%s+","\\ ") + if next(r) == nil then + if bssInfoInp['alMac'] == 'FF:FF:FF:FF:FF:FF' then + remove_easymesh_bss(r, cfgs['wildCardAlMacCfgs'], bssInfoInp) + else + remove_easymesh_bss(r, cfgs['distinctAlMacCfgs'], bssInfoInp) + end + end + end + if type(r) ~= 'table' or next(r) == nil then + r['status'] = "Unexpected Exception in remove_easymesh_bss()!" + else + mtkwifi.save_easymesh_bss_cfgs(cfgs) + r = apply_easymesh_bss_cfg(true) + end + http.write_json(r) +end + +function get_user_preferred_channel() + local r = c_get_user_preferred_channel() + http.write_json(r) +end + +function get_sp_rule_list() + local r = c_get_sp_rule_list() + http.write_json(r) +end + +function del_sp_rule(index) + if index == nil then + index = "" + end + local r = c_del_sp_rule(index) + http.write_json(r) +end + +function sp_rule_reorder(index1, index2) + local r = c_sp_rule_reorder(index1, index2) + http.write_json(r) +end + +function sp_rule_move(index, action) + local r = c_sp_rule_move(index, action) + http.write_json(r) +end + +function sp_rule_add(str_rule) + str_rule = string.gsub(str_rule, "] ", "]+") + local r = c_sp_rule_add(str_rule) + http.write_json(r) +end + +function sp_config_done() + local r = c_sp_config_done() + http.write_json(r) +end + +function submit_dpp_uri() + uri = http.formvalue("uri") + os.execute("wappctrl ra0 dpp dpp_qr_code ".."\""..uri.."\"") + luci.http.redirect(luci.dispatcher.build_url("admin", "mtk", "multi_ap")) +end + +function start_dpp_onboarding() + os.execute("wappctrl ra0 dpp dpp_start") + luci.http.redirect(luci.dispatcher.build_url("admin", "mtk", "multi_ap")) +end + +function generate_dpp_uri() + os.execute("wappctrl ra0 dpp dpp_bootstrap_gen type=qrcode") + luci.http.redirect(luci.dispatcher.build_url("admin", "mtk", "multi_ap")) +end + +function retrive_dpp_uri() + local result = mtkwifi.read_pipe(tostring("mapd_cli /tmp/mapd_ctrl get_dpp_uri").." 2>&1") + result = result:gsub("<", "<") + http.write(tostring(result)) +end \ No newline at end of file diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/mtkwifi.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/controller/mtkwifi.luac new file mode 100644 index 0000000000000000000000000000000000000000..9d611ba45bdecb2e85a959e64f3b0a12ec2aa19c GIT binary patch literal 153778 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
~;%ZxXVh>J1tw+?5;lD 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 ao!-C2PR~ peak then + peak = size + end + if tracefile then + tracefile:write( + "[", what, "] ", info.source, ":", (line or "?"), "\t", + (info.namewhat or ""), "\t", + (info.name or ""), "\t", + size, " (", peak, ")\n" + ) + end + end + + debug.sethook(trap, flags) + + return function() + debug.sethook() + tracefile:close() + end +end + diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/debug.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/debug.luac new file mode 100644 index 0000000000000000000000000000000000000000..b73fe8727211b4e8f3f10e5e04b0c35115e7f33c GIT binary patch literal 2086 zcmb7F-A>d%6h3FREFi|e2qJ6%%Q4X@F`c;?kEKjr##Hl&tKJ%UsiHJfR7*8ixvr+Xx=PcE z>5sFbC59n_Ayc&Ucs#4n0=Qe~z_Yycf)7c%7m!me2;(O?m7?2@$hldxn^71B&A8?F zw)_^Bh~rW^e!N>6*a@SkN6vUYMxAIo2{G9U5hl&{aZj`3`QPOL|v{MZAlP$Y|T<1o?#yx+((Fr=JG1=%+jL7y&0Bldpqn>k# zW}uqPIRuVem(N{oxJ*OAjOeS@sMrYXkR}H-YAi9!_yucP_cDc^ItIJxY2Nm*2j-&i zdnT4c5V;*JHl-OtFso*?A9QKp{Yu=~Um18mP~l(@XsKC0h^Tov7)>!Q~TZG#Zo|h|D%jP6GHf6hhCpg&k zu@Mx=%isiMtlVbtaoK)y%}kY(kV`cRxtXK54bI{Y+INRAjkW-%bS3U#&M0QVjp9By zi#fE#zGRT(s!dT7IQsN-hz#4^K)An1bBSfy@XUVuV?d&WCGFAW&w}mc56LBuk9B1~ z@b`=@+1Xric}k>X$|Amx0Gz9m7fGV1l1nvLn=3ixN;|)ja`uCF(O#x9t-`3E+Z8(x zhVT$=3)xGG*s^itQROE;?4vw6!dl1+c^R%_OitlnSO7l?@naZE=!<05r=8Gm_0h+p z2Usz;gMK_9FFGvD@N}cScsxwXb88Y-NRhh&NWq`P?*1R=ECI_6vk+k|#Il8iU?JOF l1rcNvga1LX9u?)T$}VzTBe4i|Nq&kg8MYzk$!u8+&>yMd;JN?+ literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/dispatcher.lua b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/dispatcher.lua new file mode 100644 index 000000000000..bd1b112f60cd --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/dispatcher.lua @@ -0,0 +1,1532 @@ +-- Copyright 2008 Steven Barth +-- Copyright 2008-2015 Jo-Philipp Wich +-- Licensed to the public under the Apache License 2.0. + +local fs = require "nixio.fs" +local sys = require "luci.sys" +local util = require "luci.util" +local xml = require "luci.xml" +local http = require "luci.http" +local nixio = require "nixio", require "nixio.util" + +module("luci.dispatcher", package.seeall) +context = util.threadlocal() +uci = require "luci.model.uci" +i18n = require "luci.i18n" +_M.fs = fs + +-- Index table +local index = nil + +local function check_fs_depends(spec) + local fs = require "nixio.fs" + + for path, kind in pairs(spec) do + if kind == "directory" then + local empty = true + for entry in (fs.dir(path) or function() end) do + empty = false + break + end + if empty then + return false + end + elseif kind == "executable" then + if fs.stat(path, "type") ~= "reg" or not fs.access(path, "x") then + return false + end + elseif kind == "file" then + if fs.stat(path, "type") ~= "reg" then + return false + end + end + end + + return true +end + +local function check_uci_depends_options(conf, s, opts) + local uci = require "luci.model.uci" + + if type(opts) == "string" then + return (s[".type"] == opts) + elseif opts == true then + for option, value in pairs(s) do + if option:byte(1) ~= 46 then + return true + end + end + elseif type(opts) == "table" then + for option, value in pairs(opts) do + local sval = s[option] + if type(sval) == "table" then + local found = false + for _, v in ipairs(sval) do + if v == value then + found = true + break + end + end + if not found then + return false + end + elseif value == true then + if sval == nil then + return false + end + else + if sval ~= value then + return false + end + end + end + end + + return true +end + +local function check_uci_depends_section(conf, sect) + local uci = require "luci.model.uci" + + for section, options in pairs(sect) do + local stype = section:match("^@([A-Za-z0-9_%-]+)$") + if stype then + local found = false + uci:foreach(conf, stype, function(s) + if check_uci_depends_options(conf, s, options) then + found = true + return false + end + end) + if not found then + return false + end + else + local s = uci:get_all(conf, section) + if not s or not check_uci_depends_options(conf, s, options) then + return false + end + end + end + + return true +end + +local function check_uci_depends(conf) + local uci = require "luci.model.uci" + + for config, values in pairs(conf) do + if values == true then + local found = false + uci:foreach(config, nil, function(s) + found = true + return false + end) + if not found then + return false + end + elseif type(values) == "table" then + if not check_uci_depends_section(config, values) then + return false + end + end + end + + return true +end + +local function check_acl_depends(require_groups, groups) + if type(require_groups) == "table" and #require_groups > 0 then + local writable = false + + for _, group in ipairs(require_groups) do + local read = false + local write = false + if type(groups) == "table" and type(groups[group]) == "table" then + for _, perm in ipairs(groups[group]) do + if perm == "read" then + read = true + elseif perm == "write" then + write = true + end + end + end + if not read and not write then + return nil + elseif write then + writable = true + end + end + + return writable + end + + return true +end + +local function check_depends(spec) + if type(spec.depends) ~= "table" then + return true + end + + if type(spec.depends.fs) == "table" then + local satisfied = false + local alternatives = (#spec.depends.fs > 0) and spec.depends.fs or { spec.depends.fs } + for _, alternative in ipairs(alternatives) do + if check_fs_depends(alternative) then + satisfied = true + break + end + end + if not satisfied then + return false + end + end + + if type(spec.depends.uci) == "table" then + local satisfied = false + local alternatives = (#spec.depends.uci > 0) and spec.depends.uci or { spec.depends.uci } + for _, alternative in ipairs(alternatives) do + if check_uci_depends(alternative) then + satisfied = true + break + end + end + if not satisfied then + return false + end + end + + return true +end + +local function target_to_json(target, module) + local action + + if target.type == "call" then + action = { + ["type"] = "call", + ["module"] = module, + ["function"] = target.name, + ["parameters"] = target.argv + } + elseif target.type == "view" then + action = { + ["type"] = "view", + ["path"] = target.view + } + elseif target.type == "template" then + action = { + ["type"] = "template", + ["path"] = target.view + } + elseif target.type == "cbi" then + action = { + ["type"] = "cbi", + ["path"] = target.model, + ["config"] = target.config + } + elseif target.type == "form" then + action = { + ["type"] = "form", + ["path"] = target.model + } + elseif target.type == "firstchild" then + action = { + ["type"] = "firstchild" + } + elseif target.type == "firstnode" then + action = { + ["type"] = "firstchild", + ["recurse"] = true + } + elseif target.type == "arcombine" then + if type(target.targets) == "table" then + action = { + ["type"] = "arcombine", + ["targets"] = { + target_to_json(target.targets[1], module), + target_to_json(target.targets[2], module) + } + } + end + elseif target.type == "alias" then + action = { + ["type"] = "alias", + ["path"] = table.concat(target.req, "/") + } + elseif target.type == "rewrite" then + action = { + ["type"] = "rewrite", + ["path"] = table.concat(target.req, "/"), + ["remove"] = target.n + } + end + + if target.post and action then + action.post = target.post + end + + return action +end + +local function tree_to_json(node, json) + local fs = require "nixio.fs" + local util = require "luci.util" + + if type(node.nodes) == "table" then + for subname, subnode in pairs(node.nodes) do + local spec = { + title = xml.striptags(subnode.title), + order = subnode.order + } + + if subnode.leaf then + spec.wildcard = true + end + + if subnode.cors then + spec.cors = true + end + + if subnode.setuser then + spec.setuser = subnode.setuser + end + + if subnode.setgroup then + spec.setgroup = subnode.setgroup + end + + if type(subnode.target) == "table" then + spec.action = target_to_json(subnode.target, subnode.module) + end + + if type(subnode.file_depends) == "table" then + for _, v in ipairs(subnode.file_depends) do + spec.depends = spec.depends or {} + spec.depends.fs = spec.depends.fs or {} + + local ft = fs.stat(v, "type") + if ft == "dir" then + spec.depends.fs[v] = "directory" + elseif v:match("/s?bin/") then + spec.depends.fs[v] = "executable" + else + spec.depends.fs[v] = "file" + end + end + end + + if type(subnode.uci_depends) == "table" then + for k, v in pairs(subnode.uci_depends) do + spec.depends = spec.depends or {} + spec.depends.uci = spec.depends.uci or {} + spec.depends.uci[k] = v + end + end + + if type(subnode.acl_depends) == "table" then + for _, acl in ipairs(subnode.acl_depends) do + spec.depends = spec.depends or {} + spec.depends.acl = spec.depends.acl or {} + spec.depends.acl[#spec.depends.acl + 1] = acl + end + end + + if (subnode.sysauth_authenticator ~= nil) or + (subnode.sysauth ~= nil and subnode.sysauth ~= false) + then + if subnode.sysauth_authenticator == "htmlauth" then + spec.auth = { + login = true, + methods = { "cookie:sysauth" } + } + elseif subname == "rpc" and subnode.module == "luci.controller.rpc" then + spec.auth = { + login = false, + methods = { "query:auth", "cookie:sysauth" } + } + elseif subnode.module == "luci.controller.admin.uci" then + spec.auth = { + login = false, + methods = { "param:sid" } + } + end + elseif subnode.sysauth == false then + spec.auth = {} + end + + if not spec.action then + spec.title = nil + end + + spec.satisfied = check_depends(spec) + json.children = json.children or {} + json.children[subname] = tree_to_json(subnode, spec) + end + end + + return json +end + +function build_url(...) + local path = {...} + local url = { http.getenv("SCRIPT_NAME") or "" } + + local p + for _, p in ipairs(path) do + if p:match("^[a-zA-Z0-9_%-%.%%/,;]+$") then + url[#url+1] = "/" + url[#url+1] = p + end + end + + if #path == 0 then + url[#url+1] = "/" + end + + return table.concat(url, "") +end + + +function error404(message) + http.status(404, "Not Found") + message = message or "Not Found" + + local function render() + local template = require "luci.template" + template.render("error404", {message=message}) + end + + if not util.copcall(render) then + http.prepare_content("text/plain") + http.write(message) + end + + return false +end + +function error500(message) + util.perror(message) + if not context.template_header_sent then + http.status(500, "Internal Server Error") + http.prepare_content("text/plain") + http.write(message) + else + require("luci.template") + if not util.copcall(luci.template.render, "error500", {message=message}) then + http.prepare_content("text/plain") + http.write(message) + end + end + return false +end + +local function determine_request_language() + local conf = require "luci.config" + assert(conf.main, "/etc/config/luci seems to be corrupt, unable to find section 'main'") + + local lang = conf.main.lang or "auto" + if lang == "auto" then + local aclang = http.getenv("HTTP_ACCEPT_LANGUAGE") or "" + for aclang in aclang:gmatch("[%w_-]+") do + local country, culture = aclang:match("^([a-z][a-z])[_-]([a-zA-Z][a-zA-Z])$") + if country and culture then + local cc = "%s_%s" %{ country, culture:lower() } + if conf.languages[cc] then + lang = cc + break + elseif conf.languages[country] then + lang = country + break + end + elseif conf.languages[aclang] then + lang = aclang + break + end + end + end + + if lang == "auto" then + lang = i18n.default + end + + i18n.setlanguage(lang) +end + +function httpdispatch(request, prefix) + http.context.request = request + + local r = {} + context.request = r + + local pathinfo = http.urldecode(request:getenv("PATH_INFO") or "", true) + + if prefix then + for _, node in ipairs(prefix) do + r[#r+1] = node + end + end + + local node + for node in pathinfo:gmatch("[^/%z]+") do + r[#r+1] = node + end + + determine_request_language() + + local stat, err = util.coxpcall(function() + dispatch(context.request) + end, error500) + + http.close() + + --context._disable_memtrace() +end + +local function require_post_security(target, args) + if type(target) == "table" and target.type == "arcombine" and type(target.targets) == "table" then + return require_post_security((type(args) == "table" and #args > 0) and target.targets[2] or target.targets[1], args) + end + + if type(target) == "table" then + if type(target.post) == "table" then + local param_name, required_val, request_val + + for param_name, required_val in pairs(target.post) do + request_val = http.formvalue(param_name) + + if (type(required_val) == "string" and + request_val ~= required_val) or + (required_val == true and request_val == nil) + then + return false + end + end + + return true + end + + return (target.post == true) + end + + return false +end + +function test_post_security() + if http.getenv("REQUEST_METHOD") ~= "POST" then + http.status(405, "Method Not Allowed") + http.header("Allow", "POST") + return false + end + + if http.formvalue("token") ~= context.authtoken then + http.status(403, "Forbidden") + luci.template.render("csrftoken") + return false + end + + return true +end + +local function session_retrieve(sid, allowed_users) + local sdat = util.ubus("session", "get", { ubus_rpc_session = sid }) + local sacl = util.ubus("session", "access", { ubus_rpc_session = sid }) + + if type(sdat) == "table" and + type(sdat.values) == "table" and + type(sdat.values.token) == "string" and + (not allowed_users or + util.contains(allowed_users, sdat.values.username)) + then + uci:set_session_id(sid) + return sid, sdat.values, type(sacl) == "table" and sacl or {} + end + + return nil, nil, nil +end + +local function session_setup(user, pass) + local login = util.ubus("session", "login", { + username = user, + password = pass, + timeout = tonumber(luci.config.sauth.sessiontime) + }) + + local rp = context.requestpath + and table.concat(context.requestpath, "/") or "" + + if type(login) == "table" and + type(login.ubus_rpc_session) == "string" + then + util.ubus("session", "set", { + ubus_rpc_session = login.ubus_rpc_session, + values = { token = sys.uniqueid(16) } + }) + nixio.syslog("info", tostring("luci: accepted login on /%s for %s from %s\n" + %{ rp, user or "?", http.getenv("REMOTE_ADDR") or "?" })) + + return session_retrieve(login.ubus_rpc_session) + end + nixio.syslog("info", tostring("luci: failed login on /%s for %s from %s\n" + %{ rp, user or "?", http.getenv("REMOTE_ADDR") or "?" })) +end + +local function check_authentication(method) + local auth_type, auth_param = method:match("^(%w+):(.+)$") + local sid, sdat + + if auth_type == "cookie" then + sid = http.getcookie(auth_param) + elseif auth_type == "param" then + sid = http.formvalue(auth_param) + elseif auth_type == "query" then + sid = http.formvalue(auth_param, true) + end + + return session_retrieve(sid) +end + +local function get_children(node) + local children = {} + + if not node.wildcard and type(node.children) == "table" then + for name, child in pairs(node.children) do + children[#children+1] = { + name = name, + node = child, + order = child.order or 1000 + } + end + + table.sort(children, function(a, b) + if a.order == b.order then + return a.name < b.name + else + return a.order < b.order + end + end) + end + + return children +end + +local function find_subnode(root, prefix, recurse, descended) + local children = get_children(root) + + if #children > 0 and (not descended or recurse) then + local sub_path = { unpack(prefix) } + + if recurse == false then + recurse = nil + end + + for _, child in ipairs(children) do + sub_path[#prefix+1] = child.name + + local res_path = find_subnode(child.node, sub_path, recurse, true) + + if res_path then + return res_path + end + end + end + + if descended then + if not recurse or + root.action.type == "cbi" or + root.action.type == "form" or + root.action.type == "view" or + root.action.type == "template" or + root.action.type == "arcombine" + then + return prefix + end + end +end + +local function merge_trees(node_a, node_b) + for k, v in pairs(node_b) do + if k == "children" then + node_a.children = node_a.children or {} + + for name, spec in pairs(v) do + node_a.children[name] = merge_trees(node_a.children[name] or {}, spec) + end + else + node_a[k] = v + end + end + + if type(node_a.action) == "table" and + node_a.action.type == "firstchild" and + node_a.children == nil + then + node_a.satisfied = false + end + + return node_a +end + +local function apply_tree_acls(node, acl) + if type(node.children) == "table" then + for _, child in pairs(node.children) do + apply_tree_acls(child, acl) + end + end + + local perm + if type(node.depends) == "table" then + perm = check_acl_depends(node.depends.acl, acl["access-group"]) + else + perm = true + end + + if perm == nil then + node.satisfied = false + elseif perm == false then + node.readonly = true + end +end + +function menu_json(acl) + local tree = context.tree or createtree() + local lua_tree = tree_to_json(tree, { + action = { + ["type"] = "firstchild", + ["recurse"] = true + } + }) + + local json_tree = createtree_json() + local menu_tree = merge_trees(lua_tree, json_tree) + + if acl then + apply_tree_acls(menu_tree, acl) + end + + return menu_tree +end + +local function init_template_engine(ctx) + local tpl = require "luci.template" + local media = luci.config.main.mediaurlbase + + if not pcall(tpl.Template, "themes/%s/header" % fs.basename(media)) then + media = nil + for name, theme in pairs(luci.config.themes) do + if name:sub(1,1) ~= "." and pcall(tpl.Template, + "themes/%s/header" % fs.basename(theme)) then + media = theme + end + end + assert(media, "No valid theme found") + end + + local function _ifattr(cond, key, val, noescape) + if cond then + local env = getfenv(3) + local scope = (type(env.self) == "table") and env.self + if type(val) == "table" then + if not next(val) then + return '' + else + val = util.serialize_json(val) + end + end + + val = tostring(val or + (type(env[key]) ~= "function" and env[key]) or + (scope and type(scope[key]) ~= "function" and scope[key]) or "") + + if noescape ~= true then + val = xml.pcdata(val) + end + + return string.format(' %s="%s"', tostring(key), val) + else + return '' + end + end + + tpl.context.viewns = setmetatable({ + write = http.write; + include = function(name) tpl.Template(name):render(getfenv(2)) end; + translate = i18n.translate; + translatef = i18n.translatef; + export = function(k, v) if tpl.context.viewns[k] == nil then tpl.context.viewns[k] = v end end; + striptags = xml.striptags; + pcdata = xml.pcdata; + media = media; + theme = fs.basename(media); + resource = luci.config.main.resourcebase; + ifattr = function(...) return _ifattr(...) end; + attr = function(...) return _ifattr(true, ...) end; + url = build_url; + }, {__index=function(tbl, key) + if key == "controller" then + return build_url() + elseif key == "REQUEST_URI" then + return build_url(unpack(ctx.requestpath)) + elseif key == "FULL_REQUEST_URI" then + local url = { http.getenv("SCRIPT_NAME") or "", http.getenv("PATH_INFO") } + local query = http.getenv("QUERY_STRING") + if query and #query > 0 then + url[#url+1] = "?" + url[#url+1] = query + end + return table.concat(url, "") + elseif key == "token" then + return ctx.authtoken + else + return rawget(tbl, key) or _G[key] + end + end}) + + return tpl +end + +function dispatch(request) + --context._disable_memtrace = require "luci.debug".trap_memtrace("l") + local ctx = context + + local auth, cors, suid, sgid + local menu = menu_json() + local page = menu + + local requested_path_full = {} + local requested_path_node = {} + local requested_path_args = {} + + local required_path_acls = {} + + for i, s in ipairs(request) do + if type(page.children) ~= "table" or not page.children[s] then + page = nil + break + end + + if not page.children[s].satisfied then + page = nil + break + end + + page = page.children[s] + auth = page.auth or auth + cors = page.cors or cors + suid = page.setuser or suid + sgid = page.setgroup or sgid + + if type(page.depends) == "table" and type(page.depends.acl) == "table" then + for _, group in ipairs(page.depends.acl) do + local found = false + for _, item in ipairs(required_path_acls) do + if item == group then + found = true + break + end + end + if not found then + required_path_acls[#required_path_acls + 1] = group + end + end + end + + requested_path_full[i] = s + requested_path_node[i] = s + + if page.wildcard then + for j = i + 1, #request do + requested_path_args[j - i] = request[j] + requested_path_full[j] = request[j] + end + break + end + end + + local tpl = init_template_engine(ctx) + + ctx.args = requested_path_args + ctx.path = requested_path_node + ctx.dispatched = page + + ctx.requestpath = ctx.requestpath or requested_path_full + ctx.requestargs = ctx.requestargs or requested_path_args + ctx.requested = ctx.requested or page + + if type(auth) == "table" and type(auth.methods) == "table" and #auth.methods > 0 then + local sid, sdat, sacl + for _, method in ipairs(auth.methods) do + sid, sdat, sacl = check_authentication(method) + + if sid and sdat and sacl then + break + end + end + + if not (sid and sdat and sacl) and auth.login then + local user = http.getenv("HTTP_AUTH_USER") + local pass = http.getenv("HTTP_AUTH_PASS") + + if user == nil and pass == nil then + user = http.formvalue("luci_username") + pass = http.formvalue("luci_password") + end + + if user and pass then + sid, sdat, sacl = session_setup(user, pass) + end + + if not sid then + context.path = {} + + http.status(403, "Forbidden") + http.header("X-LuCI-Login-Required", "yes") + + local scope = { duser = "root", fuser = user } + local ok, res = util.copcall(tpl.render_string, [[<% include("themes/" .. theme .. "/sysauth") %>]], scope) + if ok then + return res + end + return tpl.render("sysauth", scope) + end + + http.header("Set-Cookie", 'sysauth=%s; path=%s; SameSite=Strict; HttpOnly%s' %{ + sid, build_url(), http.getenv("HTTPS") == "on" and "; secure" or "" + }) + + http.redirect(build_url(unpack(ctx.requestpath))) + return + end + + if not sid or not sdat or not sacl then + http.status(403, "Forbidden") + http.header("X-LuCI-Login-Required", "yes") + return + end + + ctx.authsession = sid + ctx.authtoken = sdat.token + ctx.authuser = sdat.username + ctx.authacl = sacl + end + + if #required_path_acls > 0 then + local perm = check_acl_depends(required_path_acls, ctx.authacl and ctx.authacl["access-group"]) + if perm == nil then + http.status(403, "Forbidden") + return + end + + if page then + page.readonly = not perm + end + end + + local action = (page and type(page.action) == "table") and page.action or {} + + if action.type == "arcombine" then + action = (#requested_path_args > 0) and action.targets[2] or action.targets[1] + end + + if cors and http.getenv("REQUEST_METHOD") == "OPTIONS" then + luci.http.status(200, "OK") + luci.http.header("Access-Control-Allow-Origin", http.getenv("HTTP_ORIGIN") or "*") + luci.http.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS") + return + end + + if require_post_security(action) then + if not test_post_security() then + return + end + end + + if sgid then + sys.process.setgroup(sgid) + end + + if suid then + sys.process.setuser(suid) + end + + if action.type == "view" then + tpl.render("view", { view = action.path }) + + elseif action.type == "call" then + local ok, mod = util.copcall(require, action.module) + if not ok then + error500(mod) + return + end + + local func = mod[action["function"]] + + assert(func ~= nil, + 'Cannot resolve function "' .. action["function"] .. '". Is it misspelled or local?') + + assert(type(func) == "function", + 'The symbol "' .. action["function"] .. '" does not refer to a function but data ' .. + 'of type "' .. type(func) .. '".') + + local argv = (type(action.parameters) == "table" and #action.parameters > 0) and { unpack(action.parameters) } or {} + for _, s in ipairs(requested_path_args) do + argv[#argv + 1] = s + end + + local ok, err = util.copcall(func, unpack(argv)) + if not ok then + error500(err) + end + + elseif action.type == "firstchild" then + local sub_request = find_subnode(page, requested_path_full, action.recurse) + if sub_request then + dispatch(sub_request) + else + tpl.render("empty_node_placeholder", getfenv(1)) + end + + elseif action.type == "alias" then + local sub_request = {} + for name in action.path:gmatch("[^/]+") do + sub_request[#sub_request + 1] = name + end + + for _, s in ipairs(requested_path_args) do + sub_request[#sub_request + 1] = s + end + + dispatch(sub_request) + + elseif action.type == "rewrite" then + local sub_request = { unpack(request) } + for i = 1, action.remove do + table.remove(sub_request, 1) + end + + local n = 1 + for s in action.path:gmatch("[^/]+") do + table.insert(sub_request, n, s) + n = n + 1 + end + + for _, s in ipairs(requested_path_args) do + sub_request[#sub_request + 1] = s + end + + dispatch(sub_request) + + elseif action.type == "template" then + tpl.render(action.path, getfenv(1)) + + elseif action.type == "cbi" then + _cbi({ config = action.config, model = action.path }, unpack(requested_path_args)) + + elseif action.type == "form" then + _form({ model = action.path }, unpack(requested_path_args)) + + else + local root = find_subnode(menu, {}, true) + if not root then + error404("No root node was registered, this usually happens if no module was installed.\n" .. + "Install luci-mod-admin-full and retry. " .. + "If the module is already installed, try removing the /tmp/luci-indexcache file.") + else + error404("No page is registered at '/" .. table.concat(requested_path_full, "/") .. "'.\n" .. + "If this url belongs to an extension, make sure it is properly installed.\n" .. + "If the extension was recently installed, try removing the /tmp/luci-indexcache file.") + end + end +end + +local function hash_filelist(files) + local fprint = {} + local n = 0 + + for i, file in ipairs(files) do + local st = fs.stat(file) + if st then + fprint[n + 1] = '%x' % st.ino + fprint[n + 2] = '%x' % st.mtime + fprint[n + 3] = '%x' % st.size + n = n + 3 + end + end + + return nixio.crypt(table.concat(fprint, "|"), "$1$"):sub(5):gsub("/", ".") +end + +local function read_cachefile(file, reader) + local euid = sys.process.info("uid") + local fuid = fs.stat(file, "uid") + local mode = fs.stat(file, "modestr") + + if euid ~= fuid or mode ~= "rw-------" then + return nil + end + + return reader(file) +end + +function createindex() + local controllers = { } + local base = "%s/controller/" % util.libpath() + local _, path + + for path in (fs.glob("%s*.lua" % base) or function() end) do + controllers[#controllers+1] = path + end + + for path in (fs.glob("%s*/*.lua" % base) or function() end) do + controllers[#controllers+1] = path + end + + local cachefile + + if indexcache then + cachefile = "%s.%s.lua" %{ indexcache, hash_filelist(controllers) } + + local res = read_cachefile(cachefile, function(path) return loadfile(path)() end) + if res then + index = res + return res + end + + for file in (fs.glob("%s.*.lua" % indexcache) or function() end) do + fs.unlink(file) + end + end + + index = {} + + for _, path in ipairs(controllers) do + local modname = "luci.controller." .. path:sub(#base+1, #path-4):gsub("/", ".") + local mod = require(modname) + assert(mod ~= true, + "Invalid controller file found\n" .. + "The file '" .. path .. "' contains an invalid module line.\n" .. + "Please verify whether the module name is set to '" .. modname .. + "' - It must correspond to the file path!") + + local idx = mod.index + if type(idx) == "function" then + index[modname] = idx + end + end + + if cachefile then + local f = nixio.open(cachefile, "w", 600) + f:writeall(util.get_bytecode(index)) + f:close() + end +end + +function createtree_json() + local json = require "luci.jsonc" + local tree = {} + + local schema = { + action = "table", + auth = "table", + cors = "boolean", + depends = "table", + order = "number", + setgroup = "string", + setuser = "string", + title = "string", + wildcard = "boolean" + } + + local files = {} + local cachefile + + for file in (fs.glob("/usr/share/luci/menu.d/*.json") or function() end) do + files[#files+1] = file + end + + if indexcache then + cachefile = "%s.%s.json" %{ indexcache, hash_filelist(files) } + + local res = read_cachefile(cachefile, function(path) return json.parse(fs.readfile(path) or "") end) + if res then + return res + end + + for file in (fs.glob("%s.*.json" % indexcache) or function() end) do + fs.unlink(file) + end + end + + for _, file in ipairs(files) do + local data = json.parse(fs.readfile(file) or "") + if type(data) == "table" then + for path, spec in pairs(data) do + if type(spec) == "table" then + local node = tree + + for s in path:gmatch("[^/]+") do + if s == "*" then + node.wildcard = true + break + end + + node.children = node.children or {} + node.children[s] = node.children[s] or {} + node = node.children[s] + end + + if node ~= tree then + for k, t in pairs(schema) do + if type(spec[k]) == t then + node[k] = spec[k] + end + end + + node.satisfied = check_depends(spec) + end + end + end + end + end + + if cachefile then + local f = nixio.open(cachefile, "w", 600) + f:writeall(json.stringify(tree)) + f:close() + end + + return tree +end + +-- Build the index before if it does not exist yet. +function createtree() + if not index then + createindex() + end + + local ctx = context + local tree = {nodes={}, inreq=true} + + ctx.treecache = setmetatable({}, {__mode="v"}) + ctx.tree = tree + + local scope = setmetatable({}, {__index = luci.dispatcher}) + + for k, v in pairs(index) do + scope._NAME = k + setfenv(v, scope) + v() + end + + return tree +end + +function assign(path, clone, title, order) + local obj = node(unpack(path)) + obj.nodes = nil + obj.module = nil + + obj.title = title + obj.order = order + + setmetatable(obj, {__index = _create_node(clone)}) + + return obj +end + +function entry(path, target, title, order) + local c = node(unpack(path)) + + c.target = target + c.title = title + c.order = order + c.module = getfenv(2)._NAME + + return c +end + +-- enabling the node. +function get(...) + return _create_node({...}) +end + +function node(...) + local c = _create_node({...}) + + c.module = getfenv(2)._NAME + c.auto = nil + + return c +end + +function lookup(...) + local i, path = nil, {} + for i = 1, select('#', ...) do + local name, arg = nil, tostring(select(i, ...)) + for name in arg:gmatch("[^/]+") do + path[#path+1] = name + end + end + + for i = #path, 1, -1 do + local node = context.treecache[table.concat(path, ".", 1, i)] + if node and (i == #path or node.leaf) then + return node, build_url(unpack(path)) + end + end +end + +function _create_node(path) + if #path == 0 then + return context.tree + end + + local name = table.concat(path, ".") + local c = context.treecache[name] + + if not c then + local last = table.remove(path) + local parent = _create_node(path) + + c = {nodes={}, auto=true, inreq=true} + + parent.nodes[last] = c + context.treecache[name] = c + end + + return c +end + +-- Subdispatchers -- + +function firstchild() + return { type = "firstchild" } +end + +function firstnode() + return { type = "firstnode" } +end + +function alias(...) + return { type = "alias", req = { ... } } +end + +function rewrite(n, ...) + return { type = "rewrite", n = n, req = { ... } } +end + +function call(name, ...) + return { type = "call", argv = {...}, name = name } +end + +function post_on(params, name, ...) + return { + type = "call", + post = params, + argv = { ... }, + name = name + } +end + +function post(...) + return post_on(true, ...) +end + + +function template(name) + return { type = "template", view = name } +end + +function view(name) + return { type = "view", view = name } +end + + +function _cbi(self, ...) + local cbi = require "luci.cbi" + local tpl = require "luci.template" + local http = require "luci.http" + local util = require "luci.util" + + local config = self.config or {} + local maps = cbi.load(self.model, ...) + + local state = nil + + local function has_uci_access(config, level) + local rv = util.ubus("session", "access", { + ubus_rpc_session = context.authsession, + scope = "uci", object = config, + ["function"] = level + }) + + return (type(rv) == "table" and rv.access == true) or false + end + + local i, res + for i, res in ipairs(maps) do + if util.instanceof(res, cbi.SimpleForm) then + io.stderr:write("Model %s returns SimpleForm but is dispatched via cbi(),\n" + % self.model) + + io.stderr:write("please change %s to use the form() action instead.\n" + % table.concat(context.request, "/")) + end + + res.flow = config + local cstate = res:parse() + if cstate and (not state or cstate < state) then + state = cstate + end + end + + local function _resolve_path(path) + return type(path) == "table" and build_url(unpack(path)) or path + end + + if config.on_valid_to and state and state > 0 and state < 2 then + http.redirect(_resolve_path(config.on_valid_to)) + return + end + + if config.on_changed_to and state and state > 1 then + http.redirect(_resolve_path(config.on_changed_to)) + return + end + + if config.on_success_to and state and state > 0 then + http.redirect(_resolve_path(config.on_success_to)) + return + end + + if config.state_handler then + if not config.state_handler(state, maps) then + return + end + end + + http.header("X-CBI-State", state or 0) + + if not config.noheader then + tpl.render("cbi/header", {state = state}) + end + + local redirect + local messages + local applymap = false + local pageaction = true + local parsechain = { } + local writable = false + + for i, res in ipairs(maps) do + if res.apply_needed and res.parsechain then + local c + for _, c in ipairs(res.parsechain) do + parsechain[#parsechain+1] = c + end + applymap = true + end + + if res.redirect then + redirect = redirect or res.redirect + end + + if res.pageaction == false then + pageaction = false + end + + if res.message then + messages = messages or { } + messages[#messages+1] = res.message + end + end + + for i, res in ipairs(maps) do + local is_readable_map = has_uci_access(res.config, "read") + local is_writable_map = has_uci_access(res.config, "write") + + writable = writable or is_writable_map + + res:render({ + firstmap = (i == 1), + redirect = redirect, + messages = messages, + pageaction = pageaction, + parsechain = parsechain, + readable = is_readable_map, + writable = is_writable_map + }) + end + + if not config.nofooter then + tpl.render("cbi/footer", { + flow = config, + pageaction = pageaction, + redirect = redirect, + state = state, + autoapply = config.autoapply, + trigger_apply = applymap, + writable = writable + }) + end +end + +function cbi(model, config) + return { + type = "cbi", + post = { ["cbi.submit"] = true }, + config = config, + model = model + } +end + + +function arcombine(trg1, trg2) + return { + type = "arcombine", + env = getfenv(), + targets = {trg1, trg2} + } +end + + +function _form(self, ...) + local cbi = require "luci.cbi" + local tpl = require "luci.template" + local http = require "luci.http" + + local maps = luci.cbi.load(self.model, ...) + local state = nil + + local i, res + for i, res in ipairs(maps) do + local cstate = res:parse() + if cstate and (not state or cstate < state) then + state = cstate + end + end + + http.header("X-CBI-State", state or 0) + tpl.render("header") + for i, res in ipairs(maps) do + res:render() + end + tpl.render("footer") +end + +function form(model) + return { + type = "form", + post = { ["cbi.submit"] = true }, + model = model + } +end + +translate = i18n.translate + +-- This function does not actually translate the given argument but +-- is used by build/i18n-scan.pl to find translatable entries. +function _(text) + return text +end diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/dispatcher.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/dispatcher.luac new file mode 100644 index 0000000000000000000000000000000000000000..caad21ada0331299bf427618eb8b7b7d8cc73294 GIT binary patch literal 62557 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 Bc9H-9 literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/http.lua b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/http.lua new file mode 100644 index 000000000000..20b55f2854ff --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/http.lua @@ -0,0 +1,554 @@ +-- Copyright 2008 Steven Barth +-- Copyright 2010-2018 Jo-Philipp Wich +-- Licensed to the public under the Apache License 2.0. + +local util = require "luci.util" +local coroutine = require "coroutine" +local table = require "table" +local lhttp = require "lucihttp" +local nixio = require "nixio" +local ltn12 = require "luci.ltn12" + +local table, ipairs, pairs, type, tostring, tonumber, error = + table, ipairs, pairs, type, tostring, tonumber, error + +module "luci.http" + +HTTP_MAX_CONTENT = 1024*100 -- 100 kB maximum content size + +context = util.threadlocal() + +Request = util.class() +function Request.__init__(self, env, sourcein, sinkerr) + self.input = sourcein + self.error = sinkerr + + + -- File handler nil by default to let .content() work + self.filehandler = nil + + -- HTTP-Message table + self.message = { + env = env, + headers = {}, + params = urldecode_params(env.QUERY_STRING or ""), + } + + self.parsed_input = false +end + +function Request.formvalue(self, name, noparse) + if not noparse and not self.parsed_input then + self:_parse_input() + end + + if name then + return self.message.params[name] + else + return self.message.params + end +end + +function Request.formvaluetable(self, prefix) + local vals = {} + prefix = prefix and prefix .. "." or "." + + if not self.parsed_input then + self:_parse_input() + end + + local void = self.message.params[nil] + for k, v in pairs(self.message.params) do + if k:find(prefix, 1, true) == 1 then + vals[k:sub(#prefix + 1)] = tostring(v) + end + end + + return vals +end + +function Request.content(self) + if not self.parsed_input then + self:_parse_input() + end + + return self.message.content, self.message.content_length +end + +function Request.getcookie(self, name) + return lhttp.header_attribute("cookie; " .. (self:getenv("HTTP_COOKIE") or ""), name) +end + +function Request.getenv(self, name) + if name then + return self.message.env[name] + else + return self.message.env + end +end + +function Request.setfilehandler(self, callback) + self.filehandler = callback + + if not self.parsed_input then + return + end + + -- If input has already been parsed then uploads are stored as unlinked + -- temporary files pointed to by open file handles in the parameter + -- value table. Loop all params, and invoke the file callback for any + -- param with an open file handle. + local name, value + for name, value in pairs(self.message.params) do + if type(value) == "table" then + while value.fd do + local data = value.fd:read(1024) + local eof = (not data or data == "") + + callback(value, data, eof) + + if eof then + value.fd:close() + value.fd = nil + end + end + end + end +end + +function Request._parse_input(self) + parse_message_body( + self.input, + self.message, + self.filehandler + ) + self.parsed_input = true +end + +function close() + if not context.eoh then + context.eoh = true + coroutine.yield(3) + end + + if not context.closed then + context.closed = true + coroutine.yield(5) + end +end + +function content() + return context.request:content() +end + +function formvalue(name, noparse) + return context.request:formvalue(name, noparse) +end + +function formvaluetable(prefix) + return context.request:formvaluetable(prefix) +end + +function getcookie(name) + return context.request:getcookie(name) +end + +-- or the environment table itself. +function getenv(name) + return context.request:getenv(name) +end + +function setfilehandler(callback) + return context.request:setfilehandler(callback) +end + +function header(key, value) + if not context.headers then + context.headers = {} + end + context.headers[key:lower()] = value + coroutine.yield(2, key, value) +end + +function prepare_content(mime) + if not context.headers or not context.headers["content-type"] then + if mime == "application/xhtml+xml" then + if not getenv("HTTP_ACCEPT") or + not getenv("HTTP_ACCEPT"):find("application/xhtml+xml", nil, true) then + mime = "text/html; charset=UTF-8" + end + header("Vary", "Accept") + end + header("Content-Type", mime) + end +end + +function source() + return context.request.input +end + +function status(code, message) + code = code or 200 + message = message or "OK" + context.status = code + coroutine.yield(1, code, message) +end + +-- This function is as a valid LTN12 sink. +-- If the content chunk is nil this function will automatically invoke close. +function write(content, src_err) + if not content then + if src_err then + error(src_err) + else + close() + end + return true + elseif #content == 0 then + return true + else + if not context.eoh then + if not context.status then + status() + end + if not context.headers or not context.headers["content-type"] then + header("Content-Type", "text/html; charset=utf-8") + end + if not context.headers["cache-control"] then + header("Cache-Control", "no-cache") + header("Expires", "0") + end + if not context.headers["x-frame-options"] then + header("X-Frame-Options", "SAMEORIGIN") + end + if not context.headers["x-xss-protection"] then + header("X-XSS-Protection", "1; mode=block") + end + if not context.headers["x-content-type-options"] then + header("X-Content-Type-Options", "nosniff") + end + + context.eoh = true + coroutine.yield(3) + end + coroutine.yield(4, content) + return true + end +end + +function splice(fd, size) + coroutine.yield(6, fd, size) +end + +function redirect(url) + if url == "" then url = "/" end + status(302, "Found") + header("Location", url) + close() +end + +function build_querystring(q) + local s, n, k, v = {}, 1, nil, nil + + for k, v in pairs(q) do + s[n+0] = (n == 1) and "?" or "&" + s[n+1] = util.urlencode(k) + s[n+2] = "=" + s[n+3] = util.urlencode(v) + n = n + 4 + end + + return table.concat(s, "") +end + +urldecode = util.urldecode + +urlencode = util.urlencode + +function write_json(x) + util.serialize_json(x, write) +end + +-- from given url or string. Returns a table with urldecoded values. +-- Simple parameters are stored as string values associated with the parameter +-- name within the table. Parameters with multiple values are stored as array +-- containing the corresponding values. +function urldecode_params(url, tbl) + local parser, name + local params = tbl or { } + + parser = lhttp.urlencoded_parser(function (what, buffer, length) + if what == parser.TUPLE then + name, value = nil, nil + elseif what == parser.NAME then + name = lhttp.urldecode(buffer) + elseif what == parser.VALUE and name then + params[name] = lhttp.urldecode(buffer) or "" + end + + return true + end) + + if parser then + parser:parse((url or ""):match("[^?]*$")) + parser:parse(nil) + end + + return params +end + +-- separated by "&". Tables are encoded as parameters with multiple values by +-- repeating the parameter name with each value. +function urlencode_params(tbl) + local k, v + local n, enc = 1, {} + for k, v in pairs(tbl) do + if type(v) == "table" then + local i, v2 + for i, v2 in ipairs(v) do + if enc[1] then + enc[n] = "&" + n = n + 1 + end + + enc[n+0] = lhttp.urlencode(k) + enc[n+1] = "=" + enc[n+2] = lhttp.urlencode(v2) + n = n + 3 + end + else + if enc[1] then + enc[n] = "&" + n = n + 1 + end + + enc[n+0] = lhttp.urlencode(k) + enc[n+1] = "=" + enc[n+2] = lhttp.urlencode(v) + n = n + 3 + end + end + + return table.concat(enc, "") +end + +-- Content-Type. Stores all extracted data associated with its parameter name +-- in the params table within the given message object. Multiple parameter +-- values are stored as tables, ordinary ones as strings. +-- If an optional file callback function is given then it is fed with the +-- file contents chunk by chunk and only the extracted file name is stored +-- within the params table. The callback function will be called subsequently +-- with three arguments: +-- o Table containing decoded (name, file) and raw (headers) mime header data +-- o String value containing a chunk of the file data +-- o Boolean which indicates whether the current chunk is the last one (eof) +function mimedecode_message_body(src, msg, file_cb) + local parser, header, field + local len, maxlen = 0, tonumber(msg.env.CONTENT_LENGTH or nil) + + parser, err = lhttp.multipart_parser(msg.env.CONTENT_TYPE, function (what, buffer, length) + if what == parser.PART_INIT then + field = { } + + elseif what == parser.HEADER_NAME then + header = buffer:lower() + + elseif what == parser.HEADER_VALUE and header then + if header:lower() == "content-disposition" and + lhttp.header_attribute(buffer, nil) == "form-data" + then + field.name = lhttp.header_attribute(buffer, "name") + field.file = lhttp.header_attribute(buffer, "filename") + field[1] = field.file + end + + if field.headers then + field.headers[header] = buffer + else + field.headers = { [header] = buffer } + end + + elseif what == parser.PART_BEGIN then + return not field.file + + elseif what == parser.PART_DATA and field.name and length > 0 then + if field.file then + if file_cb then + file_cb(field, buffer, false) + msg.params[field.name] = msg.params[field.name] or field + else + if not field.fd then + field.fd = nixio.mkstemp(field.name) + end + + if field.fd then + field.fd:write(buffer) + msg.params[field.name] = msg.params[field.name] or field + end + end + else + field.value = buffer + end + + elseif what == parser.PART_END and field.name then + if field.file and msg.params[field.name] then + if file_cb then + file_cb(field, "", true) + elseif field.fd then + field.fd:seek(0, "set") + end + else + local val = msg.params[field.name] + + if type(val) == "table" then + val[#val+1] = field.value or "" + elseif val ~= nil then + msg.params[field.name] = { val, field.value or "" } + else + msg.params[field.name] = field.value or "" + end + end + + field = nil + + elseif what == parser.ERROR then + err = buffer + end + + return true + end, HTTP_MAX_CONTENT) + + return ltn12.pump.all(src, function (chunk) + len = len + (chunk and #chunk or 0) + + if maxlen and len > maxlen + 2 then + return nil, "Message body size exceeds Content-Length" + end + + if not parser or not parser:parse(chunk) then + return nil, err + end + + return true + end) +end + +-- Content-Type. Stores all extracted data associated with its parameter name +-- in the params table within the given message object. Multiple parameter +-- values are stored as tables, ordinary ones as strings. +function urldecode_message_body(src, msg) + local err, name, value, parser + local len, maxlen = 0, tonumber(msg.env.CONTENT_LENGTH or nil) + + parser = lhttp.urlencoded_parser(function (what, buffer, length) + if what == parser.TUPLE then + name, value = nil, nil + elseif what == parser.NAME then + name = lhttp.urldecode(buffer, lhttp.DECODE_PLUS) + elseif what == parser.VALUE and name then + local val = msg.params[name] + + if type(val) == "table" then + val[#val+1] = lhttp.urldecode(buffer, lhttp.DECODE_PLUS) or "" + elseif val ~= nil then + msg.params[name] = { val, lhttp.urldecode(buffer, lhttp.DECODE_PLUS) or "" } + else + msg.params[name] = lhttp.urldecode(buffer, lhttp.DECODE_PLUS) or "" + end + elseif what == parser.ERROR then + err = buffer + end + + return true + end, HTTP_MAX_CONTENT) + + return ltn12.pump.all(src, function (chunk) + len = len + (chunk and #chunk or 0) + + if maxlen and len > maxlen + 2 then + return nil, "Message body size exceeds Content-Length" + elseif len > HTTP_MAX_CONTENT then + return nil, "Message body size exceeds maximum allowed length" + end + + if not parser or not parser:parse(chunk) then + return nil, err + end + + return true + end) +end + +-- This function will examine the Content-Type within the given message object +-- to select the appropriate content decoder. +-- Currently the application/x-www-urlencoded and application/form-data +-- mime types are supported. If the encountered content encoding can't be +-- handled then the whole message body will be stored unaltered as "content" +-- property within the given message object. +function parse_message_body(src, msg, filecb) + if msg.env.CONTENT_LENGTH or msg.env.REQUEST_METHOD == "POST" then + local ctype = lhttp.header_attribute(msg.env.CONTENT_TYPE, nil) + + -- Is it multipart/mime ? + if ctype == "multipart/form-data" then + return mimedecode_message_body(src, msg, filecb) + + -- Is it application/x-www-form-urlencoded ? + elseif ctype == "application/x-www-form-urlencoded" then + return urldecode_message_body(src, msg) + + end + + -- Unhandled encoding + -- If a file callback is given then feed it chunk by chunk, else + -- store whole buffer in message.content + local sink + + -- If we have a file callback then feed it + if type(filecb) == "function" then + local meta = { + name = "raw", + encoding = msg.env.CONTENT_TYPE + } + sink = function( chunk ) + if chunk then + return filecb(meta, chunk, false) + else + return filecb(meta, nil, true) + end + end + -- ... else append to .content + else + msg.content = "" + msg.content_length = 0 + + sink = function( chunk ) + if chunk then + if ( msg.content_length + #chunk ) <= HTTP_MAX_CONTENT then + msg.content = msg.content .. chunk + msg.content_length = msg.content_length + #chunk + return true + else + return nil, "POST data exceeds maximum allowed length" + end + end + return true + end + end + + -- Pump data... + while true do + local ok, err = ltn12.pump.step( src, sink ) + + if not ok and err then + return nil, err + elseif not ok then -- eof + return true + end + end + + return true + end + + return false +end diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/http.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/http.luac new file mode 100644 index 0000000000000000000000000000000000000000..fb8332775b74cb6b30f94479a30578f6c314109c GIT binary patch literal 21596 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 DRonkP literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/i18n.lua b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/i18n.lua new file mode 100644 index 000000000000..323912b65022 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/i18n.lua @@ -0,0 +1,55 @@ +-- Copyright 2008 Steven Barth +-- Licensed to the public under the Apache License 2.0. + +local tparser = require "luci.template.parser" +local util = require "luci.util" +local tostring = tostring + +module "luci.i18n" + +i18ndir = util.libpath() .. "/i18n/" +context = util.threadlocal() +default = "en" + + +function setlanguage(lang) + local code, subcode = lang:match("^([A-Za-z][A-Za-z])[%-_]([A-Za-z][A-Za-z])$") + if not (code and subcode) then + subcode = lang:match("^([A-Za-z][A-Za-z])$") + if not subcode then + return nil + end + end + + context.parent = code and code:lower() + context.lang = context.parent and context.parent.."-"..subcode:lower() or subcode:lower() + + if tparser.load_catalog(context.lang, i18ndir) and + tparser.change_catalog(context.lang) + then + return context.lang + + elseif context.parent then + if tparser.load_catalog(context.parent, i18ndir) and + tparser.change_catalog(context.parent) + then + return context.parent + end + end + + return nil +end + +function translate(key) + return tparser.translate(key) or key +end + +function translatef(key, ...) + return tostring(translate(key)):format(...) +end + +function dump() + local rv = {} + tparser.get_translations(function(k, v) rv[k] = v end) + return rv +end diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/i18n.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/i18n.luac new file mode 100644 index 0000000000000000000000000000000000000000..a3a32578b3b481318eb57b50661ddc07f9bded91 GIT binary patch literal 2389 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 literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/ltn12.lua b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/ltn12.lua new file mode 100644 index 000000000000..3a7268ccaef6 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/ltn12.lua @@ -0,0 +1,316 @@ +--[[ +LuaSocket 2.0.2 license +Copyright � 2004-2007 Diego Nehab + +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. +]]-- +--[[ + Changes made by LuCI project: + * Renamed to luci.ltn12 to avoid collisions with luasocket + * Added inline documentation +]]-- +----------------------------------------------------------------------------- +-- LTN12 - Filters, sources, sinks and pumps. +-- LuaSocket toolkit. +-- Author: Diego Nehab +-- RCS ID: $Id$ +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module +----------------------------------------------------------------------------- +local string = require("string") +local table = require("table") +local base = _G + +-- See http://lua-users.org/wiki/FiltersSourcesAndSinks for design concepts +module("luci.ltn12") + +filter = {} +source = {} +sink = {} +pump = {} + +-- 2048 seems to be better in windows... +BLOCKSIZE = 2048 +_VERSION = "LTN12 1.0.1" + +----------------------------------------------------------------------------- +-- Filter stuff +----------------------------------------------------------------------------- + + +-- by passing it each chunk and updating a context between calls. +function filter.cycle(low, ctx, extra) + base.assert(low) + return function(chunk) + local ret + ret, ctx = low(ctx, chunk, extra) + return ret + end +end + +-- (thanks to Wim Couwenberg) +function filter.chain(...) + local n = table.getn(arg) + local top, index = 1, 1 + local retry = "" + return function(chunk) + retry = chunk and retry + while true do + if index == top then + chunk = arg[index](chunk) + if chunk == "" or top == n then return chunk + elseif chunk then index = index + 1 + else + top = top+1 + index = top + end + else + chunk = arg[index](chunk or "") + if chunk == "" then + index = index - 1 + chunk = retry + elseif chunk then + if index == n then return chunk + else index = index + 1 end + else base.error("filter returned inappropriate nil") end + end + end + end +end + +----------------------------------------------------------------------------- +-- Source stuff +----------------------------------------------------------------------------- + + +-- create an empty source +local function empty() + return nil +end + +function source.empty() + return empty +end + +function source.error(err) + return function() + return nil, err + end +end + +function source.file(handle, io_err) + if handle then + return function() + local chunk = handle:read(BLOCKSIZE) + if chunk and chunk:len() == 0 then chunk = nil end + if not chunk then handle:close() end + return chunk + end + else return source.error(io_err or "unable to open file") end +end + +function source.simplify(src) + base.assert(src) + return function() + local chunk, err_or_new = src() + src = err_or_new or src + if not chunk then return nil, err_or_new + else return chunk end + end +end + +function source.string(s) + if s then + local i = 1 + return function() + local chunk = string.sub(s, i, i+BLOCKSIZE-1) + i = i + BLOCKSIZE + if chunk ~= "" then return chunk + else return nil end + end + else return source.empty() end +end + +function source.rewind(src) + base.assert(src) + local t = {} + return function(chunk) + if not chunk then + chunk = table.remove(t) + if not chunk then return src() + else return chunk end + else + t[#t+1] = chunk + end + end +end + +function source.chain(src, f) + base.assert(src and f) + local last_in, last_out = "", "" + local state = "feeding" + local err + return function() + if not last_out then + base.error('source is empty!', 2) + end + while true do + if state == "feeding" then + last_in, err = src() + if err then return nil, err end + last_out = f(last_in) + if not last_out then + if last_in then + base.error('filter returned inappropriate nil') + else + return nil + end + elseif last_out ~= "" then + state = "eating" + if last_in then last_in = "" end + return last_out + end + else + last_out = f(last_in) + if last_out == "" then + if last_in == "" then + state = "feeding" + else + base.error('filter returned ""') + end + elseif not last_out then + if last_in then + base.error('filter returned inappropriate nil') + else + return nil + end + else + return last_out + end + end + end + end +end + +-- Sources will be used one after the other, as if they were concatenated +-- (thanks to Wim Couwenberg) +function source.cat(...) + local src = table.remove(arg, 1) + return function() + while src do + local chunk, err = src() + if chunk then return chunk end + if err then return nil, err end + src = table.remove(arg, 1) + end + end +end + +----------------------------------------------------------------------------- +-- Sink stuff +----------------------------------------------------------------------------- + + +function sink.table(t) + t = t or {} + local f = function(chunk, err) + if chunk then t[#t+1] = chunk end + return 1 + end + return f, t +end + +function sink.simplify(snk) + base.assert(snk) + return function(chunk, err) + local ret, err_or_new = snk(chunk, err) + if not ret then return nil, err_or_new end + snk = err_or_new or snk + return 1 + end +end + +function sink.file(handle, io_err) + if handle then + return function(chunk, err) + if not chunk then + handle:close() + return 1 + else return handle:write(chunk) end + end + else return sink.error(io_err or "unable to open file") end +end + +-- creates a sink that discards data +local function null() + return 1 +end + +function sink.null() + return null +end + +function sink.error(err) + return function() + return nil, err + end +end + +function sink.chain(f, snk) + base.assert(f and snk) + return function(chunk, err) + if chunk ~= "" then + local filtered = f(chunk) + local done = chunk and "" + while true do + local ret, snkerr = snk(filtered, err) + if not ret then return nil, snkerr end + if filtered == done then return 1 end + filtered = f(done) + end + else return 1 end + end +end + +----------------------------------------------------------------------------- +-- Pump stuff +----------------------------------------------------------------------------- + + +function pump.step(src, snk) + local chunk, src_err = src() + local ret, snk_err = snk(chunk, src_err) + if chunk and ret then return 1 + else return nil, src_err or snk_err end +end + +function pump.all(src, snk, step) + base.assert(src and snk) + step = step or pump.step + while true do + local ret, err = step(src, snk) + if not ret then + if err then return nil, err + else return 1 end + end + end +end + diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/ltn12.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/ltn12.luac new file mode 100644 index 0000000000000000000000000000000000000000..cb138de46d3176da76f60dba6b18fbfcb97f50e7 GIT binary patch literal 10090 zcmb_iX^>o16~6CvXC@&a0m2M~VH_f*mPjDrTB5urlVBD=fg*?@p_%DG%S`vsiy>}v z8zy_)l|qP7%PHKe+{?Y*Xu_gsSz;9mNI?}pTtER)f*|^R=ic-B^-Cwi4^HLld(OG% zo_pTC=PvI}PFm9}zS9Kxx%s(fzHt%eb`joEJk6BGjTU}S<{qV7%`7%5|4n$XEnXlCLN^(dp9xzps#e%PKGpph}b-6q&3 za)F}d4|J_v#yY1-HuaRIkOz{^o-PpD_g-Xk%PQh z*=lVUzL1x;khd)_z_ulIyM&+7x?uMp`aThsm>oy8()G=9tz`1OUQut<%9U$OzQ@ar z;?-lQmad`NaDLl#ygJ%sozC-EbELdP=DKaOu{_o&)!K5s+N_PB4JV#dFITQ>OB2oU z36npo#1r{NhViUUZ?!AL%t~F1>K+Swl;goVjFa zJNl8$BaBcVX5`vpxzZL(;}ea|DX~_oB2*0xp`)pGSTB!HjFmUSTt})_+ElKLn!JoI zf*2__+KnsCv9Y#PZKeIIa#L6#$xnEa1I_K8a-cnIJ` zGX0WhiuHP_)({XTO)t;^?H7c6gPn?_g4U(4PZ54j60(t_ITZZ!8K%X&o80Fi6VK|*alYVPW zU(^G=4W^)50okISLBiauHIeCyr5)-`= z1mr~@9e}>w&utOfqLWstzkc>-0>)!5@c#1b!#?f$ibAQI%En@!qVIML_DRa&3LBeN zjOU3M?-P?8O!{k&8iy8yf$VXdDY^B$*o$r}8bUrcYn9UIP`Of^n5b1JYUN_1G*l^% zi5&-dbp{tePUAwz-Ea}~hr>Hi-whW-ueby}aVc=wELe+r;_?*U1)jLVg9+0O>!44A zr*RtC5G3)Kl{%m!*Xjc2GNe{(FsQZ7iG*#j)Lz_;D)v6ul8Uu!WXm95>0ELPpgKPT zW;n4DxM~&@QO}i??h2wg8VJdNu=nGa8nt3@dfQr*8&yj=j^8SP83>u&3_07L`ici_N$DALC!Et8b3K@*GYRQ! z)K_4xlc?BQsRo9dw?;4w9?>VZv||Ky54X{~29Y_wSmNljc>J$Wb^HhV~L65v^sl)7Bq zz=Tq@F#hL8lsf_3-gCTb3&{Ymdb#x6r!&6GTS@`qG! zUfpeFw&PAmCfEFrq19~oIBe4R1mrY637OzyvV+cKm

q0E0+|&ArDlPe^r13vy*S zzdj6(_y|C!hr*{(mj>sWKHGjF$f{ROS;F87WT2UFXiT~VmH_6?vj?@#^)J_86XE zH%^$9C>aAEHi@BD8n3>$B##aC6X)W`u;V=*IEr{2bzm>#ZulJZ#231N4|VY5Ln&#- z$=9@SHxiu@yq3}k#BG4X{h17Ig-q{ROhYG+iju_kB%Vxd@7x$v)3Hf(hQo%W+ktxl zeA4J$h;jE2V3~^CRSJ>&Of?WmC_#UWox>c#`jF=Xc9U?f z3p146&>EYApJC{cIt)KIjB>!5fU5+u>%)fhkz62>{t)2+hNCY&FiAgycBIN!cr=RT9UhmfC(*_~ckJW9+t-VZ z**@NmIetvqx%eNGH#m8I%@ThRz5{(4--WFB9(cv~!DsOU=!hQz zKT6@p;EA7f!B3$heg?!D%s^KB9K7Ne;1&D9r*Q}5J5#s|yy9-~S^Nq*;vV2P9=}D& zbw}c~@IWJt73+-+)n=pf?x6`&Z{WpUt~H4*zwSxP_KOEa?pvYPZ?n8AD3n{*Uoqy=H^Qsx3Z26 z7_!iP_3SB$9rKu>ruxZmHr6kVE~)d93Fp+P&Po5 zx8%7wI@w*bu7Ix3c3zyP5CG4Deds(W;0Q%v+i4g7M>Y%&=d9=M{V-DSP$3>n;ZNYR zcnG>f;bGLVW1R^162yVdPK4?(1j#ru8?>eWn2uT!KZfu${zDf$3|$tSK^Xv)B(^7U z`Z>TP_QoivhOZj9^Lc?b_)DH;P+^E558pYK-q$<`3tx-=#%(+3LG4^YPWZ9>07K;F=BO7$DbFtD?XJ0gHXumXWBrN zV}-uBI7l6DvClyvH-nerLzDL4{aRooC!Z^J2dp^}@qE z03@GU$qph$?s?>sA)^Hat}DVfqB^1Z&>sch-(kPo9ti`2wj>ssJM!m4(cOTQ=uI90LP_wj>txPvTUOXPtsEWNm2cM~AG? z@i2M?ZLylTvZsJ?ax$lSH#Q?sh&&3S6ziXKu$^RJg9&3^p z$r;G`z+$yvfuJ0i!P`;6ZJ0OV<3Nm;%Dt%RkFka#{QU}be2JWbf7AtgS_Z95j(;DM z`lx+^G2djzK$(nyJIff>32;H2n8G6PiUIIxoD4Y&?AhG66TBRJ7DLdT;=y6z`a5_H zo(wG~lcZ~qIz7GI8&7G}F#ULjvm}jHEAE0#rF!7TeI(=)0bO#~ndw;7936tRF=@!{ zoB7a(K)ZntkOp(8(-Zyis-U6Jqv~Ls#NDyEzXmmDuofx93!4RSb|T@D)Ciaww?faY zlyk&;X$vLOfjXwk0|j@k#y)K{T{|`hc5oUR60gnRbjXLo64YhEpgEbz)B3M3$EJ0D z1gPtrc#a&kJTOVP8OpbF)vH%dCtG&dR<09U)r$lFvlLOi1;GDS=w({$1Ma|jISINx zjxL7v!N|C)No4H)2&*4oU1K!i_7SljSC{*t?~7iTl;Mu?#TCP9vBRy$%Ik}tpDfJa zTfGeb^X^yE+L)_Mb?7T`AQ^up6t7I-Rp1q`27g)# zr-LV!c$|TfIMd^7l*H>i&Oy1%gZv7hm;z-k#&MwPLH>H6=}Y~49?KhnogUHardware NAT Acceleration designed for reducing cpu loading")) + +s = m:section(TypedSection, "hwnat", "HWNAT") +s.addremove=false +s.anonymous = true +enable = s:option(Flag,"enabled",translate("Enable")) + +m:section(SimpleSection).template = "admin_mtk/hwnat_status" + +return m \ No newline at end of file diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/model/cbi/hwnat.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/model/cbi/hwnat.luac new file mode 100644 index 0000000000000000000000000000000000000000..6a9e630666ed0d60ffbbb753b458bea8999da937 GIT binary patch literal 800 zcmah{%TC-d6g>_eU^=wRRw^TuRhR7#Ae5AcShPq8q%M&gyAY8$Q65F?+{Liq8~B*a zg1_KnxHoY+6%y)^PVV*bjqk(W{?@8f2)QXYF~fl9>c*<(bJi*^unE>b(RUI7u>%$z zu!O*agLoug!~#!b=Cro~iX+at7z+egk|yE+bLWB$ComySyRVDhM*DYcXWc%9oMVTM-hsn=@?}6DMh+K`@X4LAqF3KStPmytyDJpENW{B3S@I(LmyCE89XR6~_9_(AcVz2q8cBYBT~F7q|=Y?#~{lbiS-`4PP27HK2N?ePq> CsCvTy literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/model/cbi/ipsec.lua b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/model/cbi/ipsec.lua new file mode 100755 index 000000000000..9008c74f3b7d --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/model/cbi/ipsec.lua @@ -0,0 +1,122 @@ +require "luci.model.uci" +require "luci.sys" +local sec_name +local cursor = luci.model.uci.cursor() +cursor:foreach("ipsec", "remote", function(s) sec_name = s['.name'] end) +m = Map("ipsec", translate("IP Security")) + +s = m:section(TypedSection, "remote") +s.anonymous = true +o=s:option(Value, "localGatewayName", translate("Local Gateway Name")) +o.default = sec_name + +o=s:option(ListValue, "enabled", translate("IPSec VPN")) +o.widget="radio" +o.orientation = "horizontal" +o:value("0", "Disable") -- Key and value pairs +o:value("1", "Enable") + +tunnel = m:section(NamedSection, "TUNNEL") +o=tunnel:option(Value, "local_subnet", translate("Local Group Subnet")) +o.rmempty= false +o.datatype = 'ip4addr' + +s = m:section(TypedSection, "remote") +s.anonymous = true +o=s:option(Value, "gateway", translate("Remote Gateway IP Address")) +o.rmempty= false +o.datatype = 'ip4addr' + +tunnel = m:section(NamedSection, "TUNNEL") +o=tunnel:option(Value, "remote_subnet", translate("Remote Group Subnet")) +o.rmempty= false +o.datatype = 'ip4addr' + +s = m:section(TypedSection, "remote") +s.anonymous = true +p = s:option(ListValue, "authentication_method",translate"Keying Mode") +p:value("psk", "PSK") + +o=s:option(Value, "pre_shared_key", translate("Pre-shared Key")) +o.rmempty= false + +tunnel = m:section(NamedSection, "TUNNEL") +x = tunnel:option(ListValue, "mode",translate"Auto Mode") +x:value("add", "Add") +x:value("route", "Route") +x:value("start", "Start") +x:value("ignore", "Ignore") + + +d = tunnel:option(ListValue, "keyexchange",translate"Key Exchange") +d:value("ikev2", "ikev2") +d.default = "ikev2" + +c = tunnel:option(Value, "ikelifetime",translate"IKE Lifetime") +c.default = "10800" + +e = tunnel:option(Value, "lifetime",translate"Key Lifetime") +e.default = "3600" + +z = m:section(NamedSection, "phase_1_settings", "crypto_proposal", "Phase 1 Settings") +z.addremove = false + +q = z:option(ListValue, "encryption_algorithm",translate"Encryption") +q:value("aes128", "AES128") +q:value("aes192", "AES192") +q:value("aes256", "AES256") +q.default = "aes128" + +w = z:option(ListValue, "hash_algorithm",translate"Authentication") +w:value("sha1", "SHA1") +w:value("sha256", "SHA256") +w.default = "sha1" + +r = z:option(ListValue, "dh_group",translate"Group") +r:value("modp768", "modp768") +r:value("modp1024", "modp1024") +r:value("modp1536", "modp1536") +r.default = "modp768" + +j = m:section(NamedSection, "phase_2_settings", "crypto_proposal", "Phase 2 Settings") +l = j:option(ListValue, "encryption_algorithm",translate"Encryption") +l:value("aes128", "AES128") +l:value("aes192", "AES192") +l:value("aes256", "AES256") +l.default = "aes128" + +t = j:option(ListValue, "hash_algorithm",translate"Authentication") +t:value("sha1", "SHA1") +t:value("sha256", "SHA256") +t.default = "sha1" + +u = j:option(ListValue, "dh_group",translate"Group") +u:value("modp768", "modp768") +u:value("modp1024", "modp1024") +u:value("modp1536", "modp1536") +u.default = "modp768" + +con = m:section(TypedSection, "remote") +con.anonymous = true +status = con:option(Value, "vpn_status", translate"Status") +status.default = "Disconnected/Command not found" + +function m.on_commit(Map) + + local cur = luci.model.uci.cursor() + CBI_PREFIX = "cbid.ipsec." + local org_sec_name,new_sec_name + cur:foreach("ipsec", "remote", function(s) org_sec_name = s['.name'] end) + + local new_sec_name = luci.http.formvalue(CBI_PREFIX ..org_sec_name.. ".localGatewayName") + + if(new_sec_name ~= org_sec_name) then + --cur:rename('ipsec',org_sec_name,new_sec_name) + cur:save('ipsec') + cur:commit('ipsec') + end +end + +m:section(SimpleSection).template = "admin_mtk/mtk_ipsec_view" + +return m diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/model/cbi/ipsec.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/model/cbi/ipsec.luac new file mode 100644 index 0000000000000000000000000000000000000000..4c92779e63f50a10651d41aec4b716f9eaa7ec11 GIT binary patch literal 6568 zcmb7ITXP#p6+SJ?#!lkg?A&a#h+?6jU>)1JY=DBANmh2ec8p^uAuPKqCeqlNwKSth zBRkI1s3=!(B?V9Ll;VjO_AkJLrr@XWL-@{2_w;D2M9Nfsp6Rd8Ieq$^(-(Pmy=^@t zksr^G(`kwy!)dha@_emrNrGRw@oxpuD85XCXwD!JeFyRP2>4=`XqiG_cFN1(y zcS8~)N2Zyhe?Xs~w?>dV>gFJsqfs$}z7d)eB;E!@uYw#M0k6CF#Hh$edf;=ff)Vf| zbR7KmPyBvOMKNlQlGz!hBiIPTQF^Ovbn|os9233KvSW}Pld@wcWsQ|TM>^e=zwrAt z-50_fht4?sCHO135S*BZIpt?(g3J&c`)N$j5s>&NPQ_!Ir%sO9{rs=dn8w=QK5WjX zWz_$8{2MMIXmCoy%}C_C=i4dMcM3tAX+6 z9G{i3N!c*K0nymv9BS%ZJhtLG;KB?i&O`2eMh;x4IF)0JX|D@(6F49mGrRyj z^znSb9d{qT>8TO(qSU{b(g#NuA-k24oszOsDOqqd1=(%XyIeb*b90(HooTF%X|BzY zS2PW|X~=#4Ex+R$<(OTHZHAY$O#>Vd$Lum}U6!$sTnAj3;lvflT}kAQ>B*a(al$L{ zIO3`vCjbY;b#WEGpf9Qm$%Wv=wS+r1p7z@8T$8>-My-DX91#0H1N|B4ljKZrKs1h$ z>wp6kcT8XO#!}42u{3AnSaxTy$IQGxhQTc6HhVILic7~(ap@R>i(;4_r(X=a7za2J zL&c?I2ri0Y+Gf8P(j01KPWqmTq2kgpR9rfS;Cf=1-Zu=`2BN(j?_{qG!Sh}&zN>Q? zfCG+=ucCbX&mhz<{VS>S5VAoaj*YMX&D`IK#77j+OPn(}(c-6RKW{rtoAT!*QEQi- z`MOuJYxDS`{6v~>9kwXXXB$5(N%^t3L6HjO^RgNBeMdIR?Pkks>OptBrfrq0S~)sY zDX-+4cHQ&gQ%>2|b{|*`%Ab5uMx|u**~#rZFq4(8m)O{tF{8i4y?lstB#&2 z*|W|)1=&@I`;>1Ld&g?md_BK)r{zn_O2&4rXEnQ`WmY%geBsIFhIa12sqES?qnvA6 z6$ien5P40ML8&*}o zdCofED&>zJZ)_CTwNwLgi4Q!FzC|29C_8L3ft;!wtTCZs=w{HsZ#Cix>*0ZKvrd2 zg(?*b%*cLGu3GLc~``wp)|Wn^`&SXfV3Wn^*T z&H~C@c>?)l`%7e%1$IMQP@Ce7+i3$Y^_I}6S}nV@SZdk6kNl#X&dYizHxC=WS86o9 zhS%ctr*@G|Rw*oELsKfr_QWMw_x9nS^7T38FG+*88=1s5SF&omSlE8GPIi*t!pcEbDrG3XfAT=SD;{-B^AcB7x+9YfG72>g)(ak>*y zClt75<>=#hEHP&WxlxQVX5P(>b?)Yhm_mT-bQtF7aoEYtbx8a+pxk9c1Z(0y_(>t? zOEVB_rvBzxa$J)-{_wk#TE6%0u9h~pip#4{^$u5l=2Yh6e@y73ZrDo}hnAnJ?_+hS zf`E2ip8pr)ewsgdt5Lcwi?~F#Sp`QiXc|^iM`Z5P8jAEI(NS zobGS>U=JLl2BgiWzdiK556{uh;3`z`@vXom$DvV|VZOBT^HGY!9W|0+uI~hRV@h|r z_JN+=dpr4}!J7Q=Kc+Yl3LHTNUSRBE7VLUI@G1E&utWX8OOwHMH3(ed3=YR2@M>c4 z@xkE3Gz;EX|y;U|z`{PZ388Tdi?1TtC7gUjMNxGWaHWpM-CAl!sZ7Pr7L zJ{BvhCKv!Aj7Bv`||w)?<`(|Q{Z!H0^lb($N5t6PeGJiF5>?s Josl%l{vUQn(@p>Y literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/model/uci.lua b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/model/uci.lua new file mode 100644 index 000000000000..816f6f20538a --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/model/uci.lua @@ -0,0 +1,508 @@ +-- Copyright 2008 Steven Barth +-- Licensed to the public under the Apache License 2.0. + +local os = require "os" +local util = require "luci.util" +local table = require "table" + + +local setmetatable, rawget, rawset = setmetatable, rawget, rawset +local require, getmetatable, assert = require, getmetatable, assert +local error, pairs, ipairs, select = error, pairs, ipairs, select +local type, tostring, tonumber, unpack = type, tostring, tonumber, unpack + +-- The typical workflow for UCI is: Get a cursor instance from the +-- cursor factory, modify data (via Cursor.add, Cursor.delete, etc.), +-- save the changes to the staging area via Cursor.save and finally +-- Cursor.commit the data to the actual config files. +-- LuCI then needs to Cursor.apply the changes so daemons etc. are +-- reloaded. +module "luci.model.uci" + +local ERRSTR = { + "Invalid command", + "Invalid argument", + "Method not found", + "Entry not found", + "No data", + "Permission denied", + "Timeout", + "Not supported", + "Unknown error", + "Connection failed" +} + +local session_id = nil + +local function call(cmd, args) + if type(args) == "table" and session_id then + args.ubus_rpc_session = session_id + end + return util.ubus("uci", cmd, args) +end + + +function cursor() + return _M +end + +function cursor_state() + return _M +end + +function substate(self) + return self +end + + +function get_confdir(self) + return "/etc/config" +end + +function get_savedir(self) + return "/tmp/.uci" +end + +function get_session_id(self) + return session_id +end + +function set_confdir(self, directory) + return false +end + +function set_savedir(self, directory) + return false +end + +function set_session_id(self, id) + session_id = id + return true +end + + +function load(self, config) + return true +end + +function save(self, config) + return true +end + +function unload(self, config) + return true +end + + +function changes(self, config) + local rv, err = call("changes", { config = config }) + + if type(rv) == "table" and type(rv.changes) == "table" then + return rv.changes + elseif err then + return nil, ERRSTR[err] + else + return { } + end +end + + +function revert(self, config) + local _, err = call("revert", { config = config }) + return (err == nil), ERRSTR[err] +end + +function commit(self, config) + local _, err = call("commit", { config = config }) + return (err == nil), ERRSTR[err] +end + +function apply(self, rollback) + local _, err + + if rollback then + local sys = require "luci.sys" + local conf = require "luci.config" + local timeout = tonumber(conf and conf.apply and conf.apply.rollback or 90) or 0 + + _, err = call("apply", { + timeout = (timeout > 90) and timeout or 90, + rollback = true + }) + + if not err then + local now = os.time() + local token = sys.uniqueid(16) + + util.ubus("session", "set", { + ubus_rpc_session = "00000000000000000000000000000000", + values = { + rollback = { + token = token, + session = session_id, + timeout = now + timeout + } + } + }) + + return token + end + else + _, err = call("changes", {}) + + if not err then + if type(_) == "table" and type(_.changes) == "table" then + local k, v + for k, v in pairs(_.changes) do + _, err = call("commit", { config = k }) + if err then + break + end + end + end + end + + if not err then + _, err = call("apply", { rollback = false }) + end + end + + return (err == nil), ERRSTR[err] +end + +function confirm(self, token) + local is_pending, time_remaining, rollback_sid, rollback_token = self:rollback_pending() + + if is_pending then + if token ~= rollback_token then + return false, "Permission denied" + end + + local _, err = util.ubus("uci", "confirm", { + ubus_rpc_session = rollback_sid + }) + + if not err then + util.ubus("session", "set", { + ubus_rpc_session = "00000000000000000000000000000000", + values = { rollback = {} } + }) + end + + return (err == nil), ERRSTR[err] + end + + return false, "No data" +end + +function rollback(self) + local is_pending, time_remaining, rollback_sid = self:rollback_pending() + + if is_pending then + local _, err = util.ubus("uci", "rollback", { + ubus_rpc_session = rollback_sid + }) + + if not err then + util.ubus("session", "set", { + ubus_rpc_session = "00000000000000000000000000000000", + values = { rollback = {} } + }) + end + + return (err == nil), ERRSTR[err] + end + + return false, "No data" +end + +function rollback_pending(self) + local rv, err = util.ubus("session", "get", { + ubus_rpc_session = "00000000000000000000000000000000", + keys = { "rollback" } + }) + + local now = os.time() + + if type(rv) == "table" and + type(rv.values) == "table" and + type(rv.values.rollback) == "table" and + type(rv.values.rollback.token) == "string" and + type(rv.values.rollback.session) == "string" and + type(rv.values.rollback.timeout) == "number" and + rv.values.rollback.timeout > now + then + return true, + rv.values.rollback.timeout - now, + rv.values.rollback.session, + rv.values.rollback.token + end + + return false, ERRSTR[err] +end + + +function foreach(self, config, stype, callback) + if type(callback) == "function" then + local rv, err = call("get", { + config = config, + type = stype + }) + + if type(rv) == "table" and type(rv.values) == "table" then + local sections = { } + local res = false + local index = 1 + + local _, section + for _, section in pairs(rv.values) do + section[".index"] = section[".index"] or index + sections[index] = section + index = index + 1 + end + + table.sort(sections, function(a, b) + return a[".index"] < b[".index"] + end) + + for _, section in ipairs(sections) do + local continue = callback(section) + res = true + if continue == false then + break + end + end + return res + else + return false, ERRSTR[err] or "No data" + end + else + return false, "Invalid argument" + end +end + +local function _get(self, operation, config, section, option) + if section == nil then + return nil + elseif type(option) == "string" and option:byte(1) ~= 46 then + local rv, err = call(operation, { + config = config, + section = section, + option = option + }) + + if type(rv) == "table" then + return rv.value or nil + elseif err then + return false, ERRSTR[err] + else + return nil + end + elseif option == nil then + local values = self:get_all(config, section) + if values then + return values[".type"], values[".name"] + else + return nil + end + else + return false, "Invalid argument" + end +end + +function get(self, ...) + return _get(self, "get", ...) +end + +function get_state(self, ...) + return _get(self, "state", ...) +end + +function get_all(self, config, section) + local rv, err = call("get", { + config = config, + section = section + }) + + if type(rv) == "table" and type(rv.values) == "table" then + return rv.values + elseif err then + return false, ERRSTR[err] + else + return nil + end +end + +function get_bool(self, ...) + local val = self:get(...) + return (val == "1" or val == "true" or val == "yes" or val == "on") +end + +function get_first(self, config, stype, option, default) + local rv = default + + self:foreach(config, stype, function(s) + local val = not option and s[".name"] or s[option] + + if type(default) == "number" then + val = tonumber(val) + elseif type(default) == "boolean" then + val = (val == "1" or val == "true" or + val == "yes" or val == "on") + end + + if val ~= nil then + rv = val + return false + end + end) + + return rv +end + +function get_list(self, config, section, option) + if config and section and option then + local val = self:get(config, section, option) + return (type(val) == "table" and val or { val }) + end + return { } +end + + +function section(self, config, stype, name, values) + local rv, err = call("add", { + config = config, + type = stype, + name = name, + values = values + }) + + if type(rv) == "table" then + return rv.section + elseif err then + return false, ERRSTR[err] + else + return nil + end +end + + +function add(self, config, stype) + return self:section(config, stype) +end + +function set(self, config, section, option, ...) + if select('#', ...) == 0 then + local sname, err = self:section(config, option, section) + return (not not sname), err + else + local _, err = call("set", { + config = config, + section = section, + values = { [option] = select(1, ...) } + }) + return (err == nil), ERRSTR[err] + end +end + +function set_list(self, config, section, option, value) + if section == nil or option == nil then + return false + elseif value == nil or (type(value) == "table" and #value == 0) then + return self:delete(config, section, option) + elseif type(value) == "table" then + return self:set(config, section, option, value) + else + return self:set(config, section, option, { value }) + end +end + +function tset(self, config, section, values) + local _, err = call("set", { + config = config, + section = section, + values = values + }) + return (err == nil), ERRSTR[err] +end + +function reorder(self, config, section, index) + local sections + + if type(section) == "string" and type(index) == "number" then + local pos = 0 + + sections = { } + + self:foreach(config, nil, function(s) + if pos == index then + pos = pos + 1 + end + + if s[".name"] ~= section then + pos = pos + 1 + sections[pos] = s[".name"] + else + sections[index + 1] = section + end + end) + elseif type(section) == "table" then + sections = section + else + return false, "Invalid argument" + end + + local _, err = call("order", { + config = config, + sections = sections + }) + + return (err == nil), ERRSTR[err] +end + + +function delete(self, config, section, option) + local _, err = call("delete", { + config = config, + section = section, + option = option + }) + return (err == nil), ERRSTR[err] +end + +function delete_all(self, config, stype, comparator) + local _, err + if type(comparator) == "table" then + _, err = call("delete", { + config = config, + type = stype, + match = comparator + }) + elseif type(comparator) == "function" then + local rv = call("get", { + config = config, + type = stype + }) + + if type(rv) == "table" and type(rv.values) == "table" then + local sname, section + for sname, section in pairs(rv.values) do + if comparator(section) then + _, err = call("delete", { + config = config, + section = sname + }) + end + end + end + elseif comparator == nil then + _, err = call("delete", { + config = config, + type = stype + }) + else + return false, "Invalid argument" + end + + return (err == nil), ERRSTR[err] +end diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/model/uci.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/model/uci.luac new file mode 100644 index 0000000000000000000000000000000000000000..9715fe816f363f78ae1f849bec1fa0509ed70bdb GIT binary patch literal 20146 zcmc&+32Go zw*t7|=zv{AfD|EMWQVv6NkN%{q#zZbQZuuXhzv;}{v4@2kN9Uxf?48W~W|Azx*}6<^i;3>wO)++s$Bs~J_#WR)|NRk@|C3fHo# zp6yU$11dA`tFCYP=!DW){9qhfU4#yH40foC(7BF9_^a?&;b$Dxk?BM_{6YBaB+7Iw zBD@OPD*TL#JYC4+MrnfxFM_rRe--{J{EUY@-N@r1k7k=_TX%L0g0>jbIy)8-UroeU z5zm;;f~Y&9vTR24$JRquqpr+ZsGoV6#<~$-OvD!vUzIqrNFP=~&!CDZ&Us}71J$

t<5Y8T0L>TiEr>!E6d16{j%j`lOi9CadW8Rv1 zBK{p%+@RVJsD&!Ny&@?%rTeRdR6 zvxUlZp^~>$GY+Jb^Y`3Ys3<=XN5*C;B(^A(pPem~kxQB?l*^@ZGdh!>ETeBy=VU5A zTbL@0qqLbTcJ%lR`pLFe*7-w)0WX0WfG+{42t+I1xU(IK}$v4Nf;}Y zKuMNDk7m*3WTvS}Qly^QXx5tz=O@51>4i~YDNH6ug@vINlP*=23#IZzQoEow1GrIJ116FI{LKeqm( zuQj=tXP<>FXXx_tVgz0qMYdhvOOT(i<)YH|_hs;}WY|$IIU;={iH@|F1I)wz${=C% zk02TmO~C%V3jS*tMui+q4!8rcc)(&-_7^JS`;E5V$?S@mUv0q(TDE5PK}ZyLH39)6 z?OGEp++UfV*>6@4=4M-5jEMl)R$G0@IKa%dErw-e2pY$V`z=gfgg3E_gJ6p-WC&E_ zhVXkWL$1OeRjDfQ5K57Y;z&PW!Mv=tWn01nSbLb6Qu#PCM%hik{v3jTJ;P^c@;YQ8 znD;3*c>}7r32@M)51*r`Vub0DGVOK1jTS7^vT3q}pu-YI$LJZDeG9VO#=P;!H+O;6 z+-kwREt@B?1E5PtHpxkMJ0gckI754HKo){|pGtcpC=WqBV|#Bx7J_-7N_%;fKMHUq z>4m=l)Pe3C&J{VWmuuSb9gUSjIlZBBcI&|HsX3aoAtz|kH1^tv??JvE#2Uj5v z3OE4TP+hxw9PMy5>c$*19~Fdi6|bzD_7iquQcDtwDE4)%1t>_{K>_&$G>!yqTN+%; z2=d4WG5}p{Q=g4U3ppZLq`Q2~6eEdM&|xnb)0j=F9p?r`B4+WDx(9E*`SrKlYzp(I z2;2=|A;@x@xd5c+ZCKYZ7fQRBOV`nA02s3K5c6K@-@J9h-i+LUV*{lYm;tRF*twPr zYOrsTCbkkPG~na0{}H1+N(lH&JV_AcB-@lAM8^e^M|gZ4p!aygN=H4x~`oX z7^9W;9uHXmIeG<_W6f!Jr$eo>eSL1O>Z;x<`sONksL}94Y+veqp6Z^*SWfl7?NGl` zCr>GL!mGZ0#nbMIQ{K`S=R*(v{K5J8u&dvD`$(U1_qecDwQi2r$%Vc;iShKB_O+`a zIi`J`Wx5wzz}kS@6J!i+C>)3^AZh5b=V%u^^436Kb{tqcSFfNA$IB@8>0p2Ln*H%-St^A~BpQpgUF8t7$`-$1( z*rHQ==nN%oTkY9QO3A^?S}}cF;6;T|vm9S4_5|%Xz9=L~&8FvylXuS*u)5o_R|Ij! z=+GcF*{yl=6k(1@=h_xGA3ZLxDOa4qHI)^&4|6=KW^Ktz>1d(Y97??O#Ocu$PE*%g z>b?`zM&0=}i#Nj;yai#wTM_=>LZhqzBB5FVYGv}w!zUIkmf#EChOpoS!fBiY?S9}L zz&n9ow|EzP;@!Y|0G4|%@SDJIwZR8KCmz@)K8SqFDLjaHJ3NFm;sB@`d)#tXr@YAN6jXjOcBblm_lC4IOTFx_**I2JI9zi+!TrR z!Og4C&Efqlb_A});oj_@ghSCqsHk<MDi)SOKtA*m)WJ`&FQp1IMB4_S8D`a1dh6 zQ@ZM4mz9IuVLe(vcqt6LdYy+NM;+>eeRsp^avkdi^7LQ{@iFpjHSlvN7&aXBSXHTY zw^8lx$2y1+VI5jrcjp&+u@(*28fGnYQIG3lw+0L~LVu5o#S(4R9nZAOof|h~@inPf z>gE;MGmuQz;_@@*-KIO6*s7a_fwyw!MzyHT=2xb!wb{PFmOF!OzGAa+77>skCDt0b z*=60FqK{jA0zUCci{FPY_!PoveA?3f2tM(d7Wgb^pG$%HYFpqG(gYMGvPhU6n~_UF z6oHIX76fwxCSp7}8&4x-$0Y4y9yr>A(Q;urKUw5Os3cPs=!9pZvsiB>QOd@NQLB&`2s1a;_4kO&7%`Qr1bAiiU zO;}gzu^hCauByGzn8OHDV~keipjFs0QtY`~JlM!pPxc`{I&z{>4Y5;PSk6IXf(CY7 z&`Ol8IvP7nt=4j(N9{x(R4#OJ&eZ0dN&dzH9djeTRE~$YSuHlnWL(YvnO-!vDM>9L zi$8C%4xf0e1-@Wue+HlU^A`9LXkSi&`TlAP{4LT1oOTMf@G+M7=YiNpwwWaI*+RCN z+*Y=k=mK=3rh(@oloVbzN*Z%XueK82uOZ)00pC%b2jLF@L(upzfa%q^5D8(foqBl4 z(JBlbHI~!3APK7@HRY}-2wtu3oYtDo-#t%-IP$of z7Xs4$5c0nk7XwSkw-n~QB`Dw;!fRn2N128PC32}y15l=+FuKU$GQ@qrb~p6q>rDF< z#`e8%Cpe@@f5{iWbZKB?0vx73x`C2n<#_A}ITJ_I}K zt7i-$3`JE?-snuPaMP)FLFu&0K(|s~=V}4BTJ$7UoaXJ%2|v=YONh zRq>bitzZwh<~B0ml{bMgp6=5Q>cO3~@}wT+YbWq^NAH4i8tC)xps$m4@@_%uc>;!r zt1P<0)zal^Y4)nfSI26}m9t0V#t`YbdUN&UVIWquBXdRb*}HjIH@YplBlik2FNXik zWB0~mwbesAZ9l#yRJt*mWYpTW4_0Y;%`X zpw?7P-ChGfF!pMI9R%2q^JMemmkh{ug_U&zU8tuW*ajCIf-Y}l2VhRJ4xu3Vd zzkw$B1;YPs<4?lh4t|Mz{|WpTfU7vg`Aot?JPpcz#^$3>o(VWEhIgcJHsXSv2=B5u z$L2d1{xdC}1-~77kak`I)Je?CW_vLRqRfjGG*A@DSq9){jleNPs;m#ZC^jz(Bta~N zwK_S_eWt;Q)gevF+mHUUrdzK>f@Ch^Qi&&Y(xeNK?D7(hRGIow^{bXbF`|=~1VZPA zn=XAO?*;7=pw*dN(r6OtHlE2Pwb_T~AkRfWx(`Bc*N1&5^9rCgPk^O2hRNg?$dLP=-vR;pTZrovDK*O0p#VEv<7_KCpg)v z<3B+=tuoDgfv@rI1AXz(mp-mb=%IGdNB1zx*OXqWH{3OB^o)&1d-J%{eu!g2H~WwW z`#yCC>XnUM^K>q|Az}8|aXe9pO|}gj2x8kUD?nPr;tZu3KATG1#YT&Q%$Qjenx;N} zw_?oqB88VAPP`O&SppPhPJ>Kcyz#mU#BPADG{eh3&Ilb- zL_HEFmgMw=rnB356>`GF(wILV5UWWOD>P}$?gd_qY&4rJE!{Q~E|IOi`Mnb3eKmkD zE_mk~eHN|3=OS1D9z+<6ym{<|=*BG!;%4NpLP{&JVj3~&NX$$Mri={x57uK$6SEGtnVAW4gM{}s@1^^FRI-Y|TR{-+zP>6#Mo(CKZ zRd~9l9c%1paGc#W7{!`n4B(PeZ_;3Bh7iAlMhDjEeAGk3M&pCcw6g?m&;W76Dh8Lv zh#d5Vy)CzzX}LVZZWHw>4u%a2`VppOXZ*l6aV_!*4kAn(YJuxPBgjY&Mnoc(AFk4( z1qqcSIXyIx%-e^AZc#RvRaivb5?WG;jNVAwMx&(7)GBx*`uTc5;j<+#Xm}fbG2+Ww zso@~Nis9&C4PU{pF|PD5z86EGbtAxO2b1;$O1GXo*#^A0xw)^DvBt+!e4L-@WPH{r zu^Ts`3xb;w-Yz(lBp+h7bGsr;6SYm!V315f(vt30R5%H^8FdzKr~<6D_d5YsbgaS3 zdBhA6?`EPxt`B~IHSTgQ+C`8rDBfJ* z;Xc$eT#M+}VyIPREUB4f3Tdo!b~zT=lK0~x$b4{H$Yb$0OhUo`ksO!{@i%}iTj7mJ zPh$kMG~NVS8Zb`S1vN2>a4U>~PK*N+7DwQ7R5G&YN$MqJ4nRkVA|ptnCJK}6!nr{? zg<%tgBl)?hN_-NDKaYuu-~>Y{ybY~WXawv{D=_^ofD)Xw{eahe(Zn6TbSz@BqrTJn00@6A|ZwLyhOdJibp2-WLu<2L*{m=LE@Xx0|uuvNy!77{u*`2;T$T3mmufH^WckEuabBiZF3s3y^69naS=+hvNyRq)WdODoL`4VpAnal^V>D z5jXNFHQ130IiZgnJd2AMa4R7+W|L~Sy_HzPh~5rB={wldFQ@>^CFUJd{&ffgwh7I> zcq`Vk(A!W!8mu(gqnK6fq6{>_Dv~#|O2&gk<4&U1_pxT*+i@`{BGKOlZ6w5f0MGk@ z%kw@gi>5R?0akoSxBC^`5da>{1bllUxdg~1BJ zkFPOxaqRayt?>@f(|9LnX}k+G!MhQD5AYjXfO+4$1voEU7BZ%X3F~(e2&vIA3(0Ag z2wk!#tRJ)cOox-rAZfAC7*(Q8&8}-9bC5yReN5>;Vj7v|+t-^(~0+){tWD=ZM56aid6bZSF0NGJlv&H!X0EIMmLvDc`a|_H19X@k-4DDbqhns6>fqS^&pl{37iFb zM0yi;ndCH6Kr(C7M%D>+6U!ta$#pq#QC8?i%}Yl%Cb?*0E<>C&N|SWh*mQ|(WpBQO zk^T+vwAp*%jx5&_7fnpFDK-v?b<3FiFQZq2zd-maz+YP0U&C()j)pv!(Z_T56ij}i zZ%@C;|0+8CB;fn%+}EMw3kcU>-j_q%+^GN`1NjV6n}^2T4IU0u*j3eGS3}`(vF7VC z4{Gf3W8{Xi0S^vcQCXPo?rO8_aws1i@nFQXQ$zVKO~uJJa!s1<#79Nt58zO<1o{Xc zsVGltPdhbFXP4*kM?DZcTEYbneeB0)qy@AyfIj-(!tyE8zYg@TBailb)RXu11fJd< zG|Uk{9#^t&$uVhcFVH7>jMCzXrz1~1X;rB)u`x}(@oFcLA}G=2fT-(qzA}#gOhEMX z43Avwo?z3N>kOYEIo)5w@X|O9n&9gQKLPxmrTsm8!8Z_2;~zj1d=ues+4y(hZwKE+ zzVD^*Pl%`Seb59yK=_9?{v-GV>-cBjC%{iF{sq3^UlIP9jsG0J;1>uB{sZB5pvq7g zW%^=0zX8OQ)d5A3ER);393hoLLx;cTo5`C$doWQZxh6!*OGir5@(Rta<9xBktD#o_ zaZBcyc(J9Xji1olJ+h!_XKlPDrAz+tg{19_l2Q*nBz0pyE=S&%1L=MUor4p8ZeBv9 zu9kl~@o^MoaRI9lu%LhnCE(^zz)47gLr&sX5Et+kl5cFdQ{jG%On)}OuUao2^0}NmH_u)AKNDGMZT2;9!2=839KRf(-z>{=JTOG?Z9V{S0X)$ zS0hfm61c|VD)={~kVE|Sz>NSuDF|*w_->1%@aI#gAkIrX-VnYo1s-f3ws=2$p6UdT zAk6Jr5|1LzZGeEA9GZ(XWaWJp#w$9E%`SsvrD%eANlN)8kv?6txKNTwJ)0yrTuLD+ zW1&hF_C-{h4D-snDIp7QjFq4+a-ie}UHtz>ZBDR9K1q;9?f)W`Cfg6um!MA8Py0ZZ a_}i{a5oMC}$Lw`qkRqD57WY^pJoVoP7$)BU literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sgi/cgi.lua b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sgi/cgi.lua new file mode 100644 index 000000000000..400db4710d37 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sgi/cgi.lua @@ -0,0 +1,73 @@ +-- Copyright 2008 Steven Barth +-- Licensed to the public under the Apache License 2.0. + +exectime = os.clock() +module("luci.sgi.cgi", package.seeall) +local ltn12 = require("luci.ltn12") +require("nixio.util") +require("luci.http") +require("luci.sys") +require("luci.dispatcher") + +-- Limited source to avoid endless blocking +local function limitsource(handle, limit) + limit = limit or 0 + local BLOCKSIZE = ltn12.BLOCKSIZE + + return function() + if limit < 1 then + handle:close() + return nil + else + local read = (limit > BLOCKSIZE) and BLOCKSIZE or limit + limit = limit - read + + local chunk = handle:read(read) + if not chunk then handle:close() end + return chunk + end + end +end + +function run() + local r = luci.http.Request( + luci.sys.getenv(), + limitsource(io.stdin, tonumber(luci.sys.getenv("CONTENT_LENGTH"))), + ltn12.sink.file(io.stderr) + ) + + local x = coroutine.create(luci.dispatcher.httpdispatch) + local hcache = "" + local active = true + + while coroutine.status(x) ~= "dead" do + local res, id, data1, data2 = coroutine.resume(x, r) + + if not res then + print("Status: 500 Internal Server Error") + print("Content-Type: text/plain\n") + print(id) + break; + end + + if active then + if id == 1 then + io.write("Status: " .. tostring(data1) .. " " .. data2 .. "\r\n") + elseif id == 2 then + hcache = hcache .. data1 .. ": " .. data2 .. "\r\n" + elseif id == 3 then + io.write(hcache) + io.write("\r\n") + elseif id == 4 then + io.write(tostring(data1 or "")) + elseif id == 5 then + io.flush() + io.close() + active = false + elseif id == 6 then + data1:copyz(nixio.stdout, data2) + data1:close() + end + end + end +end diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sgi/cgi.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sgi/cgi.luac new file mode 100644 index 0000000000000000000000000000000000000000..1375d18adf825efcc0d2b6dcbaa2db3adc505edb GIT binary patch literal 3140 zcmb7GTW=dh6h5)S!T2<&~bsLQXJ@es(zQhAfl zp8YvYlB!ZD6S^iP>Hz1;`rwbKQW<=rInal(URA_%aYUy6Woz^n))o>uMdwsZG3aHb zL=p$~+{F49h~XA=zmNUwH`~bk{{2kxm;)R~8H+r8+v+3)d;4$!kAT)V1X=@G=F(1kGUU<6y&VLYH->6r4oyPRpA6Z<8DlbGbxDdy?AcJv{2bB7pD@e2Opc zsGvx=VYEQc$1%;*fDfB)Vt!i+x}yf7JP_1^+WA3j%!`51M`bSy5;gE{DWY#t0C(h2 z+)+bOZV0Ljn#NCXq=vN?LYtT0&=&M3)@FrbgG#qHNeqU_S4ou`r3res`I(vQr@uC)HQ*oJH@ zQlB>$;1hDiI)^DeI-{2XY5P-^?op=-YF`5qd$TM%iZw}hDM&R^`) z`c>f=HJ?n>R5}V~tS~~Hu&hs()FG_QxEO+@OZClIqtq)I_Sr~!(r)HRa?n9 zp%;YkWQ9y4)lT*NWcH6#k{rFmGj(lekIY*3K}{Tzqxi0LqhK`;7cVwic_x227i)XH z>o;<-{y3(=V;nEw z1Za(upfyf`KAqtud>JqQPrL$o#;gB<8LTm08^i0EkK+yS7;oMO&OnAC3uu7WXo6%{*9n4>THYTLqJE2@vNE&P+bW+&Cd(k8$I zH!?Y~8=$koB&RcC>5JO2LhIzN+*2biyYv9-#7JKFz>EC0O +-- Licensed to the public under the Apache License 2.0. + +require "nixio.util" +require "luci.http" +require "luci.sys" +require "luci.dispatcher" +require "luci.ltn12" + +function handle_request(env) + exectime = os.clock() + local renv = { + CONTENT_LENGTH = env.CONTENT_LENGTH, + CONTENT_TYPE = env.CONTENT_TYPE, + REQUEST_METHOD = env.REQUEST_METHOD, + REQUEST_URI = env.REQUEST_URI, + PATH_INFO = env.PATH_INFO, + SCRIPT_NAME = env.SCRIPT_NAME:gsub("/+$", ""), + SCRIPT_FILENAME = env.SCRIPT_NAME, + SERVER_PROTOCOL = env.SERVER_PROTOCOL, + QUERY_STRING = env.QUERY_STRING, + DOCUMENT_ROOT = env.DOCUMENT_ROOT, + HTTPS = env.HTTPS, + REDIRECT_STATUS = env.REDIRECT_STATUS, + REMOTE_ADDR = env.REMOTE_ADDR, + REMOTE_NAME = env.REMOTE_NAME, + REMOTE_PORT = env.REMOTE_PORT, + REMOTE_USER = env.REMOTE_USER, + SERVER_ADDR = env.SERVER_ADDR, + SERVER_NAME = env.SERVER_NAME, + SERVER_PORT = env.SERVER_PORT + } + + local k, v + for k, v in pairs(env.headers) do + k = k:upper():gsub("%-", "_") + renv["HTTP_" .. k] = v + end + + local len = tonumber(env.CONTENT_LENGTH) or 0 + local function recv() + if len > 0 then + local rlen, rbuf = uhttpd.recv(4096) + if rlen >= 0 then + len = len - rlen + return rbuf + end + end + return nil + end + + local send = uhttpd.send + + local req = luci.http.Request( + renv, recv, luci.ltn12.sink.file(io.stderr) + ) + + + local x = coroutine.create(luci.dispatcher.httpdispatch) + local hcache = { } + local active = true + + while coroutine.status(x) ~= "dead" do + local res, id, data1, data2 = coroutine.resume(x, req) + + if not res then + send("Status: 500 Internal Server Error\r\n") + send("Content-Type: text/plain\r\n\r\n") + send(tostring(id)) + break + end + + if active then + if id == 1 then + send("Status: ") + send(tostring(data1)) + send(" ") + send(tostring(data2)) + send("\r\n") + elseif id == 2 then + hcache[data1] = data2 + elseif id == 3 then + for k, v in pairs(hcache) do + send(tostring(k)) + send(": ") + send(tostring(v)) + send("\r\n") + end + send("\r\n") + elseif id == 4 then + send(tostring(data1 or "")) + elseif id == 5 then + active = false + elseif id == 6 then + data1:copyz(nixio.stdout, data2) + end + end + end +end diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sgi/uhttpd.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sgi/uhttpd.luac new file mode 100644 index 0000000000000000000000000000000000000000..6b0eb11a48bc78790af0d762f319f8879dc76079 GIT binary patch literal 3961 zcmai1TW=dh6h6CN+aWaN?vxTBK#{oh%1tUD)|YkSwuw_`0~89zwYQB`V~6!RYI(`T zX}34{1HeB(Nc;pRkZ>syJfpnu!bQ9wctY^Ncg8zj$0TB;&ok#cXJ*cvIWx1q??kI~ zk~Cw;7@}R&EoN)p^A;)xYOPYhaHZ>a;x`H;O`+2dc)nDx)SM#E?le6(92TCm%%Vo4U!k&0qO9o9X(4C=@-bnM z-!UmC#IjFG)YSG(u0`7yQeN>pQeN>XMK{cprY-A~T-E7gK|1U&F^}5!%{2G(eZe(d z@jFt!;!}p@tG>y#xatcjU-LUszUGs~`s=>QwYcsJDc|rrQoi9+mi6zikkF#Z7-g5U zG^S}<~UtWW*os0TwfPO5x zo*Jf&_BYXwb<#F5espb^V)}8zf?YOs)5x}O8e{P9m`>Ej80*k)tlf-Kgp5t_uhO^C z1nm)$Pkk$SGa@3Jsb%el6nyLGq7$Krc4-%Frb(0SFT!@z7$MW3*qDL&)37)15+7o& z1pT!%YEH!x9V6&J((B*tBYJ(hV-D&muEvylW4G*|5PxFa=~%)fk!!ES65@}gQNeOd ztfyl*3&PlpU~f7Rfio7dq9Y`tbj#@|X=4@=4qS45H=(VCda+d>?dpGA;r0Bzv~~X9 zmPRR$b!l>4yp}gIBF1}|{6Z(?bAo<_pRj|UZ5fYW;3s79^U}@Fzl&KoY1z#8L&Bgq z;-)9)rzJkl2zhucoeNIctGoW{V;85BQy7%* zFD~brtutg8U4afhxsQx)N`vX$EJ4p~4vxy+>dKk53a?m&;!GiL=hOKU;R@hs;Z$+f zF65>s2zk&gnS6R~iWgkS=j|{mleRrG8_rg+GP#14w$VLl&p{C`bSiIK#bhQ^_z&6j zYrt|QU%)g2$S@iA z2gd0JEb88V`{IIgblh_;cn241rD}a-n61LlH$AsnKc_emn)4vA+)o&^{c1*TBa@ZM$1@>crW^+0rA=w9fWOzN6S%E^N%-DV%Qo z;B(IdF!%;y@aGSMJ!aeyfIsiH!(HGRcL%r!H3M&l#3*V8qAr0b6Ju|H`%p8+0&q4m z5OIkIQ8OL}9vQ$s$UEUt==#BZS$=E>aD*Ap1b7a$!t-eVAK3PV9q=Ofmw=adfC+vO zDR7K*A2MhwSZEI-3oaMn7;47xKAb>%5L4h3rqLcm9$Y_WATv$|D4=G{4#0*yh&gZy zub@4MSHUUpc-%fUfY%{2-uNG!hMw_e0Gwn#%~ae5C2(bc^U?sgIQ2zYe~I6yY!SIk z9)!T89+2nh&;Ya8WB34ZdF*V%9Y5#P9k=8)+{fu@a7lmFY%T^VqE yg;?pf53$m!#h#~+FwQ4{VitQ0NO>!AuoO636nG^H94ZC==1}0w<8KA7Rrwc_J}im= literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/store.lua b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/store.lua new file mode 100644 index 000000000000..a73598113780 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/store.lua @@ -0,0 +1,6 @@ +-- Copyright 2009 Steven Barth +-- Copyright 2009 Jo-Philipp Wich +-- Licensed to the public under the Apache License 2.0. + +local util = require "luci.util" +module("luci.store", util.threadlocal) diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/store.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/store.luac new file mode 100644 index 0000000000000000000000000000000000000000..3e65656ec0b76fb1c50682f8d3c70b7e5b451e73 GIT binary patch literal 245 zcmZvWK@I{T3`IYRI#F++@d$GUX5r3_dpBf;L=p@UVUIU+<+&^!*xBT#?e9x|50|8` z5VDgUc9=`Oq7SBbNmrw^1p&Z{G$`PhA +-- Licensed to the public under the Apache License 2.0. + +local io = require "io" +local os = require "os" +local table = require "table" +local nixio = require "nixio" +local fs = require "nixio.fs" +local uci = require "luci.model.uci" + +local luci = {} +luci.util = require "luci.util" +luci.ip = require "luci.ip" + +local tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select, unpack = + tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select, unpack + + +module "luci.sys" + +function call(...) + return os.execute(...) / 256 +end + +exec = luci.util.exec + +-- containing the whole environment is returned otherwise this function returns +-- the corresponding string value for the given name or nil if no such variable +-- exists. +getenv = nixio.getenv + +function hostname(newname) + if type(newname) == "string" and #newname > 0 then + fs.writefile( "/proc/sys/kernel/hostname", newname ) + return newname + else + return nixio.uname().nodename + end +end + +function httpget(url, stream, target) + if not target then + local source = stream and io.popen or luci.util.exec + return source("wget -qO- %s" % luci.util.shellquote(url)) + else + return os.execute("wget -qO %s %s" % + {luci.util.shellquote(target), luci.util.shellquote(url)}) + end +end + +function reboot() + return os.execute("reboot >/dev/null 2>&1") +end + +function syslog() + return luci.util.exec("logread") +end + +function dmesg() + return luci.util.exec("dmesg") +end + +function uniqueid(bytes) + local rand = fs.readfile("/dev/urandom", bytes) + return rand and nixio.bin.hexlify(rand) +end + +function uptime() + return nixio.sysinfo().uptime +end + + +net = {} + +local function _nethints(what, callback) + local _, k, e, mac, ip, name, duid, iaid + local cur = uci.cursor() + local ifn = { } + local hosts = { } + local lookup = { } + + local function _add(i, ...) + local k = select(i, ...) + if k then + if not hosts[k] then hosts[k] = { } end + hosts[k][1] = select(1, ...) or hosts[k][1] + hosts[k][2] = select(2, ...) or hosts[k][2] + hosts[k][3] = select(3, ...) or hosts[k][3] + hosts[k][4] = select(4, ...) or hosts[k][4] + end + end + + luci.ip.neighbors(nil, function(neigh) + if neigh.mac and neigh.family == 4 then + _add(what, neigh.mac:string(), neigh.dest:string(), nil, nil) + elseif neigh.mac and neigh.family == 6 then + _add(what, neigh.mac:string(), nil, neigh.dest:string(), nil) + end + end) + + if fs.access("/etc/ethers") then + for e in io.lines("/etc/ethers") do + mac, name = e:match("^([a-fA-F0-9:-]+)%s+(%S+)") + mac = luci.ip.checkmac(mac) + if mac and name then + if luci.ip.checkip4(name) then + _add(what, mac, name, nil, nil) + else + _add(what, mac, nil, nil, name) + end + end + end + end + + cur:foreach("dhcp", "dnsmasq", + function(s) + if s.leasefile and fs.access(s.leasefile) then + for e in io.lines(s.leasefile) do + mac, ip, name = e:match("^%d+ (%S+) (%S+) (%S+)") + mac = luci.ip.checkmac(mac) + if mac and ip then + _add(what, mac, ip, nil, name ~= "*" and name) + end + end + end + end + ) + + cur:foreach("dhcp", "odhcpd", + function(s) + if type(s.leasefile) == "string" and fs.access(s.leasefile) then + for e in io.lines(s.leasefile) do + duid, iaid, name, _, ip = e:match("^# %S+ (%S+) (%S+) (%S+) (-?%d+) %S+ %S+ ([0-9a-f:.]+)/[0-9]+") + mac = net.duid_to_mac(duid) + if mac then + if ip and iaid == "ipv4" then + _add(what, mac, ip, nil, name ~= "*" and name) + elseif ip then + _add(what, mac, nil, ip, name ~= "*" and name) + end + end + end + end + end + ) + + cur:foreach("dhcp", "host", + function(s) + for mac in luci.util.imatch(s.mac) do + mac = luci.ip.checkmac(mac) + if mac then + _add(what, mac, s.ip, nil, s.name) + end + end + end) + + for _, e in ipairs(nixio.getifaddrs()) do + if e.name ~= "lo" then + ifn[e.name] = ifn[e.name] or { } + if e.family == "packet" and e.addr and #e.addr == 17 then + ifn[e.name][1] = e.addr:upper() + elseif e.family == "inet" then + ifn[e.name][2] = e.addr + elseif e.family == "inet6" then + ifn[e.name][3] = e.addr + end + end + end + + for _, e in pairs(ifn) do + if e[what] and (e[2] or e[3]) then + _add(what, e[1], e[2], e[3], e[4]) + end + end + + for _, e in pairs(hosts) do + lookup[#lookup+1] = (what > 1) and e[what] or (e[2] or e[3]) + end + + if #lookup > 0 then + lookup = luci.util.ubus("network.rrdns", "lookup", { + addrs = lookup, + timeout = 250, + limit = 1000 + }) or { } + end + + for _, e in luci.util.kspairs(hosts) do + callback(e[1], e[2], e[3], lookup[e[2]] or lookup[e[3]] or e[4]) + end +end + +-- Each entry contains the values in the following order: +-- [ "mac", "name" ] +function net.mac_hints(callback) + if callback then + _nethints(1, function(mac, v4, v6, name) + name = name or v4 + if name and name ~= mac then + callback(mac, name or v4) + end + end) + else + local rv = { } + _nethints(1, function(mac, v4, v6, name) + name = name or v4 + if name and name ~= mac then + rv[#rv+1] = { mac, name or v4 } + end + end) + return rv + end +end + +-- Each entry contains the values in the following order: +-- [ "ip", "name" ] +function net.ipv4_hints(callback) + if callback then + _nethints(2, function(mac, v4, v6, name) + name = name or mac + if name and name ~= v4 then + callback(v4, name) + end + end) + else + local rv = { } + _nethints(2, function(mac, v4, v6, name) + name = name or mac + if name and name ~= v4 then + rv[#rv+1] = { v4, name } + end + end) + return rv + end +end + +-- Each entry contains the values in the following order: +-- [ "ip", "name" ] +function net.ipv6_hints(callback) + if callback then + _nethints(3, function(mac, v4, v6, name) + name = name or mac + if name and name ~= v6 then + callback(v6, name) + end + end) + else + local rv = { } + _nethints(3, function(mac, v4, v6, name) + name = name or mac + if name and name ~= v6 then + rv[#rv+1] = { v6, name } + end + end) + return rv + end +end + +function net.host_hints(callback) + if callback then + _nethints(1, function(mac, v4, v6, name) + if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then + callback(mac, v4, v6, name) + end + end) + else + local rv = { } + _nethints(1, function(mac, v4, v6, name) + if mac and mac ~= "00:00:00:00:00:00" and (v4 or v6 or name) then + local e = { } + if v4 then e.ipv4 = v4 end + if v6 then e.ipv6 = v6 end + if name then e.name = name end + rv[mac] = e + end + end) + return rv + end +end + +function net.conntrack(callback) + local ok, nfct = pcall(io.lines, "/proc/net/nf_conntrack") + if not ok or not nfct then + return nil + end + + local line, connt = nil, (not callback) and { } + for line in nfct do + local fam, l3, l4, rest = + line:match("^(ipv[46]) +(%d+) +%S+ +(%d+) +(.+)$") + + local timeout, tuples = rest:match("^(%d+) +(.+)$") + + if not tuples then + tuples = rest + end + + if fam and l3 and l4 and not tuples:match("^TIME_WAIT ") then + l4 = nixio.getprotobynumber(l4) + + local entry = { + bytes = 0, + packets = 0, + layer3 = fam, + layer4 = l4 and l4.name or "unknown", + timeout = tonumber(timeout, 10) + } + + local key, val + for key, val in tuples:gmatch("(%w+)=(%S+)") do + if key == "bytes" or key == "packets" then + entry[key] = entry[key] + tonumber(val, 10) + elseif key == "src" or key == "dst" then + if entry[key] == nil then + entry[key] = luci.ip.new(val):string() + end + elseif key == "sport" or key == "dport" then + if entry[key] == nil then + entry[key] = val + end + elseif val then + entry[key] = val + end + end + + if callback then + callback(entry) + else + connt[#connt+1] = entry + end + end + end + + return callback and true or connt +end + +function net.devices() + local devs = {} + local seen = {} + for k, v in ipairs(nixio.getifaddrs()) do + if v.name and not seen[v.name] then + seen[v.name] = true + devs[#devs+1] = v.name + end + end + return devs +end + +function net.duid_to_mac(duid) + local b1, b2, b3, b4, b5, b6 + + if type(duid) == "string" then + -- DUID-LLT / Ethernet + if #duid == 28 then + b1, b2, b3, b4, b5, b6 = duid:match("^00010001(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)%x%x%x%x%x%x%x%x$") + + -- DUID-LL / Ethernet + elseif #duid == 20 then + b1, b2, b3, b4, b5, b6 = duid:match("^00030001(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)$") + + -- DUID-LL / Ethernet (Without Header) + elseif #duid == 12 then + b1, b2, b3, b4, b5, b6 = duid:match("^(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)$") + end + end + + return b1 and luci.ip.checkmac(table.concat({ b1, b2, b3, b4, b5, b6 }, ":")) +end + +process = {} + +function process.info(key) + local s = {uid = nixio.getuid(), gid = nixio.getgid()} + return not key and s or s[key] +end + +function process.list() + local data = {} + local k + local ps = luci.util.execi("/bin/busybox top -bn1") + + if not ps then + return + end + + for line in ps do + local pid, ppid, user, stat, vsz, mem, cpu, cmd = line:match( + "^ *(%d+) +(%d+) +(%S.-%S) +([RSDZTW][ 2 then + fd:close() + end +end + +function process.exec(command, stdout, stderr, nowait) + local out_r, out_w, err_r, err_w + if stdout then out_r, out_w = nixio.pipe() end + if stderr then err_r, err_w = nixio.pipe() end + + local pid = nixio.fork() + if pid == 0 then + nixio.chdir("/") + + local null = nixio.open("/dev/null", "w+") + if null then + nixio.dup(out_w or null, nixio.stdout) + nixio.dup(err_w or null, nixio.stderr) + nixio.dup(null, nixio.stdin) + xclose(out_w) + xclose(out_r) + xclose(err_w) + xclose(err_r) + xclose(null) + end + + nixio.exec(unpack(command)) + os.exit(-1) + end + + local _, pfds, rv = nil, {}, { code = -1, pid = pid } + + xclose(out_w) + xclose(err_w) + + if out_r then + pfds[#pfds+1] = { + fd = out_r, + cb = type(stdout) == "function" and stdout, + name = "stdout", + events = nixio.poll_flags("in", "err", "hup") + } + end + + if err_r then + pfds[#pfds+1] = { + fd = err_r, + cb = type(stderr) == "function" and stderr, + name = "stderr", + events = nixio.poll_flags("in", "err", "hup") + } + end + + while #pfds > 0 do + local nfds, err = nixio.poll(pfds, -1) + if not nfds and err ~= nixio.const.EINTR then + break + end + + local i + for i = #pfds, 1, -1 do + local rfd = pfds[i] + if rfd.revents > 0 then + local chunk, err = rfd.fd:read(4096) + if chunk and #chunk > 0 then + if rfd.cb then + rfd.cb(chunk) + else + rfd.buf = rfd.buf or {} + rfd.buf[#rfd.buf + 1] = chunk + end + else + table.remove(pfds, i) + if rfd.buf then + rv[rfd.name] = table.concat(rfd.buf, "") + end + rfd.fd:close() + end + end + end + end + + if not nowait then + _, _, rv.code = nixio.waitpid(pid) + end + + return rv +end + + +user = {} + +-- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" } +user.getuser = nixio.getpw + +function user.getpasswd(username) + local pwe = nixio.getsp and nixio.getsp(username) or nixio.getpw(username) + local pwh = pwe and (pwe.pwdp or pwe.passwd) + if not pwh or #pwh < 1 then + return nil, pwe + else + return pwh, pwe + end +end + +function user.checkpasswd(username, pass) + local pwh, pwe = user.getpasswd(username) + if pwe then + return (pwh == nil or nixio.crypt(pass, pwh) == pwh) + end + return false +end + +function user.setpasswd(username, password) + return os.execute("(echo %s; sleep 1; echo %s) | passwd %s >/dev/null 2>&1" %{ + luci.util.shellquote(password), + luci.util.shellquote(password), + luci.util.shellquote(username) + }) +end + + +wifi = {} + +function wifi.getiwinfo(ifname) + local ntm = require "luci.model.network" + + ntm.init() + + local wnet = ntm:get_wifinet(ifname) + if wnet and wnet.iwinfo then + return wnet.iwinfo + end + + local wdev = ntm:get_wifidev(ifname) + if wdev and wdev.iwinfo then + return wdev.iwinfo + end + + return { ifname = ifname } +end + + +init = {} +init.dir = "/etc/init.d/" + +function init.names() + local names = { } + for name in fs.glob(init.dir.."*") do + names[#names+1] = fs.basename(name) + end + return names +end + +function init.index(name) + name = fs.basename(name) + if fs.access(init.dir..name) then + return call("env -i sh -c 'source %s%s enabled; exit ${START:-255}' >/dev/null" + %{ init.dir, name }) + end +end + +local function init_action(action, name) + name = fs.basename(name) + if fs.access(init.dir..name) then + return call("env -i %s%s %s >/dev/null" %{ init.dir, name, action }) + end +end + +function init.enabled(name) + return (init_action("enabled", name) == 0) +end + +function init.enable(name) + return (init_action("enable", name) == 0) +end + +function init.disable(name) + return (init_action("disable", name) == 0) +end + +function init.start(name) + return (init_action("start", name) == 0) +end + +function init.stop(name) + return (init_action("stop", name) == 0) +end + +function init.restart(name) + return (init_action("restart", name) == 0) +end + +function init.reload(name) + return (init_action("reload", name) == 0) +end diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sys.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sys.luac new file mode 100644 index 0000000000000000000000000000000000000000..ebe07d771fd98b23e60562212da50b6202bc03d0 GIT binary patch literal 28389 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 MEz>ymnS}2D1En9X0ssI2 literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sys/zoneinfo.lua b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sys/zoneinfo.lua new file mode 100644 index 000000000000..aa054a246f3b --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sys/zoneinfo.lua @@ -0,0 +1,19 @@ +-- Licensed to the public under the Apache License 2.0. + +local setmetatable, require, rawget, rawset = setmetatable, require, rawget, rawset + +module "luci.sys.zoneinfo" + +setmetatable(_M, { + __index = function(t, k) + if k == "TZ" and not rawget(t, k) then + local m = require "luci.sys.zoneinfo.tzdata" + rawset(t, k, rawget(m, k)) + elseif k == "OFFSET" and not rawget(t, k) then + local m = require "luci.sys.zoneinfo.tzoffset" + rawset(t, k, rawget(m, k)) + end + + return rawget(t, k) + end +}) diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sys/zoneinfo.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sys/zoneinfo.luac new file mode 100644 index 0000000000000000000000000000000000000000..06ad32d826b6072b7b45d20350ecb98ae32efce6 GIT binary patch literal 1068 zcma)5O>fgc5Pj=xMZ!k~t&|8gf+H82-(Vlo6R4`liA!XNgCg=F8e4^)wxT_hpMcW- z7DuB0qR0M_zL(gI>q5&!Z)fN2%w%>vyY(&!_h4+*RhaYAP5Ya0Std=9~t}#fGh4PTpg7&r}Lwq&A^#gw5w$7GcJ;86px}f zjKfdE2rOeM`_Z>#uutMqPJ{X(RQLd9r6gt@+NP& z%bBkWzQ~LgRc{u2MtDu@4WY>@4QhFi3&-l5DpFJtdRjT;ol^US-h*e^x}DCG!?D^DlmkI literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sys/zoneinfo/tzdata.lua b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sys/zoneinfo/tzdata.lua new file mode 100644 index 000000000000..a3edbf5cb49a --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sys/zoneinfo/tzdata.lua @@ -0,0 +1,457 @@ +-- Licensed to the public under the Apache License 2.0. + +module "luci.sys.zoneinfo.tzdata" + +TZ = { + { 'Africa/Abidjan', 'GMT0' }, + { 'Africa/Accra', 'GMT0' }, + { 'Africa/Addis Ababa', 'EAT-3' }, + { 'Africa/Algiers', 'CET-1' }, + { 'Africa/Asmara', 'EAT-3' }, + { 'Africa/Bamako', 'GMT0' }, + { 'Africa/Bangui', 'WAT-1' }, + { 'Africa/Banjul', 'GMT0' }, + { 'Africa/Bissau', 'GMT0' }, + { 'Africa/Blantyre', 'CAT-2' }, + { 'Africa/Brazzaville', 'WAT-1' }, + { 'Africa/Bujumbura', 'CAT-2' }, + { 'Africa/Cairo', 'EET-2' }, + { 'Africa/Casablanca', '<+01>-1' }, + { 'Africa/Ceuta', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Africa/Conakry', 'GMT0' }, + { 'Africa/Dakar', 'GMT0' }, + { 'Africa/Dar es Salaam', 'EAT-3' }, + { 'Africa/Djibouti', 'EAT-3' }, + { 'Africa/Douala', 'WAT-1' }, + { 'Africa/El Aaiun', '<+01>-1' }, + { 'Africa/Freetown', 'GMT0' }, + { 'Africa/Gaborone', 'CAT-2' }, + { 'Africa/Harare', 'CAT-2' }, + { 'Africa/Johannesburg', 'SAST-2' }, + { 'Africa/Juba', 'CAT-2' }, + { 'Africa/Kampala', 'EAT-3' }, + { 'Africa/Khartoum', 'CAT-2' }, + { 'Africa/Kigali', 'CAT-2' }, + { 'Africa/Kinshasa', 'WAT-1' }, + { 'Africa/Lagos', 'WAT-1' }, + { 'Africa/Libreville', 'WAT-1' }, + { 'Africa/Lome', 'GMT0' }, + { 'Africa/Luanda', 'WAT-1' }, + { 'Africa/Lubumbashi', 'CAT-2' }, + { 'Africa/Lusaka', 'CAT-2' }, + { 'Africa/Malabo', 'WAT-1' }, + { 'Africa/Maputo', 'CAT-2' }, + { 'Africa/Maseru', 'SAST-2' }, + { 'Africa/Mbabane', 'SAST-2' }, + { 'Africa/Mogadishu', 'EAT-3' }, + { 'Africa/Monrovia', 'GMT0' }, + { 'Africa/Nairobi', 'EAT-3' }, + { 'Africa/Ndjamena', 'WAT-1' }, + { 'Africa/Niamey', 'WAT-1' }, + { 'Africa/Nouakchott', 'GMT0' }, + { 'Africa/Ouagadougou', 'GMT0' }, + { 'Africa/Porto-Novo', 'WAT-1' }, + { 'Africa/Sao Tome', 'GMT0' }, + { 'Africa/Tripoli', 'EET-2' }, + { 'Africa/Tunis', 'CET-1' }, + { 'Africa/Windhoek', 'CAT-2' }, + { 'America/Adak', 'HST10HDT,M3.2.0,M11.1.0' }, + { 'America/Anchorage', 'AKST9AKDT,M3.2.0,M11.1.0' }, + { 'America/Anguilla', 'AST4' }, + { 'America/Antigua', 'AST4' }, + { 'America/Araguaina', '<-03>3' }, + { 'America/Argentina/Buenos Aires', '<-03>3' }, + { 'America/Argentina/Catamarca', '<-03>3' }, + { 'America/Argentina/Cordoba', '<-03>3' }, + { 'America/Argentina/Jujuy', '<-03>3' }, + { 'America/Argentina/La Rioja', '<-03>3' }, + { 'America/Argentina/Mendoza', '<-03>3' }, + { 'America/Argentina/Rio Gallegos', '<-03>3' }, + { 'America/Argentina/Salta', '<-03>3' }, + { 'America/Argentina/San Juan', '<-03>3' }, + { 'America/Argentina/San Luis', '<-03>3' }, + { 'America/Argentina/Tucuman', '<-03>3' }, + { 'America/Argentina/Ushuaia', '<-03>3' }, + { 'America/Aruba', 'AST4' }, + { 'America/Asuncion', '<-04>4<-03>,M10.1.0/0,M3.4.0/0' }, + { 'America/Atikokan', 'EST5' }, + { 'America/Bahia', '<-03>3' }, + { 'America/Bahia Banderas', 'CST6CDT,M4.1.0,M10.5.0' }, + { 'America/Barbados', 'AST4' }, + { 'America/Belem', '<-03>3' }, + { 'America/Belize', 'CST6' }, + { 'America/Blanc-Sablon', 'AST4' }, + { 'America/Boa Vista', '<-04>4' }, + { 'America/Bogota', '<-05>5' }, + { 'America/Boise', 'MST7MDT,M3.2.0,M11.1.0' }, + { 'America/Cambridge Bay', 'MST7MDT,M3.2.0,M11.1.0' }, + { 'America/Campo Grande', '<-04>4' }, + { 'America/Cancun', 'EST5' }, + { 'America/Caracas', '<-04>4' }, + { 'America/Cayenne', '<-03>3' }, + { 'America/Cayman', 'EST5' }, + { 'America/Chicago', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/Chihuahua', 'MST7MDT,M4.1.0,M10.5.0' }, + { 'America/Costa Rica', 'CST6' }, + { 'America/Creston', 'MST7' }, + { 'America/Cuiaba', '<-04>4' }, + { 'America/Curacao', 'AST4' }, + { 'America/Danmarkshavn', 'GMT0' }, + { 'America/Dawson', 'MST7' }, + { 'America/Dawson Creek', 'MST7' }, + { 'America/Denver', 'MST7MDT,M3.2.0,M11.1.0' }, + { 'America/Detroit', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Dominica', 'AST4' }, + { 'America/Edmonton', 'MST7MDT,M3.2.0,M11.1.0' }, + { 'America/Eirunepe', '<-05>5' }, + { 'America/El Salvador', 'CST6' }, + { 'America/Fort Nelson', 'MST7' }, + { 'America/Fortaleza', '<-03>3' }, + { 'America/Glace Bay', 'AST4ADT,M3.2.0,M11.1.0' }, + { 'America/Goose Bay', 'AST4ADT,M3.2.0,M11.1.0' }, + { 'America/Grand Turk', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Grenada', 'AST4' }, + { 'America/Guadeloupe', 'AST4' }, + { 'America/Guatemala', 'CST6' }, + { 'America/Guayaquil', '<-05>5' }, + { 'America/Guyana', '<-04>4' }, + { 'America/Halifax', 'AST4ADT,M3.2.0,M11.1.0' }, + { 'America/Havana', 'CST5CDT,M3.2.0/0,M11.1.0/1' }, + { 'America/Hermosillo', 'MST7' }, + { 'America/Indiana/Indianapolis', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Indiana/Knox', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/Indiana/Marengo', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Indiana/Petersburg', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Indiana/Tell City', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/Indiana/Vevay', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Indiana/Vincennes', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Indiana/Winamac', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Inuvik', 'MST7MDT,M3.2.0,M11.1.0' }, + { 'America/Iqaluit', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Jamaica', 'EST5' }, + { 'America/Juneau', 'AKST9AKDT,M3.2.0,M11.1.0' }, + { 'America/Kentucky/Louisville', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Kentucky/Monticello', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Kralendijk', 'AST4' }, + { 'America/La Paz', '<-04>4' }, + { 'America/Lima', '<-05>5' }, + { 'America/Los Angeles', 'PST8PDT,M3.2.0,M11.1.0' }, + { 'America/Lower Princes', 'AST4' }, + { 'America/Maceio', '<-03>3' }, + { 'America/Managua', 'CST6' }, + { 'America/Manaus', '<-04>4' }, + { 'America/Marigot', 'AST4' }, + { 'America/Martinique', 'AST4' }, + { 'America/Matamoros', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/Mazatlan', 'MST7MDT,M4.1.0,M10.5.0' }, + { 'America/Menominee', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/Merida', 'CST6CDT,M4.1.0,M10.5.0' }, + { 'America/Metlakatla', 'AKST9AKDT,M3.2.0,M11.1.0' }, + { 'America/Mexico City', 'CST6CDT,M4.1.0,M10.5.0' }, + { 'America/Miquelon', '<-03>3<-02>,M3.2.0,M11.1.0' }, + { 'America/Moncton', 'AST4ADT,M3.2.0,M11.1.0' }, + { 'America/Monterrey', 'CST6CDT,M4.1.0,M10.5.0' }, + { 'America/Montevideo', '<-03>3' }, + { 'America/Montserrat', 'AST4' }, + { 'America/Nassau', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/New York', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Nipigon', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Nome', 'AKST9AKDT,M3.2.0,M11.1.0' }, + { 'America/Noronha', '<-02>2' }, + { 'America/North Dakota/Beulah', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/North Dakota/Center', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/North Dakota/New Salem', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/Nuuk', '<-03>3<-02>,M3.5.0/-2,M10.5.0/-1' }, + { 'America/Ojinaga', 'MST7MDT,M3.2.0,M11.1.0' }, + { 'America/Panama', 'EST5' }, + { 'America/Pangnirtung', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Paramaribo', '<-03>3' }, + { 'America/Phoenix', 'MST7' }, + { 'America/Port of Spain', 'AST4' }, + { 'America/Port-au-Prince', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Porto Velho', '<-04>4' }, + { 'America/Puerto Rico', 'AST4' }, + { 'America/Punta Arenas', '<-03>3' }, + { 'America/Rainy River', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/Rankin Inlet', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/Recife', '<-03>3' }, + { 'America/Regina', 'CST6' }, + { 'America/Resolute', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/Rio Branco', '<-05>5' }, + { 'America/Santarem', '<-03>3' }, + { 'America/Santiago', '<-04>4<-03>,M9.1.6/24,M4.1.6/24' }, + { 'America/Santo Domingo', 'AST4' }, + { 'America/Sao Paulo', '<-03>3' }, + { 'America/Scoresbysund', '<-01>1<+00>,M3.5.0/0,M10.5.0/1' }, + { 'America/Sitka', 'AKST9AKDT,M3.2.0,M11.1.0' }, + { 'America/St Barthelemy', 'AST4' }, + { 'America/St Johns', 'NST3:30NDT,M3.2.0,M11.1.0' }, + { 'America/St Kitts', 'AST4' }, + { 'America/St Lucia', 'AST4' }, + { 'America/St Thomas', 'AST4' }, + { 'America/St Vincent', 'AST4' }, + { 'America/Swift Current', 'CST6' }, + { 'America/Tegucigalpa', 'CST6' }, + { 'America/Thule', 'AST4ADT,M3.2.0,M11.1.0' }, + { 'America/Thunder Bay', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Tijuana', 'PST8PDT,M3.2.0,M11.1.0' }, + { 'America/Toronto', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Tortola', 'AST4' }, + { 'America/Vancouver', 'PST8PDT,M3.2.0,M11.1.0' }, + { 'America/Whitehorse', 'MST7' }, + { 'America/Winnipeg', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/Yakutat', 'AKST9AKDT,M3.2.0,M11.1.0' }, + { 'America/Yellowknife', 'MST7MDT,M3.2.0,M11.1.0' }, + { 'Antarctica/Casey', '<+11>-11' }, + { 'Antarctica/Davis', '<+07>-7' }, + { 'Antarctica/DumontDUrville', '<+10>-10' }, + { 'Antarctica/Macquarie', 'AEST-10AEDT,M10.1.0,M4.1.0/3' }, + { 'Antarctica/Mawson', '<+05>-5' }, + { 'Antarctica/McMurdo', 'NZST-12NZDT,M9.5.0,M4.1.0/3' }, + { 'Antarctica/Palmer', '<-03>3' }, + { 'Antarctica/Rothera', '<-03>3' }, + { 'Antarctica/Syowa', '<+03>-3' }, + { 'Antarctica/Troll', '<+00>0<+02>-2,M3.5.0/1,M10.5.0/3' }, + { 'Antarctica/Vostok', '<+06>-6' }, + { 'Arctic/Longyearbyen', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Asia/Aden', '<+03>-3' }, + { 'Asia/Almaty', '<+06>-6' }, + { 'Asia/Amman', 'EET-2EEST,M2.5.4/24,M10.5.5/1' }, + { 'Asia/Anadyr', '<+12>-12' }, + { 'Asia/Aqtau', '<+05>-5' }, + { 'Asia/Aqtobe', '<+05>-5' }, + { 'Asia/Ashgabat', '<+05>-5' }, + { 'Asia/Atyrau', '<+05>-5' }, + { 'Asia/Baghdad', '<+03>-3' }, + { 'Asia/Bahrain', '<+03>-3' }, + { 'Asia/Baku', '<+04>-4' }, + { 'Asia/Bangkok', '<+07>-7' }, + { 'Asia/Barnaul', '<+07>-7' }, + { 'Asia/Beirut', 'EET-2EEST,M3.5.0/0,M10.5.0/0' }, + { 'Asia/Bishkek', '<+06>-6' }, + { 'Asia/Brunei', '<+08>-8' }, + { 'Asia/Chita', '<+09>-9' }, + { 'Asia/Choibalsan', '<+08>-8' }, + { 'Asia/Colombo', '<+0530>-5:30' }, + { 'Asia/Damascus', 'EET-2EEST,M3.5.5/0,M10.5.5/0' }, + { 'Asia/Dhaka', '<+06>-6' }, + { 'Asia/Dili', '<+09>-9' }, + { 'Asia/Dubai', '<+04>-4' }, + { 'Asia/Dushanbe', '<+05>-5' }, + { 'Asia/Famagusta', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Asia/Gaza', 'EET-2EEST,M3.4.4/72,M10.4.4/25' }, + { 'Asia/Hebron', 'EET-2EEST,M3.4.4/72,M10.4.4/25' }, + { 'Asia/Ho Chi Minh', '<+07>-7' }, + { 'Asia/Hong Kong', 'HKT-8' }, + { 'Asia/Hovd', '<+07>-7' }, + { 'Asia/Irkutsk', '<+08>-8' }, + { 'Asia/Jakarta', 'WIB-7' }, + { 'Asia/Jayapura', 'WIT-9' }, + { 'Asia/Jerusalem', 'IST-2IDT,M3.4.4/26,M10.5.0' }, + { 'Asia/Kabul', '<+0430>-4:30' }, + { 'Asia/Kamchatka', '<+12>-12' }, + { 'Asia/Karachi', 'PKT-5' }, + { 'Asia/Kathmandu', '<+0545>-5:45' }, + { 'Asia/Khandyga', '<+09>-9' }, + { 'Asia/Kolkata', 'IST-5:30' }, + { 'Asia/Krasnoyarsk', '<+07>-7' }, + { 'Asia/Kuala Lumpur', '<+08>-8' }, + { 'Asia/Kuching', '<+08>-8' }, + { 'Asia/Kuwait', '<+03>-3' }, + { 'Asia/Macau', 'CST-8' }, + { 'Asia/Magadan', '<+11>-11' }, + { 'Asia/Makassar', 'WITA-8' }, + { 'Asia/Manila', 'PST-8' }, + { 'Asia/Muscat', '<+04>-4' }, + { 'Asia/Nicosia', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Asia/Novokuznetsk', '<+07>-7' }, + { 'Asia/Novosibirsk', '<+07>-7' }, + { 'Asia/Omsk', '<+06>-6' }, + { 'Asia/Oral', '<+05>-5' }, + { 'Asia/Phnom Penh', '<+07>-7' }, + { 'Asia/Pontianak', 'WIB-7' }, + { 'Asia/Pyongyang', 'KST-9' }, + { 'Asia/Qatar', '<+03>-3' }, + { 'Asia/Qostanay', '<+06>-6' }, + { 'Asia/Qyzylorda', '<+05>-5' }, + { 'Asia/Riyadh', '<+03>-3' }, + { 'Asia/Sakhalin', '<+11>-11' }, + { 'Asia/Samarkand', '<+05>-5' }, + { 'Asia/Seoul', 'KST-9' }, + { 'Asia/Shanghai', 'CST-8' }, + { 'Asia/Singapore', '<+08>-8' }, + { 'Asia/Srednekolymsk', '<+11>-11' }, + { 'Asia/Taipei', 'CST-8' }, + { 'Asia/Tashkent', '<+05>-5' }, + { 'Asia/Tbilisi', '<+04>-4' }, + { 'Asia/Tehran', '<+0330>-3:30' }, + { 'Asia/Thimphu', '<+06>-6' }, + { 'Asia/Tokyo', 'JST-9' }, + { 'Asia/Tomsk', '<+07>-7' }, + { 'Asia/Ulaanbaatar', '<+08>-8' }, + { 'Asia/Urumqi', '<+06>-6' }, + { 'Asia/Ust-Nera', '<+10>-10' }, + { 'Asia/Vientiane', '<+07>-7' }, + { 'Asia/Vladivostok', '<+10>-10' }, + { 'Asia/Yakutsk', '<+09>-9' }, + { 'Asia/Yangon', '<+0630>-6:30' }, + { 'Asia/Yekaterinburg', '<+05>-5' }, + { 'Asia/Yerevan', '<+04>-4' }, + { 'Atlantic/Azores', '<-01>1<+00>,M3.5.0/0,M10.5.0/1' }, + { 'Atlantic/Bermuda', 'AST4ADT,M3.2.0,M11.1.0' }, + { 'Atlantic/Canary', 'WET0WEST,M3.5.0/1,M10.5.0' }, + { 'Atlantic/Cape Verde', '<-01>1' }, + { 'Atlantic/Faroe', 'WET0WEST,M3.5.0/1,M10.5.0' }, + { 'Atlantic/Madeira', 'WET0WEST,M3.5.0/1,M10.5.0' }, + { 'Atlantic/Reykjavik', 'GMT0' }, + { 'Atlantic/South Georgia', '<-02>2' }, + { 'Atlantic/St Helena', 'GMT0' }, + { 'Atlantic/Stanley', '<-03>3' }, + { 'Australia/Adelaide', 'ACST-9:30ACDT,M10.1.0,M4.1.0/3' }, + { 'Australia/Brisbane', 'AEST-10' }, + { 'Australia/Broken Hill', 'ACST-9:30ACDT,M10.1.0,M4.1.0/3' }, + { 'Australia/Darwin', 'ACST-9:30' }, + { 'Australia/Eucla', '<+0845>-8:45' }, + { 'Australia/Hobart', 'AEST-10AEDT,M10.1.0,M4.1.0/3' }, + { 'Australia/Lindeman', 'AEST-10' }, + { 'Australia/Lord Howe', '<+1030>-10:30<+11>-11,M10.1.0,M4.1.0' }, + { 'Australia/Melbourne', 'AEST-10AEDT,M10.1.0,M4.1.0/3' }, + { 'Australia/Perth', 'AWST-8' }, + { 'Australia/Sydney', 'AEST-10AEDT,M10.1.0,M4.1.0/3' }, + { 'Etc/GMT', 'GMT0' }, + { 'Etc/GMT+1', '<-01>1' }, + { 'Etc/GMT+10', '<-10>10' }, + { 'Etc/GMT+11', '<-11>11' }, + { 'Etc/GMT+12', '<-12>12' }, + { 'Etc/GMT+2', '<-02>2' }, + { 'Etc/GMT+3', '<-03>3' }, + { 'Etc/GMT+4', '<-04>4' }, + { 'Etc/GMT+5', '<-05>5' }, + { 'Etc/GMT+6', '<-06>6' }, + { 'Etc/GMT+7', '<-07>7' }, + { 'Etc/GMT+8', '<-08>8' }, + { 'Etc/GMT+9', '<-09>9' }, + { 'Etc/GMT-1', '<+01>-1' }, + { 'Etc/GMT-10', '<+10>-10' }, + { 'Etc/GMT-11', '<+11>-11' }, + { 'Etc/GMT-12', '<+12>-12' }, + { 'Etc/GMT-13', '<+13>-13' }, + { 'Etc/GMT-14', '<+14>-14' }, + { 'Etc/GMT-2', '<+02>-2' }, + { 'Etc/GMT-3', '<+03>-3' }, + { 'Etc/GMT-4', '<+04>-4' }, + { 'Etc/GMT-5', '<+05>-5' }, + { 'Etc/GMT-6', '<+06>-6' }, + { 'Etc/GMT-7', '<+07>-7' }, + { 'Etc/GMT-8', '<+08>-8' }, + { 'Etc/GMT-9', '<+09>-9' }, + { 'Europe/Amsterdam', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Andorra', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Astrakhan', '<+04>-4' }, + { 'Europe/Athens', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Belgrade', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Berlin', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Bratislava', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Brussels', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Bucharest', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Budapest', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Busingen', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Chisinau', 'EET-2EEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Copenhagen', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Dublin', 'IST-1GMT0,M10.5.0,M3.5.0/1' }, + { 'Europe/Gibraltar', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Guernsey', 'GMT0BST,M3.5.0/1,M10.5.0' }, + { 'Europe/Helsinki', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Isle of Man', 'GMT0BST,M3.5.0/1,M10.5.0' }, + { 'Europe/Istanbul', '<+03>-3' }, + { 'Europe/Jersey', 'GMT0BST,M3.5.0/1,M10.5.0' }, + { 'Europe/Kaliningrad', 'EET-2' }, + { 'Europe/Kirov', '<+03>-3' }, + { 'Europe/Kyiv', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Lisbon', 'WET0WEST,M3.5.0/1,M10.5.0' }, + { 'Europe/Ljubljana', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/London', 'GMT0BST,M3.5.0/1,M10.5.0' }, + { 'Europe/Luxembourg', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Madrid', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Malta', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Mariehamn', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Minsk', '<+03>-3' }, + { 'Europe/Monaco', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Moscow', 'MSK-3' }, + { 'Europe/Oslo', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Paris', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Podgorica', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Prague', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Riga', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Rome', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Samara', '<+04>-4' }, + { 'Europe/San Marino', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Sarajevo', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Saratov', '<+04>-4' }, + { 'Europe/Simferopol', 'MSK-3' }, + { 'Europe/Skopje', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Sofia', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Stockholm', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Tallinn', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Tirane', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Ulyanovsk', '<+04>-4' }, + { 'Europe/Uzhgorod', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Vaduz', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Vatican', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Vienna', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Vilnius', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Volgograd', '<+03>-3' }, + { 'Europe/Warsaw', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Zagreb', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Zaporozhye', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Zurich', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Indian/Antananarivo', 'EAT-3' }, + { 'Indian/Chagos', '<+06>-6' }, + { 'Indian/Christmas', '<+07>-7' }, + { 'Indian/Cocos', '<+0630>-6:30' }, + { 'Indian/Comoro', 'EAT-3' }, + { 'Indian/Kerguelen', '<+05>-5' }, + { 'Indian/Mahe', '<+04>-4' }, + { 'Indian/Maldives', '<+05>-5' }, + { 'Indian/Mauritius', '<+04>-4' }, + { 'Indian/Mayotte', 'EAT-3' }, + { 'Indian/Reunion', '<+04>-4' }, + { 'Pacific/Apia', '<+13>-13' }, + { 'Pacific/Auckland', 'NZST-12NZDT,M9.5.0,M4.1.0/3' }, + { 'Pacific/Bougainville', '<+11>-11' }, + { 'Pacific/Chatham', '<+1245>-12:45<+1345>,M9.5.0/2:45,M4.1.0/3:45' }, + { 'Pacific/Chuuk', '<+10>-10' }, + { 'Pacific/Easter', '<-06>6<-05>,M9.1.6/22,M4.1.6/22' }, + { 'Pacific/Efate', '<+11>-11' }, + { 'Pacific/Fakaofo', '<+13>-13' }, + { 'Pacific/Fiji', '<+12>-12<+13>,M11.2.0,M1.2.3/99' }, + { 'Pacific/Funafuti', '<+12>-12' }, + { 'Pacific/Galapagos', '<-06>6' }, + { 'Pacific/Gambier', '<-09>9' }, + { 'Pacific/Guadalcanal', '<+11>-11' }, + { 'Pacific/Guam', 'ChST-10' }, + { 'Pacific/Honolulu', 'HST10' }, + { 'Pacific/Kanton', '<+13>-13' }, + { 'Pacific/Kiritimati', '<+14>-14' }, + { 'Pacific/Kosrae', '<+11>-11' }, + { 'Pacific/Kwajalein', '<+12>-12' }, + { 'Pacific/Majuro', '<+12>-12' }, + { 'Pacific/Marquesas', '<-0930>9:30' }, + { 'Pacific/Midway', 'SST11' }, + { 'Pacific/Nauru', '<+12>-12' }, + { 'Pacific/Niue', '<-11>11' }, + { 'Pacific/Norfolk', '<+11>-11<+12>,M10.1.0,M4.1.0/3' }, + { 'Pacific/Noumea', '<+11>-11' }, + { 'Pacific/Pago Pago', 'SST11' }, + { 'Pacific/Palau', '<+09>-9' }, + { 'Pacific/Pitcairn', '<-08>8' }, + { 'Pacific/Pohnpei', '<+11>-11' }, + { 'Pacific/Port Moresby', '<+10>-10' }, + { 'Pacific/Rarotonga', '<-10>10' }, + { 'Pacific/Saipan', 'ChST-10' }, + { 'Pacific/Tahiti', '<-10>10' }, + { 'Pacific/Tarawa', '<+12>-12' }, + { 'Pacific/Tongatapu', '<+13>-13' }, + { 'Pacific/Wake', '<+12>-12' }, + { 'Pacific/Wallis', '<+12>-12' }, +} diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sys/zoneinfo/tzdata.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/sys/zoneinfo/tzdata.luac new file mode 100644 index 0000000000000000000000000000000000000000..4391f64ec7fb17ef4b6a880e8698e8ca5b2b6f92 GIT binary patch literal 28384 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�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 +-- Licensed to the public under the Apache License 2.0. + +local util = require "luci.util" +local config = require "luci.config" +local tparser = require "luci.template.parser" + +local tostring, pairs, loadstring = tostring, pairs, loadstring +local setmetatable, loadfile = setmetatable, loadfile +local getfenv, setfenv, rawget = getfenv, setfenv, rawget +local assert, type, error = assert, type, error + +--- LuCI template library. +module "luci.template" + +config.template = config.template or {} +viewdir = config.template.viewdir or util.libpath() .. "/view" + + +-- Define the namespace for template modules +context = util.threadlocal() + +--- Render a certain template. +-- @param name Template name +-- @param scope Scope to assign to template (optional) +function render(name, scope) + return Template(name):render(scope or getfenv(2)) +end + +--- Render a template from a string. +-- @param template Template string +-- @param scope Scope to assign to template (optional) +function render_string(template, scope) + return Template(nil, template):render(scope or getfenv(2)) +end + + +-- Template class +Template = util.class() + +-- Shared template cache to store templates in to avoid unnecessary reloading +Template.cache = setmetatable({}, {__mode = "v"}) + + +-- Constructor - Reads and compiles the template on-demand +function Template.__init__(self, name, template) + if name then + self.template = self.cache[name] + self.name = name + else + self.name = "[string]" + end + + -- Create a new namespace for this template + self.viewns = context.viewns + + -- If we have a cached template, skip compiling and loading + if not self.template then + + -- Compile template + local err + local sourcefile + + if name then + sourcefile = viewdir .. "/" .. name .. ".htm" + self.template, _, err = tparser.parse(sourcefile) + else + sourcefile = "[string]" + self.template, _, err = tparser.parse_string(template) + end + + -- If we have no valid template throw error, otherwise cache the template + if not self.template then + error("Failed to load template '" .. self.name .. "'.\n" .. + "Error while parsing template '" .. sourcefile .. "':\n" .. + (err or "Unknown syntax error")) + elseif name then + self.cache[name] = self.template + end + end +end + + +-- Renders a template +function Template.render(self, scope) + scope = scope or getfenv(2) + + -- Put our predefined objects in the scope of the template + setfenv(self.template, setmetatable({}, {__index = + function(tbl, key) + return rawget(tbl, key) or self.viewns[key] or scope[key] + end})) + + -- Now finally render the thing + local stat, err = util.copcall(self.template) + if not stat then + error("Failed to execute template '" .. self.name .. "'.\n" .. + "A runtime error occurred: " .. tostring(err or "(nil)")) + end +end diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/template.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/template.luac new file mode 100644 index 0000000000000000000000000000000000000000..2c677e8195210a7b141b203f8b521e7eff84df6a GIT binary patch literal 3988 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 TU9Eh9C-I}(N8d|2e^~S%$;>l6 literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/util.lua b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/util.lua new file mode 100644 index 000000000000..89757917ff65 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/util.lua @@ -0,0 +1,782 @@ +-- Copyright 2008 Steven Barth +-- Licensed to the public under the Apache License 2.0. + +local io = require "io" +local math = require "math" +local table = require "table" +local debug = require "debug" +local ldebug = require "luci.debug" +local string = require "string" +local coroutine = require "coroutine" +local tparser = require "luci.template.parser" +local json = require "luci.jsonc" +local lhttp = require "lucihttp" + +local _ubus = require "ubus" +local _ubus_connection = nil + +local getmetatable, setmetatable = getmetatable, setmetatable +local rawget, rawset, unpack, select = rawget, rawset, unpack, select +local tostring, type, assert, error = tostring, type, assert, error +local ipairs, pairs, next, loadstring = ipairs, pairs, next, loadstring +local require, pcall, xpcall = require, pcall, xpcall +local collectgarbage, get_memory_limit = collectgarbage, get_memory_limit + +module "luci.util" + +-- +-- Pythonic string formatting extension +-- +getmetatable("").__mod = function(a, b) + local ok, res + + if not b then + return a + elseif type(b) == "table" then + local k, _ + for k, _ in pairs(b) do if type(b[k]) == "userdata" then b[k] = tostring(b[k]) end end + + ok, res = pcall(a.format, a, unpack(b)) + if not ok then + error(res, 2) + end + return res + else + if type(b) == "userdata" then b = tostring(b) end + + ok, res = pcall(a.format, a, b) + if not ok then + error(res, 2) + end + return res + end +end + + +-- +-- Class helper routines +-- + +-- Instantiates a class +local function _instantiate(class, ...) + local inst = setmetatable({}, {__index = class}) + + if inst.__init__ then + inst:__init__(...) + end + + return inst +end + +-- The class object can be instantiated by calling itself. +-- Any class functions or shared parameters can be attached to this object. +-- Attaching a table to the class object makes this table shared between +-- all instances of this class. For object parameters use the __init__ function. +-- Classes can inherit member functions and values from a base class. +-- Class can be instantiated by calling them. All parameters will be passed +-- to the __init__ function of this class - if such a function exists. +-- The __init__ function must be used to set any object parameters that are not shared +-- with other objects of this class. Any return values will be ignored. +function class(base) + return setmetatable({}, { + __call = _instantiate, + __index = base + }) +end + +function instanceof(object, class) + local meta = getmetatable(object) + while meta and meta.__index do + if meta.__index == class then + return true + end + meta = getmetatable(meta.__index) + end + return false +end + + +-- +-- Scope manipulation routines +-- + +coxpt = setmetatable({}, { __mode = "kv" }) + +local tl_meta = { + __mode = "k", + + __index = function(self, key) + local t = rawget(self, coxpt[coroutine.running()] + or coroutine.running() or 0) + return t and t[key] + end, + + __newindex = function(self, key, value) + local c = coxpt[coroutine.running()] or coroutine.running() or 0 + local r = rawget(self, c) + if not r then + rawset(self, c, { [key] = value }) + else + r[key] = value + end + end +} + +-- the current active coroutine. A thread local store is private a table object +-- whose values can't be accessed from outside of the running coroutine. +function threadlocal(tbl) + return setmetatable(tbl or {}, tl_meta) +end + + +-- +-- Debugging routines +-- + +function perror(obj) + return io.stderr:write(tostring(obj) .. "\n") +end + +function dumptable(t, maxdepth, i, seen) + i = i or 0 + seen = seen or setmetatable({}, {__mode="k"}) + + for k,v in pairs(t) do + perror(string.rep("\t", i) .. tostring(k) .. "\t" .. tostring(v)) + if type(v) == "table" and (not maxdepth or i < maxdepth) then + if not seen[v] then + seen[v] = true + dumptable(v, maxdepth, i+1, seen) + else + perror(string.rep("\t", i) .. "*** RECURSION ***") + end + end + end +end + + +-- +-- String and data manipulation routines +-- + +-- compatibility wrapper for xml.pcdata +function pcdata(value) + local xml = require "luci.xml" + + perror("luci.util.pcdata() has been replaced by luci.xml.pcdata() - Please update your code.") + return xml.pcdata(value) +end + +function urlencode(value) + if value ~= nil then + local str = tostring(value) + return lhttp.urlencode(str, lhttp.ENCODE_IF_NEEDED + lhttp.ENCODE_FULL) + or str + end + return nil +end + +function urldecode(value, decode_plus) + if value ~= nil then + local flag = decode_plus and lhttp.DECODE_PLUS or 0 + local str = tostring(value) + return lhttp.urldecode(str, lhttp.DECODE_IF_NEEDED + flag) + or str + end + return nil +end + +-- compatibility wrapper for xml.striptags +function striptags(value) + local xml = require "luci.xml" + + perror("luci.util.striptags() has been replaced by luci.xml.striptags() - Please update your code.") + return xml.striptags(value) +end + +function shellquote(value) + return string.format("'%s'", string.gsub(value or "", "'", "'\\''")) +end + +-- for bash, ash and similar shells single-quoted strings are taken +-- literally except for single quotes (which terminate the string) +-- (and the exception noted below for dash (-) at the start of a +-- command line parameter). +function shellsqescape(value) + local res + res, _ = string.gsub(value, "'", "'\\''") + return res +end + +-- bash, ash and other similar shells interpret a dash (-) at the start +-- of a command-line parameters as an option indicator regardless of +-- whether it is inside a single-quoted string. It must be backlash +-- escaped to resolve this. This requires in some funky special-case +-- handling. It may actually be a property of the getopt function +-- rather than the shell proper. +function shellstartsqescape(value) + res, _ = string.gsub(value, "^%-", "\\-") + return shellsqescape(res) +end + +-- containing the resulting substrings. The optional max parameter specifies +-- the number of bytes to process, regardless of the actual length of the given +-- string. The optional last parameter, regex, specifies whether the separator +-- sequence is interpreted as regular expression. +-- pattern as regular expression (optional, default is false) +function split(str, pat, max, regex) + pat = pat or "\n" + max = max or #str + + local t = {} + local c = 1 + + if #str == 0 then + return {""} + end + + if #pat == 0 then + return nil + end + + if max == 0 then + return str + end + + repeat + local s, e = str:find(pat, c, not regex) + max = max - 1 + if s and max < 0 then + t[#t+1] = str:sub(c) + else + t[#t+1] = str:sub(c, s and s - 1) + end + c = e and e + 1 or #str + 1 + until not s or max < 0 + + return t +end + +function trim(str) + return (str:gsub("^%s*(.-)%s*$", "%1")) +end + +function cmatch(str, pat) + local count = 0 + for _ in str:gmatch(pat) do count = count + 1 end + return count +end + +-- one token per invocation, the tokens are separated by whitespace. If the +-- input value is a table, it is transformed into a string first. A nil value +-- will result in a valid iterator which aborts with the first invocation. +function imatch(v) + if type(v) == "table" then + local k = nil + return function() + k = next(v, k) + return v[k] + end + + elseif type(v) == "number" or type(v) == "boolean" then + local x = true + return function() + if x then + x = false + return tostring(v) + end + end + + elseif type(v) == "userdata" or type(v) == "string" then + return tostring(v):gmatch("%S+") + end + + return function() end +end + +-- value or 0 if the unit is unknown. Upper- or lower case is irrelevant. +-- Recognized units are: +-- o "y" - one year (60*60*24*366) +-- o "m" - one month (60*60*24*31) +-- o "w" - one week (60*60*24*7) +-- o "d" - one day (60*60*24) +-- o "h" - one hour (60*60) +-- o "min" - one minute (60) +-- o "kb" - one kilobyte (1024) +-- o "mb" - one megabyte (1024*1024) +-- o "gb" - one gigabyte (1024*1024*1024) +-- o "kib" - one si kilobyte (1000) +-- o "mib" - one si megabyte (1000*1000) +-- o "gib" - one si gigabyte (1000*1000*1000) +function parse_units(ustr) + + local val = 0 + + -- unit map + local map = { + -- date stuff + y = 60 * 60 * 24 * 366, + m = 60 * 60 * 24 * 31, + w = 60 * 60 * 24 * 7, + d = 60 * 60 * 24, + h = 60 * 60, + min = 60, + + -- storage sizes + kb = 1024, + mb = 1024 * 1024, + gb = 1024 * 1024 * 1024, + + -- storage sizes (si) + kib = 1000, + mib = 1000 * 1000, + gib = 1000 * 1000 * 1000 + } + + -- parse input string + for spec in ustr:lower():gmatch("[0-9%.]+[a-zA-Z]*") do + + local num = spec:gsub("[^0-9%.]+$","") + local spn = spec:gsub("^[0-9%.]+", "") + + if map[spn] or map[spn:sub(1,1)] then + val = val + num * ( map[spn] or map[spn:sub(1,1)] ) + else + val = val + num + end + end + + + return val +end + +-- also register functions above in the central string class for convenience +string.split = split +string.trim = trim +string.cmatch = cmatch +string.parse_units = parse_units + + +function append(src, ...) + for i, a in ipairs({...}) do + if type(a) == "table" then + for j, v in ipairs(a) do + src[#src+1] = v + end + else + src[#src+1] = a + end + end + return src +end + +function combine(...) + return append({}, ...) +end + +function contains(table, value) + for k, v in pairs(table) do + if value == v then + return k + end + end + return false +end + +-- Both table are - in fact - merged together. +function update(t, updates) + for k, v in pairs(updates) do + t[k] = v + end +end + +function keys(t) + local keys = { } + if t then + for k, _ in kspairs(t) do + keys[#keys+1] = k + end + end + return keys +end + +function clone(object, deep) + local copy = {} + + for k, v in pairs(object) do + if deep and type(v) == "table" then + v = clone(v, deep) + end + copy[k] = v + end + + return setmetatable(copy, getmetatable(object)) +end + + +-- Serialize the contents of a table value. +function _serialize_table(t, seen) + assert(not seen[t], "Recursion detected.") + seen[t] = true + + local data = "" + local idata = "" + local ilen = 0 + + for k, v in pairs(t) do + if type(k) ~= "number" or k < 1 or math.floor(k) ~= k or ( k - #t ) > 3 then + k = serialize_data(k, seen) + v = serialize_data(v, seen) + data = data .. ( #data > 0 and ", " or "" ) .. + '[' .. k .. '] = ' .. v + elseif k > ilen then + ilen = k + end + end + + for i = 1, ilen do + local v = serialize_data(t[i], seen) + idata = idata .. ( #idata > 0 and ", " or "" ) .. v + end + + return idata .. ( #data > 0 and #idata > 0 and ", " or "" ) .. data +end + +-- with loadstring(). +function serialize_data(val, seen) + seen = seen or setmetatable({}, {__mode="k"}) + + if val == nil then + return "nil" + elseif type(val) == "number" then + return val + elseif type(val) == "string" then + return "%q" % val + elseif type(val) == "boolean" then + return val and "true" or "false" + elseif type(val) == "function" then + return "loadstring(%q)" % get_bytecode(val) + elseif type(val) == "table" then + return "{ " .. _serialize_table(val, seen) .. " }" + else + return '"[unhandled data type:' .. type(val) .. ']"' + end +end + +function restore_data(str) + return loadstring("return " .. str)() +end + + +-- +-- Byte code manipulation routines +-- + +-- will be stripped before it is returned. +function get_bytecode(val) + local code + + if type(val) == "function" then + code = string.dump(val) + else + code = string.dump( loadstring( "return " .. serialize_data(val) ) ) + end + + return code -- and strip_bytecode(code) +end + +-- numbers and debugging numbers will be discarded. Original version by +-- Peter Cawley (http://lua-users.org/lists/lua-l/2008-02/msg01158.html) +function strip_bytecode(code) + local version, format, endian, int, size, ins, num, lnum = code:byte(5, 12) + local subint + if endian == 1 then + subint = function(code, i, l) + local val = 0 + for n = l, 1, -1 do + val = val * 256 + code:byte(i + n - 1) + end + return val, i + l + end + else + subint = function(code, i, l) + local val = 0 + for n = 1, l, 1 do + val = val * 256 + code:byte(i + n - 1) + end + return val, i + l + end + end + + local function strip_function(code) + local count, offset = subint(code, 1, size) + local stripped = { string.rep("\0", size) } + local dirty = offset + count + offset = offset + count + int * 2 + 4 + offset = offset + int + subint(code, offset, int) * ins + count, offset = subint(code, offset, int) + for n = 1, count do + local t + t, offset = subint(code, offset, 1) + if t == 1 then + offset = offset + 1 + elseif t == 4 then + offset = offset + size + subint(code, offset, size) + elseif t == 3 then + offset = offset + num + elseif t == 254 or t == 9 then + offset = offset + lnum + end + end + count, offset = subint(code, offset, int) + stripped[#stripped+1] = code:sub(dirty, offset - 1) + for n = 1, count do + local proto, off = strip_function(code:sub(offset, -1)) + stripped[#stripped+1] = proto + offset = offset + off - 1 + end + offset = offset + subint(code, offset, int) * int + int + count, offset = subint(code, offset, int) + for n = 1, count do + offset = offset + subint(code, offset, size) + size + int * 2 + end + count, offset = subint(code, offset, int) + for n = 1, count do + offset = offset + subint(code, offset, size) + size + end + stripped[#stripped+1] = string.rep("\0", int * 3) + return table.concat(stripped), offset + end + + return code:sub(1,12) .. strip_function(code:sub(13,-1)) +end + + +-- +-- Sorting iterator functions +-- + +function _sortiter( t, f ) + local keys = { } + + local k, v + for k, v in pairs(t) do + keys[#keys+1] = k + end + + local _pos = 0 + + table.sort( keys, f ) + + return function() + _pos = _pos + 1 + if _pos <= #keys then + return keys[_pos], t[keys[_pos]], _pos + end + end +end + +-- the provided callback function. +function spairs(t,f) + return _sortiter( t, f ) +end + +-- The table pairs are sorted by key. +function kspairs(t) + return _sortiter( t ) +end + +-- The table pairs are sorted by value. +function vspairs(t) + return _sortiter( t, function (a,b) return t[a] < t[b] end ) +end + + +-- +-- System utility functions +-- + +function bigendian() + return string.byte(string.dump(function() end), 7) == 0 +end + +function exec(command) + local pp = io.popen(command) + local data = pp:read("*a") + pp:close() + + return data +end + +function execi(command) + local pp = io.popen(command) + + return pp and function() + local line = pp:read() + + if not line then + pp:close() + end + + return line + end +end + +-- Deprecated +function execl(command) + local pp = io.popen(command) + local line = "" + local data = {} + + while true do + line = pp:read() + if (line == nil) then break end + data[#data+1] = line + end + pp:close() + + return data +end + + +local ubus_codes = { + "INVALID_COMMAND", + "INVALID_ARGUMENT", + "METHOD_NOT_FOUND", + "NOT_FOUND", + "NO_DATA", + "PERMISSION_DENIED", + "TIMEOUT", + "NOT_SUPPORTED", + "UNKNOWN_ERROR", + "CONNECTION_FAILED" +} + +local function ubus_return(...) + if select('#', ...) == 2 then + local rv, err = select(1, ...), select(2, ...) + if rv == nil and type(err) == "number" then + return nil, err, ubus_codes[err] + end + end + + return ... +end + +function ubus(object, method, data, path, timeout) + if not _ubus_connection then + _ubus_connection = _ubus.connect(path, timeout) + assert(_ubus_connection, "Unable to establish ubus connection") + end + + if object and method then + if type(data) ~= "table" then + data = { } + end + return ubus_return(_ubus_connection:call(object, method, data)) + elseif object then + return _ubus_connection:signatures(object) + else + return _ubus_connection:objects() + end +end + +function serialize_json(x, cb) + local js = json.stringify(x) + if type(cb) == "function" then + cb(js) + else + return js + end +end + + +function libpath() + return require "nixio.fs".dirname(ldebug.__file__) +end + +function checklib(fullpathexe, wantedlib) + local fs = require "nixio.fs" + local haveldd = fs.access('/usr/bin/ldd') + local haveexe = fs.access(fullpathexe) + if not haveldd or not haveexe then + return false + end + local libs = exec(string.format("/usr/bin/ldd %s", shellquote(fullpathexe))) + if not libs then + return false + end + for k, v in ipairs(split(libs)) do + if v:find(wantedlib) then + return true + end + end + return false +end + +------------------------------------------------------------------------------- +-- Coroutine safe xpcall and pcall versions +-- +-- Encapsulates the protected calls with a coroutine based loop, so errors can +-- be dealed without the usual Lua 5.x pcall/xpcall issues with coroutines +-- yielding inside the call to pcall or xpcall. +-- +-- Authors: Roberto Ierusalimschy and Andre Carregal +-- Contributors: Thomas Harning Jr., Ignacio Burgueño, Fabio Mascarenhas +-- +-- Copyright 2005 - Kepler Project +-- +-- $Id: coxpcall.lua,v 1.13 2008/05/19 19:20:02 mascarenhas Exp $ +------------------------------------------------------------------------------- + +------------------------------------------------------------------------------- +-- Implements xpcall with coroutines +------------------------------------------------------------------------------- +local coromap = setmetatable({}, { __mode = "k" }) + +local function handleReturnValue(err, co, status, ...) + if not status then + return false, err(debug.traceback(co, (...)), ...) + end + if coroutine.status(co) == 'suspended' then + return performResume(err, co, coroutine.yield(...)) + else + return true, ... + end +end + +function performResume(err, co, ...) + return handleReturnValue(err, co, coroutine.resume(co, ...)) +end + +local function id(trace, ...) + return trace +end + +function coxpcall(f, err, ...) + local current = coroutine.running() + if not current then + if err == id then + return pcall(f, ...) + else + if select("#", ...) > 0 then + local oldf, params = f, { ... } + f = function() return oldf(unpack(params)) end + end + return xpcall(f, err) + end + else + local res, co = pcall(coroutine.create, f) + if not res then + local newf = function(...) return f(...) end + co = coroutine.create(newf) + end + coromap[co] = current + coxpt[co] = coxpt[current] or current or 0 + return performResume(err, co, ...) + end +end + +function copcall(f, ...) + return coxpcall(f, id, ...) +end diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/util.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/util.luac new file mode 100644 index 0000000000000000000000000000000000000000..6cabeaf2b2053fbb63d04b5233c702f093bd0497 GIT binary patch literal 30732 zcmdU&dz4(oedp`m?jDUEykUevb_NXLVFON_Y>qdHeS0*chY^DC2nh^~M>8#H&}e2n z4*^cHsvbsS1USJq-ej@NSvw^0aqJ}CY#ztnwY!BRB(Qgrjq^u{Le>t>=HyA9&I8-~ z`PRL^+qYXiLQc;9v32I#RrRZ1y?#~otH-T-=kjd}qd(-La&NiUt#PSI7Um}=Z=75h z&HS?BxAsNORd7`k=Qem34dJeP=Z0|~z};*(cNq6E+))&{Q@AU{sa7Mmp$3{7vD>f+KkoWs?1ph4z}-9&yTiDji+!b1D!HMJC3oPF zk~{WX$yLf_H#Asw2bRk2*wM19R4Q(0sNxPBsJLUtDz4JgtjD%EcWC+K2RAJHXjSiOw+5coCr*BFyqC&5 zw+fV2Z|Qw%d`s|`Qkq}2ddu=tX>dZ^2&1UdBG5BqcYu4v;pq-U__ux*_kZhTh1j-;w@vJ5Z$lcd;)i`M1i~aJo4@#Z|voimgXB|)w6jS`U%n1v4d<> zcenWj^j?Wve3H}oq!aw>HgCbb9NgkXKz`lkWoYz|)bFCrC-Bop@T*@$n|m)HelO*~ zUdCR=K7oA#o3>WG&Ak`GBe#PO_j2Z5#(g4lpSaM`S1R@1i(GTdMJ_sYk*l7($kkV0 z?3!CIcF|)OyXp%UyZV|-T=U(RxE)(u<=I}1Sx;d?KRy4IEx|8g397dezu}oZ;qoa7}8Z#=N6VAnb!J~)z+_PK7<1c*lWS@HzZ8D4x z1(cfE+Itr!W^1mjz9us1c9V927)*Y?g#j?UL^oEe>+tIc*K`oP@u)R-%$ z4VT8)JwHFwD(1qDg*jK2Y-M)U=J(d-N3+rzb*_zS1)Lpy5O!BC_+f1M7p7)L$M&@R zbG6CZ7~xiy`Dv@7iERGi8ER+gjm}XW_)TWD+1Y8TXTcLQqZ6|f-GVc(r8QN1h`70c zr$@&vw>-kw=;UN8y@zH}|9Pn-koTm@yK8iI$LKDKbwNg_&Lewkd#7g~9+{ljJ5i|c z-s$lLHL|r>XjRQnuAD2vytpGHu(wJ-Hc53`<(-(Cn;)GTt4;4LuuMNRQ^+kOKyKFe z_Uv=zpf@QeQnx-jGBPnWUVEsO^vK9m?ZJ!|`ak^hyJu^oC)5GT0_`+(+GWhVsvui zeYKHHw$}ez0jZ2_FwWNI=BHDl>-d1(u)=v+2_ ztfG553Hyv-iR_rzg=i*5r&?95Jyb*4SXJelX!p{+&d=I$>-Hb6ZreIIvhmhiZmDh` zY>C7Is<&^x>z1MIceX-q8M<@Jt%D=mZ@qJ5)2(+Yj+LcAvI=a!b!4!5XVsOZ^JQ)u zy8V`|cige{*6kyML)*6w5#4z2++l_qZGI61LnCQJ}{KiXXz+XJG=`lGYY9Q5AR{F8&lr0%69`(8Nyfji0 z`LNzy?~=HW1z;)i@e;HKILyMh&&B>IDy3)RqC3TUc74*Q^fjDlktsZn!I;P~^Ci58o6Zrf;gkB9aT0cB>&NQ ztq_7q^ebszk>N^g;muue6|}4W4Y-E5uZL^l5w7dNTks1voCR)zXC=IiFyXs8@OJ#_ zU7B8FATt_%tNa>eM@}&TPVdP{Y6uzBS+I){_k<0eDrHHH)UcSid0(#$b{932r{Ymw4RhN zZSvqSVy#+{aPh`Sa! zcPer28KS9|t17v2%U1waIuR!3M@C>uSJm&t?nDC3ptdm#;#Tq0q*j4+@d6=8-&%Hb zc9%$jW-`;dCJ0c?M4>~oiK%UgDT2&x;4TnHuA;&#@5jIkp6W&3opg~u>Ab%WRd7G_ zyy_p>-+A`U$O zMg7|TBhVBk8K}soH9s_`41c76AI05?d!QNa#Vy>|1?oP+dw@EF$$Wap16twb=-4ef0a}*@ypl;=%+Z*{Le|3T7-_a@#~ZyD&ASJ(+lNqk`;R0aMUl4~je8 z0aa3&RDcXyr=7teF_nXhMSkt!oTk1gGw-Ay#U+Y0Nw!n7#Zqf02~RMy{JsxxShacs z_A#Ipf(h=pt4A*-sX&hsc06g&`Me7>1laWA_jFrH*GC9D3c|)S$n5Q*H~3vMz_?pv zRZw-TIN09TeHgUgw$N2b%^Tk?=803p0)AoN8SoHc5>6!pR;xiS$J5y$qFJ}!H#)gc zQw%4nHPcv797W2k(~dwC$>Q{u>S)S>S9@+V`*F~>r7*+zAH@BUL`ubZDbAzLGnQM* z5>hbg;QkCynlxcv)A=15ifIQ^olN7ksX-ex^ONkC8Rlk|ai}oT>RLdkkegh?-JCQ+ z0hQA!&-_QOze~p}7C3YitNh&jI8$e9v_3e?mZr4{PKBr(4xJ@bs$w-!Wl+-QP&Jji z497e+m*z8l8sgj&I6eX@Wp|$D)TfhF{8T0#E{Sv!2UXy)DrKuTyoO5yf!Gk*pv8A% zjUPI~{1Ch7U|g*<2Z&H&F}>)cqcH=jFg z9zFz*;VIl7J_CN5u-Aji{-1#Qr8=%jHB}s1a9VfXJNnRgZH9wO83h>YL{5sML&tPi|s4M6D z_HY{b(B3WwYz4g}rLXqX%wtBv>Xc(~tEYO&qWf7@h@ zwP}BP_SFCI^ulbv4&82S4ZSqYkdf-~Yym%y+wcpxi%{Jq@g5-4n$VK;MflD%YJA>Ec32Zw@yZ`*d)9fdhSeM6>jB5W;^MQF8%_*DM?n&H>5SHiCo zX7~-F*GLbsM`k8@Tq2{eP;2a+lcT#r3$4eaq-4~B ziY!`j0HqCfT6-#r%;&*#;Qyh%xmWpMTKXnGk$+v4Q{+68*0ILU`9qFUJYOiFiQ8}l zcM%doQEshfQQ9WuvsFCDi1R!^tgAG&3<51W>xl*cI~w1JF0ZnDp@T;hsS0Dg%H048 zaUbC*m!Q(sN%AxE?0B$j{otCb=dNiThVPnN;50WPLNNJSroeK|@HN+jKo#luzXG93 z#5=w3YCcrntALCef~~8WFX8xYKs9?L&n1v5U1)Xcj|>hsT&xTdNJZtE*JPx#vT84x zZdFt+|MPY5TZF4r-P>Jb#@gME4MjUcHGhv_no!lONFt%xeZzU&&4%b)^PrlKCOqzj zHk3r}05(mYpD2YUFXp>){f+DXt8ZvMm>b5e_LXUWrX`9>_0sF$_X#f|)Ck(?tuBRg z266lm!A!jp<2modx+h6{d=>Z8nOh6aHSkm+K83yBC#60l_E_Y(3FF+yt4z75|EKVW zu*CUC*p7&XKOCsMiBdcskwENuw&3Frv0*vf^zr*O^^m4E6qkzENB&}-6s#La$~O;G zT?5`>HaM%RaalZ_VV9R&Nh9=}jY)}(O)S2bl%D7M!$6~SFOPfrC=c=6wrWxidErm< z`iK3RwlrOZCbiDG#^$J3-&LaS!4!sVB_r+RnW-yV3u|o4NQ$o%@HO0q|AYHaz@HZI zXSj>_OZG?Au3C#~%`zWUm(nxJSXfpTDRXWE;XhE|spXWAP4*L(HI$mM zQBkfpyTmmUy!Ei;jPJ*(exdfywRx()H9lqTzk2TaYj3>aI;^W&WBcmw3V}+qLi(NP zfu?LtA}Ha&Z+$9w`00gdofu=cT6(e$IM;VcMnh0^IPaZ&eLyNYIQI5xd7H6Q9K>iIyXb33S1 z)oW}~Roo6*n5oM4?VyEPUl<9CFUwtd$y`9`uK^`CYj4Lt2s{|}=qA?)2OZifJPS=B zB6lamahzcW?kpXjrZYg?XA=TKI-%-eD-zOi<+eC?H@c+5%18Q- zv}xru?VFP(8_Fm@qZ=o{`etaA9vEdh?qN(V?A@V<`I4>7j_GOkgFJ3lW5}HTXfs<6 z7>O0RQ1kLp%S}dhQ3U4rqa|!V~ zq05>@C=VI+UTf?_~wA_|x=|4P}I5jhJBBz4>iA$H;-Xp+uKcmrW) zgUjGkMOMOu8P`6Ek*+;ayG`R5KEaz`IM zouG^2Z)Kf7VzMc0ieL1{qiVA!)x^mv|6;VcR2^8wonP+a)?K_d-dw65 zS>>AeH?S(%6vyeYDx>@6cuToC(8~!$ukHuOn+6D@rhPu<_%-800d(WrKCoEYmMn%n z$+kw_eI+4YD-hgDu*FS=@yyTeR_vcIn?I zdEDI@t^ZR=t{nDGOl4HJJ!#!Q+hN2fTg1IN@om+db5~CMt!kd=9xlJ6)k%J;m{msg zOEHqGbKmuybG>?@ z%)R$#oXqsPk{Q>Y!@Gg*mDi{D8!6vX7YymfeZQvI0 zLo478=tbO#zla~ke>S)qzO%)T!mIM!1MUYJRvLFU5EpDk^SuzuK~CX_kfE2}5HZ@` z(V1|qLVPQ&4lW|>dqB6qS;OP z=Rh2l&e0Nc3A_NVhW6EjtuZ^tzNGArMqY}mgc{HSqtushWlD7yO;2$=n-O}(x{XQC z>PFo2mAZ!hH}{MFW%#~WZIo8~dg)^%)7SWCo}8q!rgUN6t<%ZAt$(RZ6U6U^2cUOi z540jC@f$>!doP$SKxxl_Ss+=cSxlC5vt#(u2bM;&`yQ7=U#W@JK$n4TO>AM^o7h5a zmY4|hhGq(*jOLV;a#zY1uaLWibGEw2Z{vxf@{xu;JfjABdi?2@tyFVps9VF8&w z2vFjosfTbq2EGRF1E~3Mv0g%oPLkHAhS%@9XS7;6_RL`rq;VVJylW5S5eRAM2gcNG>NF}6RWRz4wqEOrwBy@c9dtnzp=~7YD7#r=PLEb$m#J6Phc1Ee*6Z}<$e;Vbj5n6vev#|HIoT9xZw(i zbt?&49alo`CTI)mE@%rivF-`Q(LTl~BRK_PZazejo&hY?)@nkK@;ItcXs0wmxl;DX zKlbI=yCn7{)~8HV38!n3uMD_A^Mq~S)UCm;`A{P&eZ{MJUfWMv41KN=@W98D<}mI* zAo-VpI&)|e6<#LWk(xKX=B@yh>?vaZ#}527{*~}EgmvQSEWYfIo&l1G28o(frf?rk zGn2LHc#Zo7GDRXcsUN1sDRHHSE)%}eEMj5Z8`?sxS<`*3%^51pzED0<#B0J#b&i!h zOd@tH)pp~?h+QwweVlrI4$xc9*YvUkjyvyuhe<#udWRDqZA}ta_Z*FbsLAeg9%yzSJ*1qDv?T zcs3~>Iwx;Q(yzD(nbLetOQ3o3`ZBn*u~_e;7|ns$y)>YniF=XqJrcWwOE2~3NU!9} zA8wQ?;=7cGpW>k%%cFJpxsakaJC#!t`#sf?AEFEgLpeOVGI}><|1wx9FUnf}U014% zM6c~n7z_00=u%&=>wJN8@YGG-^=j;%uBK(IlXnBYyPEY_Q>aFabVFHvN9Ok0*uw1G z#Pn4Ec#Rh~=4<0OGH6>Hj!d}`!@6>I%#&-qe1*7ka++78GJ0>)M*GgZ+W1{_(TX>39~}&x2n8p8%iiz%Szex8hTz(TPt(>%=cXI}?7H@NW1O z=)z}!%Ku-$uXW(p@fYzM_|FDP=d=GxXl&|=i?vHS4GlxFfuG;FDH21&s)LAV3S%Nr zr3H(u6TF-iR!btTq}|j-zaMm)#w@J6cC%2cz@9K&zJs)kvM;B!r5;{JyUx86&h4E! zhga-cVYWlh4vuRxtsu)}!s)>KmbPp+W|T6v4W{h%LK9gR9cyyuIDIz}Q}cP6>9+vd zqep9*dlIRb$S>i30j#OABCRG~w^RtyBQbV-sD?(ma_(!K3$J&0xr409xEk(uzX{$x zcTRg)LKtzMbncO7LipPD@CMUjuaIG7K_PdSB8S$qu&7Z>QF#$gX~GYf+dVdRx2A&vNy)r6WftxxAmBi0@pO z8k4 z0@|5ylJHKv0IeHdgx-lSLVG>@4|rBW7?a4#x)J-wAiu5rT8GFLsEE=R*2HbADC2J1 ztp6{v)`3mLszQ2o5Y#~fe7NDFVY)mQmD@M%Vw9P!%`ePO^#`|Tze7SRA!GuN)x!?h zGy;eg_Q+VS)m!BY<`m(73`*Q_xfIG0Xn@0%jWvH5_DhuUV3I^ePe@f~(MCUtKdN&P zqKOV6t>L8ML%{3&@|OJ;H8F1UhZ z+8#M4alDZ|+4@E!=>Bo|q_f8doO^Z=X6GrQkF# z9uF4asN7tty5>gKnoHbjDYHsMFI#=|ws5E*#ETQ9jk(qw+@a2L zce?731z(>Be<7oUSbs@f{|>;mPV0)Ms1&1GrF?bg`m2#Y_edkUnKFOzNZoyDpi$AX z`-@3j{*LII`yET@ZFSG1HXq8VBIfC5j{I`QtCsal(q0#3s@(sIlDyi1zsBDU;``eI zR11wz6H?wAiYv1T<w^&iuF{FBY7^aKZwgM@hiPgEv+xand~ zOD3q6Y6$aT+3=ofua`Pvcdwnf+L~*>%o|6+`|B_+hujyzf_u|z~AJ<;F!H9*+i8@A#HD3H3fnecS3(k3BLeJs!iS`fTKzOz;mMVID^oUyK`-lgVP` zi(}Ybb-$D>ag9R7Nht$Yt9=97%5578j8@ZF?lC zJmDA1UCXdin>D#PLb@xZ9n!0RW53C3i9T?!nv|M#`WtzkJ5VhjuKP%GL*C_0^&?Q} zBXucb=6HTJWk|K;8aSU4+TP8{8Pxc}oNtca%+Z)O%9Tl;NMT-i^$YompVzX-vU_71 zGmbiTg2Q$lxOWBHGFR$?GPJWn1-_NgLs${3@b{hps|gd2gpxclu;S$1%=)>O)>!BMi#Vsh_WoL=Y;adqC2rJ@^_|F!K_Z??IKVd~& zf&WYp@0-s8SHaT_()U`%p0S7yoZh)p?{XU17S`JG@)4Txuv93f zGCnao|8UsCh`J8QgvCO3qVk>iwSR02%N7|HW^0GSBEy2HN-$I5I~2BO5@|ksbwZT3 zq_b@Ag}%EpEX&ryx@2oXt!#BpwF{WsW@e}7r`?W>s#S>$a?5Gmiu2>}90F}&S#d0^ zOK~iyRh*w7k>$>WvO0eV`bRs%vP>+jOC}c7%H$~$ISkbN)-%wQ6Wp$}J~hYJf?8qe zy?zsh_*!nhJ47-vy8K}AC*{#%RVH+=k$m@NqE3%Du7KgsSSb|o=lK63gO7ly)_)T*5h6bb}*}hwpkxfV@Jh57KLD*st zvBj85wn&pujn5(nNgL71RA^1bU!fuNlt97i8F|dKt=rp(@L+C&vkI62*J|(j7FL+;j28)MoQYN48g^-wXiDC zJfE)$G6&MJYU}G#X#_>bd_O590M_swFNtKGOG5L>J?Ol<6TBgzJPAut+=I}2@MDKp z0*~T9*l^J%@7)q^4oqXMP*kwIEV#o-;}4LT;RkVd;+@dW1}e0SGO*f>%uLVap73Zm zV?r%T@NdKW-QevV_#XTQg~|PW;2m9{d^@4W)j+@sCVfD_xj|0Z8R()|S@S2?MYcU5 zLXomBr>yi?KnW`IHYJ*UUdO_^KNhf1D|UL}Q|iqq`+|ZO#flw7tgNh%*5%Vvdh?}9 z3btSdNpF+VWV<4c1E2w1@#Lva-4-9#@!$tj9bl}U?IPCfbWc7ksc3rl-x z56k$KjHCEXg%t`QvYA^+N{D!FE6FW^gLsc(A5VPrBMnXwsoG-V+@kZRT@g|oOXKxy zm0FM)4Zp>{vLmOo3qh4!DP;+wom+{ht&8SvxPCO{EU71#L61v<9@>;N!aD=Qo&yxm zYSsJb?nV+0?czk`5Z@oJ=@m%k^S75zTbfh9q;OWlP!lIu9ifFq|Bw_bh^s zANjMeKvF4<|J$)uOBtotF|`CbKlj2i3OMd=`$mT+OhR}Zfkbs3E&J%jMnfN;%HIW+ z;L0Z5nQ6YC*qRyinaS28j_XHTj9eJWzO5uRGfdt^+=t(AKW^bYKxLF1Z5_bp8Tax` z1q$0PU^1TJBswEm)8r3M#V1i1FJJdCGe+{$05OD@9tH`}TcaLZV)zKIzQp-wLxfhp zS8^R95XsC=lAKbqWUAcDV698&RxY|MLF;+v2chYUD8q@mRobN{9;{HjZb>MG?aW}|JrKP zZK$?B^CY=-v`{&3qI|%H?rNpxNc?nZt88#5WAMPT49KY}%faY{ zwKw5Mp6;G1DkI^%GxEGt&+|LGZlI%G87pcX zo&DYI8;lp{-wnTKAbi&( zaE531N#~ve3_`sOql*HnbIO zl9#|imqZP0)D4SIkDAeK6x2+@lhlUu{6RzYBCZX`fVDq%yM)-Bu zeiP_VlV}-K!3IRplO$+xGy%duKWgz1G_3N^pVm)JJTx(V;52b+wD_Ijk&&HT z7|XsAs!V=^+|C3QNJf@*8~gr`^#Q&Gt`Fav`7FFIf>P|xRli&UO`zsIo+RNWf|{47 zV^M+z(MeF|}&Ar6`? zI@ZL)lPPXbpw%KYW8`0q2P#QPPqBRT66I4qJRfl{lh(&br_X<5zont>71}^QGA-F! ztw+bkYWlkSIVpp?X<=^mCXV!OnjB{}Y%QFAJlo>f$(3!$i*fp|Cb3+*6+WGA1Fz#d z=;6}^%3Cu29Q85$H{6E*j@$5g+(mo=ztB7j9Dzr8F2gbGZa5CT6VF2v#D4<3kU@P! z^)+>|b768)Y6_95TH!m#TBFK5$QM^@(abS17f z72Vp1Waq;9zKGT|;pn{ZDdH;#dke7AW=HGcQ^Xdgu`UzB@=p<4DAf&SkGm54I$)H2 zIYm{mYIF{>RKw+yLiQF?NRpqgkl3GsxKvu*kN@dJJ)9vBIn=905sEfSN)1NS)fzo@ zAAJqlFnALvL)Y`_1Y09=y-!#(@Xv0VX?zd;C^#N9OT>4Kto&_iZEa%0YFS$O+RvJA zes*-Mwxj(YNAMMA{gVn~nOorfKK`izW5>kU@vU?l2M_Q5HUEw-&WMI+(ReX(4sT}@7> zf9LhjVg5@H$EDR8Pe&DrmQYKkx-+p>=L_)8{QnV+oYm`VY>M+rU4kU@@>u>v%a+f_{tCx+j|JD~> ztu3<8F?S#IYBV#i;Sp$wl7^BxlGa2^wBj((zR}i27Ds#*ubVAC_0ftPnCZ#!o%uM; zzUXelmVYHOy0^7yO0*= ze1DJE@DI3!uNUx-xVzz>p?AZ-Krf=3aJ~soC%y$OQms1>;}@)UW!#BB-w@^p=6u_w}H3p_6U{~uO@>>n+ex1(=n|2-tH8~;{H zFXx+CNAl+a{rdr>Z~!y_+9OQbAI1F)$fpEb_G)m3eK&!jFr>fb~Nh8p+b7n?zHGl<=wq8n6IgGy_VXbqah3|hAt&cSWaCe)y{pQa%}M{7k~ zjK7FW@E4(nS_VBZ6!bYsL#mll4W;iToC!mO3wIT88}9dHxEp)d3YdWY08rx>@jm>W zs6#XC$Ng9V%eX(X0-k{Wi3~rB{h0zjiTiT}{3`C}GJGEU`3%Rfe>=mMuuo<9J#4L6 z{|LKB{Q|55z2KS-T!4QBd>`16VGLUs&rrh_<_g%2`$3?W^)+bId=~#7c7fK@2CXU$TH$J$C*^-8XrU%WB$&$kPWlnvU^pI^^Af}? z^&%5q1CxFcQMOEa@FE~vpQp9T1^knXejJo1M-jx}HG@wk|i^W$#P?{Fl_7aT+ zTHUjML2+#sr$E-7w@z|4Wp$FXiOtB8x=j87sJG%N3M8DF8PT%LjvcPY-Sdo7=#>JEexG8k#TA8z=Dz-+`^CH7(>V;RGXc)6O@sWP;4(?y4a7P# za7p=}_%24dfmOeTZ=^|j#+Au>hGt%@YGD4Cx!UL)X3%+7+i2&IVC`J9m&mE&btdrw z8c(Puxla$aaAvzMGMQZ8r0wH-@YzxPqqipTZYiSf&G(c-F?2HQk7$w_whH9zYAoP@i?(8P;su{dv2KQQl8)4kO0e6Vs1<(3^i3j8vk4o^q ZD{!s//', null, + function(x, st) { + var tb = document.getElementById('binding_entries'); + + if (st && tb) { + /* clear all rows */ + while( tb.rows.length > 1 ) + tb.deleteRow(1); + + for( var i = 0; i < st.length; i++ ) { + var tr = tb.insertRow(-1); + tr.className = 'cbi-section-table-row cbi-rowstyle-' + ((i % 2) + 1); + + tr.insertCell(-1).innerHTML = i + 1; + tr.insertCell(-1).innerHTML = st[i].type_; + tr.insertCell(-1).innerHTML = st[i].foe_entry; + tr.insertCell(-1).innerHTML = st[i].src_info; + tr.insertCell(-1).innerHTML = st[i].new_info; + } + + if( tb.rows.length == 1 ) { + var tr = tb.insertRow(-1); + tr.className = 'cbi-section-table-row'; + + var td = tr.insertCell(-1); + td.colSpan = 5; + td.innerHTML = '
<%:There are no active traffic.%>
'; + } + } + } + ); +//]]> + +

+


Warning: + Web UI (espcially auto-refresh) will decrease the performance significantly. You'd better close Web UI before any performance/throughout test.

+
+ +
+ <%:Active Binding Status%> + + + + + + + + + + + +
<%:No.%><%:Type%><%:FOE Entry%><%:Traffic before NAT%><%:Traffic after NAT%>

<%:Collecting data...%>
+
+ + diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_hwnat.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_hwnat.htm new file mode 100755 index 000000000000..2ca8cfb0eb94 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_hwnat.htm @@ -0,0 +1,86 @@ + +<% + local mtkwifi = require("mtkwifi") + local nat = false + + if string.match(mtkwifi.read_pipe("lsmod | grep hw_nat"), "hw_nat") then + nat = true + end +%> +<%+header%> + + + + + + +
+ Hardware NAT Acceleration + +
+ <% if nat then %> +

+ '"> + <% else %> + '"> + <% end %> +

+


warning: + Web UI (espcially auto-refresh) will decrease the performance significantly. You'd better disable Web UI before any performance/throughout test.

+
+ +<% if nat then %> +

+<% end %>
+
+    
+
+<%+footer%>
diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_ipsec_view.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_ipsec_view.htm
new file mode 100755
index 000000000000..8273135ee768
--- /dev/null
+++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_ipsec_view.htm
@@ -0,0 +1,85 @@
+
+<%
+        local disp = require "luci.dispatcher"
+%>
+<%
+        local section_name
+        local cur = require "luci.model.uci".cursor()
+        cur:foreach("ipsec", "remote", function(s) section_name = s['.name'] end)
+%>
+
+
+
+
+
+
+
+
+ +
+
+ diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_web_console.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_web_console.htm new file mode 100755 index 000000000000..5723abf20623 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_web_console.htm @@ -0,0 +1,94 @@ +<%+header%> + + + + +

Web Console

+
+
+ Execute shell commands or scripts as root. Be Careful. +

Press Enter to execute. Press Shift+Enter to start a new line.

+

+

+ + + + + + + + + + + +

+

+    
+
+ + + +<%+footer%> + diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_apcli.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_apcli.htm new file mode 100755 index 000000000000..97bcc6ed830e --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_apcli.htm @@ -0,0 +1,1716 @@ + +<%+header%> + +<% +local disp = require "luci.dispatcher" +-- local request = disp.context.path +local request = disp.context.request +local mtkwifi = require("mtkwifi") +--local devname = string.match(request[5], "(mt.+)%.") +local devname = request[5] +local devs = mtkwifi.get_all_devs() +local dev = {} +local vif = {} +local vifidx +for _,v in ipairs(devs) do + if v.devname == devname then + dev = v + end +end + +local vifname = request[6] or dev.apcli.vifname +assert(vifname) +vif = dev and dev.vifs[vifname] or nil +vifidx = vif and vif.vifidx or nil +--print(devs, dev, dev.apcli, devname, vifname) + +local cfgs = mtkwifi.load_profile(dev.profile) +local map_cfgs +local first_card_cfgs +local appliedMapModeDiff +if pcall(require, "map_helper") then + map_cfgs = mtkwifi.load_profile(mtkwifi.__read_easymesh_profile_path()) + first_card_cfgs = mtkwifi.load_profile(mtkwifi.detect_first_card()) + local appliedMapDiffTable = mtkwifi.diff_profile(mtkwifi.detect_first_card()) + appliedMapModeDiff = appliedMapDiffTable["MapMode"] and appliedMapDiffTable["MapMode"][2] or nil +end +%> + + + + + + + + +
+
" enctype="multipart/form-data" onreset="return cbi_validate_reset(this);" onsubmit="return validate_all() && cbi_validate_form(this, 'Some fields are invalid, cannot save values!')" autocomplete="off"> +
+ ApCli Configurations - <%=vifname and devname.."@"..vifname or devname%> + <%local diff = mtkwifi.diff_profile(dev.profile)%> + <%if next(diff) ~= nil then%> + ( '">Click here to apply changes) + <%end%> + + + + + + + + + + + + +
+ + + Available Wireless Networks + + +

+ +

+
+ +
+
+
+ Connection Configurations + + + + + + + style="display:none;" <% end %> > + + + + + + + + + + + + + + + + + + + + + + style="display:none;"<% end %>> + + + + + + style="display:none;"<% end %>> + + + + + + style="display:none;" <% end %>> + + + + + + + + + + + style="display:none" <% end %> > + + + + + + style="display: none;"<% end %>> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ApClient Mode + checked="checked"<% end %> onclick="toggle_apcli(true)"/> Enable + checked="checked"<% end %> onclick="toggle_apcli(false)"/> Disable +
MAC Repeater Mode + checked="checked"<% end %>/> Enable + checked="checked"<% end %>/> Disable +
Root AP SSID + "/> +
Root AP Channel + + This will overwrite channel of AP!
Root AP Authentication Mode + +
Root AP Encryption + +
Root AP WPA Key + "/> +
ApCli MFPC + + checked="checked" + <% end %> + <% if cfgs.ApCliAuthMode == "WPA3PSK" then %> + disabled="disabled" + <% end %> + type="checkbox"> +
ApCli MFPR + + checked="checked" + <% end %> + <% if cfgs.ApCliAuthMode == "WPA3PSK" then %> + disabled="disabled" + <% end %> + type="checkbox"> +
ApCli MFPSHA256 + + checked="checked" + <% end %> + <% if cfgs.ApCliAuthMode == "WPA3PSK" then %> + disabled="disabled" + <% end %> + type="checkbox"> +
Root AP Encryption + +
WEP Default Key + +
Root AP WEP Key 1 + " maxlength="26"/> +
WEP Key 1 Type + +
Root AP WEP Key 2 + " maxlength="26"/> +
WEP Key 2 Type + +
Root AP WEP Key 3 + " maxlength="26"/> +
WEP Key 3 Type + +
Root AP WEP Key 4 + " maxlength="26"/> +
WEP Key 4 Type + +
+
+ + +
+ + + +
+
+
+
+ + + + + +<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_apply_reboot.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_apply_reboot.htm new file mode 100755 index 000000000000..bf50265f3894 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_apply_reboot.htm @@ -0,0 +1,67 @@ +<%# + File name : mtk_wifi_apply_reboot.htm + This file is used in WebUI based on LuCI to handle the reboot event. +%> +<%+header%> +

Reboot Device

+
+

+ As the driver does not support addition or deletion of interfaces on the fly, + the settings which were changed during addition or deletion of interfaces have not been applied yet! +

+ + The changed settings will be applied only after reboot of the device. + Please click on the Reboot button. + +

+ Tip:
+ Add or delete as many interfaces as required before reboot so that you do not have to reboot the device again.
+ Please follow below instructions to add or delete an interface;
+ 1. Go to Wireless Overview web-page.
+ 2. Click on Add button to add a new interface or click on Remove button to delete an existing interface.
+ 3. If you are are adding a new interface, then, click on Save button after filling out all the required fields such as SSID etc.
+ 4. Once you are done with addition/deletion of interfaces, then please click on Reload button or + Save and Apply button on any web-page which will redirect to this web-page to perform the reboot of the device.
+

+ +
+

+ + +<%+footer%> \ No newline at end of file diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_chip_cfg.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_chip_cfg.htm new file mode 100755 index 000000000000..70ccc64c3c3e --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_chip_cfg.htm @@ -0,0 +1,600 @@ +<%+header%> + + +<% +local disp = require "luci.dispatcher" +-- local request = disp.context.path +local request = disp.context.request +local mtkwifi = require("mtkwifi") +local devname = request[5] +local devs = mtkwifi.get_all_devs() +local dev = {} +for _,v in ipairs(devs) do + if v.devname == devname then + dev = v + end +end +local cfgs = mtkwifi.load_profile(dev.profile) +local map_cfgs +local first_card_cfgs +local appliedMapModeDiff +if pcall(require, "map_helper") then + map_cfgs = mtkwifi.load_profile(mtkwifi.__read_easymesh_profile_path()) + first_card_cfgs = mtkwifi.load_profile(mtkwifi.detect_first_card()) + local appliedMapDiffTable = mtkwifi.diff_profile(mtkwifi.detect_first_card()) + appliedMapModeDiff = appliedMapDiffTable["MapMode"] and appliedMapDiffTable["MapMode"][2] or nil +end +%> + + + + + +
" enctype="multipart/form-data" onreset="return cbi_validate_reset(this)" onsubmit="return cbi_validate_form(this, 'Some fields are invalid, cannot save values!') && ValidateAllSettings()" autocomplete="false"> +
+ Chip Configurations - <%=string.split(devname,".")[1]%> + <%local diff = mtkwifi.diff_profile(dev.profile)%> + <%if next(diff) ~= nil then%> + ( '">Click here to apply changes) + <%end%> + + +
    +
  • + Basic +
  • + <% if cfgs["VOW_Airtime_Fairness_En"] then %> +
  • + VoW +
  • + <% end %> +
+ + + + + + + + + + + + + + + + + + + + + + <% if cfgs.WHNAT then %> + + + + + + <% end %> + <% if cfgs.E2pAccessMode then %> + + + + + + <% end %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Decline BA Request + checked="checked"<% end %>/> Enable + checked="checked"<% end %>/> Disable +
Reverse Direction Grant (RDG) + checked="checked"<% end %>/> Enable + checked="checked"<% end %>/> Disable +
BA Win size + <% if string.split(cfgs.WirelessMode,";")[1] == "16" or string.split(cfgs.WirelessMode,";")[1] == "17" or string.split(cfgs.WirelessMode,";")[1] == "18" then %> (range 1-256) <% else %> (range 1-64) <% end %> +
HT Disallow TKIP + checked="checked"<% end %>/> Enable + checked="checked"<% end %>/> Disable +
Wi-Fi HW NAT + + Supported by MT7615
E2pAccessMode + +
Beacon Interval + tu(range 20-999, default 100) +
Data Beacon Rate (DTIM) + Beacon interval(range 1-255, default 1) +
BG Protection Mode + +
Short Preamble + checked="checked"<% end %>/> Enable + checked="checked"<% end %>/> Disable +
TX Burst + checked="checked"<% end %>/> Enable + checked="checked"<% end %>/> Disable +
Packet Aggregate + checked="checked"<% end %>/> Enable + checked="checked"<% end %>/> Disable +
Short Slot + checked="checked"<% end %>/> Enable + checked="checked"<% end %>/> Disable +
+ + <% if cfgs["VOW_Airtime_Fairness_En"] then %> + + <% end %> + +
+ + + + + +
+ + + + +<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_dev_cfg.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_dev_cfg.htm new file mode 100755 index 000000000000..23e3a05b7fd3 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_dev_cfg.htm @@ -0,0 +1,1380 @@ + +<%+header%> + + +<% +local disp = require "luci.dispatcher" +-- local request = disp.context.path +local request = disp.context.request +local mtkwifi = require("mtkwifi") +local devname = request[5] +local devs = mtkwifi.get_all_devs() +local dev = {} +for _,v in ipairs(devs) do + if v.devname == devname then + dev = v + end +end +local cfgs = mtkwifi.load_profile(dev.profile) +local bands = mtkwifi.detect_triband() +local map_cfgs +local first_card_cfgs +local appliedMapModeDiff +if pcall(require, "map_helper") then + map_cfgs = mtkwifi.load_profile(mtkwifi.__read_easymesh_profile_path()) + first_card_cfgs = mtkwifi.load_profile(mtkwifi.detect_first_card()) + local appliedMapDiffTable = mtkwifi.diff_profile(mtkwifi.detect_first_card()) + appliedMapModeDiff = appliedMapDiffTable["MapMode"] and appliedMapDiffTable["MapMode"][2] or nil +end +%> + + + + + +
" enctype="multipart/form-data" onreset="return cbi_validate_reset(this)" onsubmit="return cbi_validate_form(this, 'Some fields are invalid, cannot save values!') && ValidateAllSettings()" autocomplete="false"> +
+ Device Configuration - <%=devname%> + <%local diff = mtkwifi.diff_profile(dev.profile)%> + <%if next(diff) ~= nil then%> + ( '">Click here to apply changes) + <%end%> + + + + + + + + + + + + + + + + + + + + + + + + + + <% if string.split(cfgs.WirelessMode,";")[1] == "16" or string.split(cfgs.WirelessMode,";")[1] == "17" or string.split(cfgs.WirelessMode,";")[1] == "18" then %> + + + + + + + + + + + + + + + + <% end %> + +
Channel + + <% if cfgs.ApCliEnable == "1" then %> APClient/Repeater Mode. <% end %>
BSS color + +
Co-located BSSID set max index + +
TWT Support + +
+ + + + + <% if string.split(cfgs.WirelessMode,";")[1] == "16" or string.split(cfgs.WirelessMode,";")[1] == "17" or string.split(cfgs.WirelessMode,";")[1] == "18" then %> + + + + + + + + + + + + + + + + <% end %> + "> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <% if mtkwifi.band(string.split(cfgs.WirelessMode,";")[1]) == "5G" then %> + + + + + + + + + + + <% end %> + + + + + + + + + + + <% if cfgs.MUTxRxEnable then %> + + + + + + <% end %> + <% if string.split(cfgs.WirelessMode,";")[1] == "16" or string.split(cfgs.WirelessMode,";")[1] == "17" or string.split(cfgs.WirelessMode,";")[1] == "18" then %> + + + + + + <% end %> + + + <% if dev.isPowerBoostSupported then%> + + + + + + + style="display:none" <% end %>> + + + + + + + + + + + + + + <% end %> +
+
+ + + + + +
+
+ + +
" enctype="multipart/form-data" onreset="return cbi_validate_reset(this)" onsubmit="return cbi_validate_form(this, 'Some fields are invalid, cannot save values!')" autocomplete="off"> +
+ Raw Configurations ( Edit WiFi profile directly ) +

WARNING : DO NOT MESS WITH IT IF YOU DON'T UNDERSTAND IT!

+ +
+
+ '" type="button"> + + '"> +
+
+ + + + +<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_loading.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_loading.htm new file mode 100755 index 000000000000..9a459f2e1020 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_loading.htm @@ -0,0 +1,90 @@ +<%# + File name : mtk_wifi_loading.htm + This file is used in WebUI based on LuCI to handle the loading event. +%> +<%+header%> +<% +local disp = require "luci.dispatcher" +local request = disp.context.request +local url = "/"..table.concat(request,'/',5) +%> +

Applying Settings

+
+ + Please wait while the settings are being applied. +
+ + + +<%+footer%> \ No newline at end of file diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_ap_capabilities.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_ap_capabilities.htm new file mode 100755 index 000000000000..db8fc513eb6c --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_ap_capabilities.htm @@ -0,0 +1,257 @@ +<%# + File name : mtk_wifi_map_ap_capabilities.htm + This file is used in WebUI based on LuCI to display EasyMesh AP capabilities. +%> +<%+header%> + + + +

EasyMesh AP Capabilities

+
+ + +<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_bh_link_metrics.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_bh_link_metrics.htm new file mode 100755 index 000000000000..6af669c69d57 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_bh_link_metrics.htm @@ -0,0 +1,220 @@ +<%# + File name : mtk_wifi_map_bh_link_metrics.htm + This file is used in WebUI based on LuCI + to display the feature of EasyMesh Configurations + named as Back-haul Link Metrics at Controller. +%> +<%+header%> + + + +

EasyMesh Back-haul Link Metrics at Controller

+ + + + + +<%+footer%> \ No newline at end of file diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_bss_cfg_renew.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_bss_cfg_renew.htm new file mode 100755 index 000000000000..def34932fe7a --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_bss_cfg_renew.htm @@ -0,0 +1,1156 @@ +<%# + File name : mtk_wifi_map_bss_cfg_renew.htm + This file is used in WebUI based on LuCI + to configure BSS in EasyMesh Network. +%> +<%+header%> + + + +<% + local mtkwifi = require("mtkwifi") + local cfgs = mtkwifi.load_easymesh_bss_cfgs() + local cfg_1905d = mtkwifi.load_profile("/etc/map/1905d.cfg") +%> + +
+ EasyMesh BSS Configuration + <%local diff = mtkwifi.diff_profile(mtkwifi.__easymesh_bss_cfgs_path())%> + <%if next(diff) ~= nil then%> + + ( '">Click here to apply EasyMesh BSS changes) + + <% end %> + +
+ + + + + +
+

+ +

+
" enctype="multipart/form-data" onsubmit="return validate_all()" autocomplete="off" style="display:none"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AL-MAC + +
Radio Band + +
+
+ +
+ +
+ + + + + +
+ + + +<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_bssinfo.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_bssinfo.htm new file mode 100755 index 000000000000..9743e9894336 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_bssinfo.htm @@ -0,0 +1,363 @@ +<%# + File name : mtk_wifi_map_bssinfo.htm + This file is used in WebUI based on LuCI + to display the feature of EasyMesh Configurations + named as Front-haul status per BSS. +%> +<%+header%> + + + +

EasyMesh Front-haul Status per BSS

+
+ + Retrieving EasyMesh Front-haul Status per BSS Information! +
+ +
+ + +<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_channel_planning_score.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_channel_planning_score.htm new file mode 100755 index 000000000000..98ca3391dee2 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_channel_planning_score.htm @@ -0,0 +1,407 @@ +<%# + File name : mtk_wifi_map_channel_planning_score.htm + This file is used in WebUI based on LuCI to display Channel Scan Result. +%> +<%+header%> + + + + + +

MAP R2 Channel Planning Score

+ +
+ + Retrieving MAP R2 Channel Planning Score! +
+ + + + + + +<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_channel_scan_result.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_channel_scan_result.htm new file mode 100755 index 000000000000..e1e9b934763c --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_channel_scan_result.htm @@ -0,0 +1,510 @@ +<%# + File name : mtk_wifi_map_channel_scan_result.htm + This file is used in WebUI based on LuCI to display Channel Scan Result. +%> +<%+header%> + + + + + + + +

MAP R2 Channel Scan Result

+ +
+ + Retrieving MAP R2 Channel Scan Result! +
+ + + + + + +<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_client_capabilities.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_client_capabilities.htm new file mode 100755 index 000000000000..c8ee08775855 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_client_capabilities.htm @@ -0,0 +1,146 @@ +<%# + File name : mtk_wifi_map_client_capabilities.htm + This file is used in WebUI based on LuCI to display EasyMesh client capabilities. +%> +<%+header%> + + + +

EasyMesh Client Capabilities

+
+ + +<%+footer%> \ No newline at end of file diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_data_element.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_data_element.htm new file mode 100755 index 000000000000..a7bf8caef199 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_data_element.htm @@ -0,0 +1,216 @@ +<%# + File name : mtk_wifi_map_data_element.htm + This file is used in WebUI based on LuCI to display Data Element. +%> +<%+header%> + + + +<% + local disp = require "luci.dispatcher" + local path = disp.context.path + local request = disp.context.request + local getAlMac = request[#request] +%> + +

MAP R2 Data Element

+ +
+ + Retrieving MAP R2 Data Element +
+ +
+ + +<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_display_bootstrapping_uri.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_display_bootstrapping_uri.htm new file mode 100755 index 000000000000..5f65b8403045 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_display_bootstrapping_uri.htm @@ -0,0 +1,36 @@ +<%# + File name : local mtkwifi = require("mtkwifi").htm + This file is used in WebUI to show MAP Bootstrapping URIs. +%> +<%+header%> + + + + + +<% + local mtkwifi = require("mtkwifi") + local dpp_cfg = mtkwifi.load_profile("/etc/dpp_cfg.txt") +%> + +

MAP Bootstrapping URIs

+ + + <% for k,v in pairs(dpp_cfg) do + if string.find(k, "agt_qr_code") then %> + + + + + <% end %> + <% end %> +
<%=k%><%=v%>
+ + +<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_runtime_topology.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_runtime_topology.htm new file mode 100755 index 000000000000..34bbec77ca1d --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_runtime_topology.htm @@ -0,0 +1,465 @@ +<%# + File name : mtk_wifi_map_runtime_topology.htm + This file is used in WebUI based on LuCI to display EasyMesh Run-time Topology. +%> +<%+header%> + +<% + local mtkwifi = require("mtkwifi") + local mapcfgs = mtkwifi.load_profile("/etc/map/1905d.cfg") +%> + + + + +

+ EasyMesh Run-time Topology Display + <% if mapcfgs.map_ver ~= "R1" then %> +

Click on the Easymesh device to display its Data Element Statistics

+ <% end %> +

+ +
+ + Retrieving EasyMesh Run-time Topology Information! +
+ + + + + + +<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_multi_ap.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_multi_ap.htm new file mode 100755 index 000000000000..05bc762a6372 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_multi_ap.htm @@ -0,0 +1,2522 @@ +<%+header%> + + + + + + + +<% local mtkwifi = require("mtkwifi") %> +<% if not mtkwifi then %> +
+ mtkwifi lua module is not available! +
+<% else %> + <% + local devs = mtkwifi.get_all_devs() + local l1dat, l1 = mtkwifi.__get_l1dat() + local bands = mtkwifi.detect_triband() + local cfg_1905d = mtkwifi.load_profile("/etc/map/1905d.cfg") + local driver_cfgs + local allDevDiff = {} + local map_cfgs = mtkwifi.load_profile(mtkwifi.__read_easymesh_profile_path()) + local first_card_cfgs = mtkwifi.load_profile(mtkwifi.detect_first_card()) + local appliedMapDiffTable = mtkwifi.diff_profile(mtkwifi.detect_first_card()) + local appliedMapModeDiff = appliedMapDiffTable["MapMode"] and appliedMapDiffTable["MapMode"][2] or nil + local isMapSupported = true + local mapd_default_cfg = mtkwifi.load_profile("/etc/map/mapd_default.cfg") + local mapd_user_cfg = mtkwifi.load_profile("/etc/map/mapd_user.cfg") + local eth_mode = mapd_default_cfg.mode + if mapd_user_cfg.mode then + eth_mode = mapd_user_cfg.mode + end + local dpp_cfg = mtkwifi.load_profile("/etc/dpp_cfg.txt") + %> + + <% if not l1dat or not l1 then %> +
+ l1profile.dat file is not available! +
+ <% else %> + <% + for idx,dev in ipairs(devs) do + local zone = l1.l1_path_to_zone(dev.profile) + local diff = mtkwifi.diff_profile(dev.profile) + if next(diff) ~= nil then + allDevDiff[dev.devname] = diff + end + if not dev.isMultiAPSupported then + isMapSupported = false + end + if zone == "dev1" then + driver_cfgs = mtkwifi.load_profile(dev.profile) + end + end + %> + + <% if not isMapSupported then %> +
+ EasyMesh feature is not supported for this platform! +
+ <% else %> + + <% if not driver_cfgs or not map_cfgs then %> +
+ Profile settings file is not available! +
+ <% else %> + +
+
+ EasyMesh Configurations + + +
+
    +
  • + Basic +
  • + <% if not appliedMapModeDiff and first_card_cfgs.MapMode == "1" then %> + + + <% if cfg_1905d.map_ver ~= "R1" and cfg_1905d.map_ver ~= "R2" then %> + + <% end %> + <% end %> +
+ +
" enctype="multipart/form-data" onsubmit="return validate_all()" autocomplete="off"> + + + + + + + <% if bands == 3 and first_card_cfgs.MapMode == "1" then %> + + + + + <% end %> + <% if first_card_cfgs.MapMode == "1" then %> + <% if mapd_default_cfg.mode then %> + + + + + + + + + + + <% end %> + + + + + + + + + + <% if appliedMapModeDiff and appliedMapModeDiff ~= "1" and first_card_cfgs.MapMode == "1" then %> + + + + + + + + <% end %> + <% if appliedMapModeDiff and appliedMapModeDiff == "1" and first_card_cfgs.MapMode ~= "1" then %> + + + + + + + + <% end %> + <% if not appliedMapModeDiff and first_card_cfgs.MapMode ~= "1" then %> + + + + + + + + <% end %> + <% end %> + <% if not appliedMapModeDiff and first_card_cfgs.MapMode == "1" then %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <% if cfg_1905d.map_ver ~= "R1" and cfg_1905d.map_ver ~= "R2" then %> + + + + + + <% end %> + + + + + + + + + + + + + + + <% if bands == 3 then %> + + + + + + + + + + + + + + + + + <% else %> + + + + + + + + + + + + + <% end %> + + + <% end %> +
EasyMesh Mode + +
Reset EasyMesh Settings to default + +
+ + EasyMesh has not been enabled yet!
+ Please click on Save and Apply button to enable the EasyMesh. +
+
Reset EasyMesh Settings to default + +
+ + EasyMesh has not been disabled yet!
+ Please click on Save and Apply button to disable the EasyMesh. +
+
Reset EasyMesh Settings to default + +
+ + Other EasyMesh related settings will be displayed only after enabling EasyMesh!
+ Please check Enable radio button of EasyMesh and then click on Save and Apply button to enable EasyMesh. +
+
+ + Other EasyMesh related settings will be displayed once Device Role is configured. + +
+ <% if not appliedMapModeDiff and first_card_cfgs.MapMode == "1" then %> + + + + + + + + + + + + + + <% if cfg_1905d.map_ver ~= "R1" then %> + + + + + + + + + <% end %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <% if cfg_1905d.map_ver ~= "R1" then %> + + + + + + + + + <% end %> + <% if bands == 3 then %> + + + + + + + + + + + + + + + + + + + + + <% else %> + + + + + + + + + + + + + + + + + <% end %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <% end %> + <% if not appliedMapModeDiff and first_card_cfgs.MapMode == "1" then %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <% end %> + + <% if cfg_1905d.map_ver ~= "R1" and cfg_1905d.map_ver ~= "R2" and not appliedMapModeDiff and first_card_cfgs.MapMode == "1" then %> + + <% end %> + +
+ + + +
+
+
+ + + <% end %> + <% end %> + <% end %> +<% end %> +<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_overview.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_overview.htm new file mode 100755 index 000000000000..edbeb1c13b07 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_overview.htm @@ -0,0 +1,557 @@ +<%+header%> + + + + +<% +local mtkwifi = require("mtkwifi") +local devs = mtkwifi.get_all_devs() +local l1dat, l1 = mtkwifi.__get_l1dat() +local dridx = l1.DEV_RINDEX +local main_ifname +local map_cfgs +local first_card_cfgs +local appliedMapModeDiff +local chipname +if pcall(require, "map_helper") then + map_cfgs = mtkwifi.load_profile(mtkwifi.__read_easymesh_profile_path()) + first_card_cfgs = mtkwifi.load_profile(mtkwifi.detect_first_card()) + local appliedMapDiffTable = mtkwifi.diff_profile(mtkwifi.detect_first_card()) + appliedMapModeDiff = appliedMapDiffTable["MapMode"] and appliedMapDiffTable["MapMode"][2] or nil +end +%> + + + +

Wireless Overview

+ + <% if #devs == 0 then %> +
+ No wireless device found! +
+ <% end %> + + <% for _,dev in ipairs(devs) do %> + <% main_ifname = l1dat and l1dat[dridx][dev.devname].main_ifname or dbdc_prefix[mainidx][subidx].."0" %> + <% if mtkwifi.exists("/sys/class/net/"..main_ifname) then %> +
+ + + + <% if chipname ~= string.split(dev.devname,".")[1].."."..(dev.mainidx) then %> + <% chipname = string.split(dev.devname,".")[1].."."..(dev.mainidx) %> + + + + + + <% end %> + + + + + + + <% if dev.vifs then%> + + <% for _,vif in ipairs(dev.vifs) do %> + + + + + + + <% end %> + + + <% if dev.apcli then %> + + + + + + + <% end %> + + <% end %> + +
+ + + <%=string.split(dev.devname,".")[1]%> + <%if not dev.vifs then%> + * FATAL ERROR: Incorrect Profile Settings + <%end%> +
+ Driver version: <%=dev.version%> +
+
"> + ','<%=luci.dispatcher.build_url("admin", "mtk", "wifi", "chip_cfg_view", dev.devname)%>')"> +
+
" style="display:none"> + Processing request. +
+
+ <%=dev.devname%> + <%local diff = mtkwifi.diff_profile(dev.profile)%> + <%if next(diff) ~= nil then%> + * need reload to apply changes + <%end%> +
+ Work mode: <% if dev.ApCliEnable == "1" then %> APCli <% else %> AP <% end %> +
+
+ <%if not dev.vifs then%> + ')"> + <%else%> + ')"> + ')"> + + <%end%> +
+ +
+ <% if vif.state == "up" then %> + + <% else %> + + <% end %> + + Interface: <%=vif.vifname%> | + Type: AP | + SSID: + + <% if vif.__ssid == "" then %> + Error: value not present in dat file + <% else %> + <%=vif.__ssid and vif.__ssid:gsub(" "," ") or nil%> <% end %> + | + Channel: + <%=vif.__channel or dev.Channel%> +
+ <% if vif.state == "up" then %> + BSSID: <%=vif.__bssid%> | Mode: <%=dev.WirelessModeList[tonumber(vif.__wirelessmode)]%> + <% else %> + Wireless is disabled or not associated + <% end %> +
+
+ <% if not vif.state then %> + + <% elseif vif.state == "up" then %> + ')"> + <% else %> + ')"> + <% end %> + ')"> + ')"> +
+ +
+ <% if dev.apcli.state == "up" then %> + + <% else %> + + <% end %> + Interface: <%=dev.apcli.devname%> | Type: STA | Status: <% if dev.ApCliEnable ~= "1" then %> Disconnected <% end %> +
+
style="display:none" <% end %>> + <%:Loading%>  Loading connection information of <%=dev.apcli.devname%> +
+ + style="display:none" <% end %>>Wireless is disabled or not associated +
+
+ <% if dev.ApCliEnable ~= "1" then %> + <% if dev.apcli.state == "up" then %> + ')"> + <% else %> + ')"> + <% end %> + ')"> + ')"> + <% else %> + ')"> + ')"> + ')"> + ')"> + ')"> + <% end %> +
+ +
+
+ <% end %> + <% end %> + + + + <%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_vif_cfg.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_vif_cfg.htm new file mode 100755 index 000000000000..026d5f729c20 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_vif_cfg.htm @@ -0,0 +1,2790 @@ + +<%+header%> + +<% +local disp = require "luci.dispatcher" +local path = disp.context.path +local request = disp.context.request +local mtkwifi = require("mtkwifi") +local devs = mtkwifi.get_all_devs() +local devname +local vifname, vifidx +local dev = {} +local vif = {} +if request[4] == "vif_add_view" then + devname, vifname = request[5], request[6] + dev = devs and devs[devname] + vifname = vifname..#dev.vifs + vifidx = #dev.vifs + 1 + +elseif request[4] == "vif_cfg_view" then + devname, vifname = request[5], request[6] + dev = devs and devs[devname] or nil + vif = dev and dev.vifs[vifname] or nil + vifidx = vif and vif.vifidx or nil +end + +local cfgs = mtkwifi.load_profile(dev.profile) +local diff = mtkwifi.diff_profile(dev.profile) +local WscValue = mtkwifi.token_get(cfgs["WscConfMode"], vifidx, "0") or "0" +local appliedWscValue = diff["WscConfMode"] and mtkwifi.token_get(diff["WscConfMode"][2], vifidx) or nil + +local map_cfgs +local first_card_cfgs = mtkwifi.load_profile(mtkwifi.detect_first_card()) +local appliedMapModeDiff +if pcall(require, "map_helper") then + map_cfgs = mtkwifi.load_profile(mtkwifi.__read_easymesh_profile_path()) + local appliedMapDiffTable = mtkwifi.diff_profile(mtkwifi.detect_first_card()) + appliedMapModeDiff = appliedMapDiffTable["MapMode"] and appliedMapDiffTable["MapEnable"][2] or nil +end + +local AuthModes = {} +local EncryptionTypeLists = {} +if string.split(cfgs.WirelessMode,";")[1] == "18" then + AuthModes = (WscValue == "0") and dev.AuthModeList_6G or dev.WpsEnableAuthModeList_6G + EncryptionTypeLists = dev.EncryptionTypeList_6G +else + AuthModes = (WscValue == "0") and dev.AuthModeList or dev.WpsEnableAuthModeList + EncryptionTypeLists = dev.EncryptionTypeList +end +%> + + + + + +
" enctype="multipart/form-data" onsubmit="return validate_all('<%=vifidx%>','<%=cfgs["HT_DisallowTKIP"]%>') && chk_WPS_ACL('<%=tostring(mtkwifi.__any_wsc_enabled(WscValue)) %>')" autocomplete="off"> +<% if not dev or not vif then%> +
+ Interface Not Exist - <%=vifname and devname.."@"..vifname or devname%> + +
+<% else %> + + <% if mtkwifi.band(vif.__wirelessmode or string.split(cfgs.WirelessMode,";")[1]) == "5G" or mtkwifi.band(vif.__wirelessmode or string.split(cfgs.WirelessMode,";")[1]) == "6G" then %> + + <% else %> + + <% end %> + +
+ Interface Configurations - <%=vifname and devname.."@"..vifname or devname%> + <%if next(diff) ~= nil then%> + ( '">Click here to apply changes) + <%end%> + + + +
    +
  • + );this.blur(); ">Basic +
  • + <% if string.split(cfgs.WirelessMode,";")[1] == "16" or string.split(cfgs.WirelessMode,";")[1] == "17" or string.split(cfgs.WirelessMode,";")[1] == "18" then %> +
  • + );this.blur(); ">HE_MU +
  • + <% end %> +
  • + );this.blur(); ">WPS +
  • + <% if map_cfgs then %> + <% if (not dev.wdsBand or dev.wdsBand == dev.dbdcBandName) and first_card_cfgs.MapMode ~= "1" then %> +
  • + WDS +
  • + <% end %> + <% else %> + <% if (not dev.wdsBand or dev.wdsBand == dev.dbdcBandName) then %> +
  • + WDS +
  • + <% end %> + <% end %> + <% if map_cfgs then %> + <% if request[4] == "vif_cfg_view" and cfgs.MapMode == "0" then%> +
  • + );this.blur(); ">Stations +
  • + <% end %> + <% else %> + <% if request[4] == "vif_cfg_view" then%> +
  • + );this.blur(); ">Stations +
  • + <% end %> + <% end %> +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <% if dev.DBDC_MODE == "0" then %> + + + + + + <% end %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
SSID + " name="<%="SSID"..vifidx%>"> +
Channel + +
Auth Mode + +
Hidden + + checked="checked" + <% end %> type="checkbox"> +
AP Isolation + + checked="checked" + <% end %> type="checkbox"> +
WMM Capable + + checked="checked" + <% end %> type="checkbox"> +
TX Rate + +
STBC + checked="checked"<% end %>/> Enable + checked="checked"<% end %>/> Disable +
HT LDPC + checked="checked"<% end %>/> Enable + checked="checked"<% end %>/> Disable +
VHT STBC + checked="checked"<% end %>/> Enable + checked="checked"<% end %>/> Disable +
VHT LDPC + checked="checked"<% end %>/> Enable + checked="checked"<% end %>/> Disable +
Mode + +
DLS Capable + checked="checked"<% end %>/> Enable + checked="checked"<% end %>/> Disable +
APSD Capable + checked="checked"<% end %>/> Enable + checked="checked"<% end %>/> Disable +
Fragment Threshold + (range 256-2346, default 2346) +
RTS Threshold + (range 1-2347, default 2347) +
VHT Short GI + +
VHT BW Signaling + checked="checked"<% end %>/> Enable + checked="checked"<% end %>/> Disable + checked="checked"<% end %>/> Dynamic +
HT Protection + checked="checked"<% end %>/> Enable + checked="checked"<% end %>/> Disable +
HT Guard Interval + +
Operating Mode + +
A-MSDU + checked="checked"<% end %>/> Enable + checked="checked"<% end %>/> Disable +
Auto Block ACK + checked="checked"<% end %>/> Enable + checked="checked"<% end %>/> Disable +
IGMP Snooping + checked="checked"<% end %>/> Enable + checked="checked"<% end %>/> Disable +
+ + <% if string.split(cfgs.WirelessMode,";")[1] == "16" or string.split(cfgs.WirelessMode,";")[1] == "17" or string.split(cfgs.WirelessMode,";")[1] == "18" then %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + <% end %> + + + + + + + + + + + + <%if tostring(mtkwifi.__any_wsc_enabled(WscValue)) == "1" then%> + <% if not appliedWscValue or (WscValue == appliedWscValue) then%> + + + + + + + + + + + + + + + + + + + + + + + + + + + + <% if map_cfgs then %> + <% if cfgs.MapMode == "0" then %> + + + + + + + + + + + + + + + <% end %> + <% else %> + + + + + + + + + + + + + + + <% end %> + + + + + + + + + + + + + + + + + + + + + + + + + + <% else %> + + + + <% end %> + <% end %> + + + + <% if not dev.wdsBand or dev.wdsBand == dev.dbdcBandName then %> + + + + + + + + style="display:none" <% end %>> + + + + + style="display:none" <% end %>> + + + + + style="display:none" <% end %>> + + + + + style="display:none" <% end %>> + + + + style="display:none" <% end %>> + + + + + style="display:none" <% end %>> + + + + style="display:none" <% end %>> + + + + + style="display:none" <% end %>> + + + + style="display:none" <% end %>> + + + + + + style="display:none" <% end %>> + + <% _wdsMac=cfgs.WdsList and cfgs.WdsList:match("^([%x:]+)") %> + + + + style="display:none" <% end %>> + + <% _wdsMac=cfgs.WdsList and cfgs.WdsList:match("^[%x:]+;([%x:]+)") %> + + + + style="display:none" <% end %>> + + <% _wdsMac=cfgs.WdsList and cfgs.WdsList:match("^[%x:]+;[%x:]+;([%x:]+)") %> + + + + style="display:none" <% end %>> + + <% _wdsMac=cfgs.WdsList and cfgs.WdsList:match("^[%x:]+;[%x:]+;[%x:]+;([%x:]+)") %> + + + + + + <% end %> + + <% if request[4] == "vif_cfg_view" then%> + + + + + + + + + + + + + + + + <% end %> + + <% if map_cfgs then %> +
+ Access Control - <%=vifname and devname.."@"..vifname or devname%> + + + + + +
Access Policy + disabled="disabled" <% end %> <% if cfgs["AccessPolicy"..(vifidx-1)] == "0" then %> checked="checked"<% end %>/> Disable +
+ disabled="disabled" <% end %> <% if cfgs["AccessPolicy"..(vifidx-1)] == "1" then %> checked="checked"<% end %>/> White List +
+ disabled="disabled" <% end %> <% if cfgs["AccessPolicy"..(vifidx-1)] == "2" then %> checked="checked"<% end %>/> Black List +
+ <% if first_card_cfgs.MapMode == "1" then %> + To set Black List see MAP application note when EasyMesh is enabled. + <% end %> +
+
+# 1. one MAC one line.
+# 2. empty lines will be ignored.
+# 3. lines start with "#" will be ignored.
+# 4. invalid MAC will be ignored.
+
+11:22:33:44:55:66
+AA:BB:CC:DD:EE:FF
+11:22:33:aa:bb:cc
+            
+ +
+ <% else %> +
+ Access Control - <%=vifname and devname.."@"..vifname or devname%> + + + + + +
Access Policy + checked="checked"<% end %>/> Disable +
+ checked="checked"<% end %>/> White List +
+ checked="checked"<% end %>/> Black List +
+
+# 1. one MAC one line.
+# 2. empty lines will be ignored.
+# 3. lines start with "#" will be ignored.
+# 4. invalid MAC will be ignored.
+
+11:22:33:44:55:66
+AA:BB:CC:DD:EE:FF
+11:22:33:aa:bb:cc
+            
+ +
+ <% end %> + +
+ + + +
+<% end %> + + + +<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/csrftoken.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/csrftoken.htm new file mode 100644 index 000000000000..57ac03f3bfa5 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/csrftoken.htm @@ -0,0 +1,24 @@ +<%# + Copyright 2015 Jo-Philipp Wich + Licensed to the public under the Apache License 2.0. +-%> + +<%+header%> + +

<%:Form token mismatch%>

+
+ +

<%:The submitted security token is invalid or already expired!%>

+ +

<%: + In order to prevent unauthorized access to the system, your request has + been blocked. Click "Continue »" below to return to the previous page. +%>

+ +
+ +

+ Continue » +

+ +<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/empty_node_placeholder.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/empty_node_placeholder.htm new file mode 100644 index 000000000000..b7e276b9609d --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/empty_node_placeholder.htm @@ -0,0 +1,11 @@ +<%# + Copyright 2010 Jo-Philipp Wich + Copyright 2018 Daniel F. Dickinson + Licensed to the public under the Apache License 2.0. +-%> + +<%+header%> + +

Component not present.

+ +<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/error404.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/error404.htm new file mode 100644 index 000000000000..ff151d1834c9 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/error404.htm @@ -0,0 +1,12 @@ +<%# + Copyright 2008 Steven Barth + Copyright 2008 Jo-Philipp Wich + Licensed to the public under the Apache License 2.0. +-%> + +<%+header%> +

404 <%:Not Found%>

+

<%:Sorry, the object you requested was not found.%>

+

<%=message%>

+<%:Unable to dispatch%>: <%=url(unpack(luci.dispatcher.context.request))%> +<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/error500.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/error500.htm new file mode 100644 index 000000000000..34a52cda84f7 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/error500.htm @@ -0,0 +1,11 @@ +<%# + Copyright 2008 Steven Barth + Copyright 2008 Jo-Philipp Wich + Licensed to the public under the Apache License 2.0. +-%> + +<%+header%> +

500 <%:Internal Server Error%>

+

<%:Sorry, the server encountered an unexpected error.%>

+
<%=message%>
+<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/footer.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/footer.htm new file mode 100644 index 000000000000..ba14ec8678d7 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/footer.htm @@ -0,0 +1,27 @@ +<%# + Copyright 2008 Steven Barth + Copyright 2008-2019 Jo-Philipp Wich + Licensed to the public under the Apache License 2.0. +-%> + +<% + local is_rollback_pending, rollback_time_remaining, rollback_session, rollback_token = luci.model.uci:rollback_pending() + + if is_rollback_pending or trigger_apply or trigger_revert then +%> + +<% + end + + include("themes/" .. theme .. "/footer") +%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/header.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/header.htm new file mode 100644 index 000000000000..b9ac4958d45a --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/header.htm @@ -0,0 +1,38 @@ +<%# + Copyright 2008 Steven Barth + Copyright 2008-2019 Jo-Philipp Wich + Licensed to the public under the Apache License 2.0. +-%> + +<% + if not luci.dispatcher.context.template_header_sent then + include("themes/" .. theme .. "/header") + luci.dispatcher.context.template_header_sent = true + end + + local applyconf = luci.config and luci.config.apply +%> + + + + diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/indexer.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/indexer.htm new file mode 100644 index 000000000000..28fc3debc3f9 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/indexer.htm @@ -0,0 +1,7 @@ +<%# + Copyright 2008 Steven Barth + Copyright 2008 Jo-Philipp Wich + Licensed to the public under the Apache License 2.0. +-%> + +<% include("themes/" .. theme .. "/indexer") %> \ No newline at end of file diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/sysauth.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/sysauth.htm new file mode 100644 index 000000000000..acd5ff7e38f9 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/sysauth.htm @@ -0,0 +1,75 @@ +<%# + Copyright 2008 Steven Barth + Copyright 2008-2012 Jo-Philipp Wich + Licensed to the public under the Apache License 2.0. +-%> + +<%+header%> + +
+ <%- if fuser then %> +
+

<%:Invalid username and/or password! Please try again.%>

+
+ <% end -%> + +
+

<%:Authorization Required%>

+
+ <%:Please enter your username and password.%> +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+ + +
+
+ + +<% +local uci = require "luci.model.uci".cursor() +local fs = require "nixio.fs" +local https_key = uci:get("uhttpd", "main", "key") +local https_port = uci:get("uhttpd", "main", "listen_https") +if type(https_port) == "table" then + https_port = https_port[1] +end + +if https_port and fs.access(https_key) then + https_port = https_port:match("(%d+)$") +%> + + + +<% end %> + +<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/view.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/view.htm new file mode 100644 index 000000000000..b451e8cfbf92 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/view.htm @@ -0,0 +1,12 @@ +<%+header%> + +
+
<%:Loading view…%>
+ +
+ +<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/xml.lua b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/xml.lua new file mode 100644 index 000000000000..30b37210bd83 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/xml.lua @@ -0,0 +1,26 @@ +-- Copyright 2008 Steven Barth +-- Licensed to the public under the Apache License 2.0. + +local tparser = require "luci.template.parser" +local string = require "string" + +local tostring = tostring + +module "luci.xml" + +-- +-- String and data manipulation routines +-- + +function pcdata(value) + return value and tparser.pcdata(tostring(value)) +end + +function striptags(value) + return value and tparser.striptags(tostring(value)) +end + + +-- also register functions above in the central string class for convenience +string.pcdata = pcdata +string.striptags = striptags diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/xml.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/xml.luac new file mode 100644 index 0000000000000000000000000000000000000000..caa0e16f62349a8ca190d2be8ce2a861d10beba0 GIT binary patch literal 864 zcmbtSOG*Pl5PcOp8r`U<5kGi@oM8eg1hR3h2u(7Cz{HG`9&tGmy@iLFO~@r&<{-XG zx??b#park1y1J<9_qs3IxxIs!nyFzI3yWR{Nh8Uv->R!rPq|i!W&~UokQ>Ht1@OT5 zqXZ@pNK^0?L1sMjCeuPNi^3K%Ed>+iS|!F4#;vj#U!g#aCA`DJviLdgXEDr)Pb7J# z-*E9DO{|L>sm(@l2D9T&M=t9>^9jF&K99g#ALrydP=)^hi5h>nzOPO3&2U z-Ut<+QMXSV5*1lg-ZQR8Qe4yDqQpCyl#D+Cp)z=+S9ZeO^16N$Q_yJ#8$f4jk8MKj z#U^i+MGdBSK~U6NPnkw5?{3H-L`)Cky?CW(E2s1N5<#c7Tc& mpbw|x15|}Gk8}EfEylm4)LfkipUHnNeG5&mDXn#Sp!N;-PG;`_ literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/lucihttp.so b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/lucihttp.so new file mode 100644 index 0000000000000000000000000000000000000000..8eb74e8121fbb335e69119fe55b167ec85321fc7 GIT binary patch literal 12291 zcmeHNeQ;FO6~DVLK=_cRd?*Tq4Wgw;A$DX2FzRNrOQ1$bLujdvv)ODGvSssOvZ=u^ zV_NeEb!;a)1VjbOOqohjr)9=C#TIG&sN=LwCj&I46`24!lU7G#A(E}c?eE-s&+g45 zFEY0EpXO$6?z_Kp?z!ilbKd=UdmjxHuW`Fvf|DhFEvU9KMA>aOEQN2@CAV_}rgle{D*Dl-svVaDsw%k5R&1i= z-FPL58d#tJd$~jNamn3&3+<@CYfaj0qEz?iG`=~y11`1N;uduDb>tby(~z%2rZgX! z^tg#CNV%#=|DS`W*~rt8ry$cX=OL5%wTZ=7@O(2eB@Z&0@TP*F2L5N>UHsyYKbrpD$q&hVj>$rgi&;s7%c5TMq{uXSE2`u}dn$9lKXAYw zbihA^;L+qCcHqAQ{Q6ZKq0;>hawzUbn8JTFRL$;(B6zUr$yL1OH~;cRgLp`DO-Y*m9bX zkr)@jXiYROf?H6CU@+ViqQ_7y`bY%T%1C`=YqTLAsSj?c4^>CNyrmZSXlSf4R2;0W zi^m$`_0gKGB32cwii98`7>Wa_XpBb$c!G6}4OKPKSo%55uiMu&R#!yosd;04tTGa= ztwcLniPRVsP=Ym)htp!XSM&h5u_01l8H$H8VlaS+QebN&z9kxoRm!Ffk$80^4$g`g zcSDi^2OD66U@Te<^Q4uLgm5Sps|ba+NdfWgb(wK}KNM?}wPKiP3pJ~*3)RP!qFhmm za1Hc^NU)-|v8FOqzdh6BY7LYq!c7H=gW;-3cw3`wv{G!^I2E|oGh!GH?=M~FtU$%nkTf=Y!A-7X4j3>xYozZhq@j72&@Ed45O2X^mken$a8@RCc zs=ueugqP2pXcS9~Tl`jw;eDT_U#NYWaX{}28RvcJkPUxV@9Tu#4>ErbZ5oiWZTNed zKgWij)*GG0Hk{?>+3=-W{z@Cp^1U{^Rme&PMn8_Q^>#r_O_H}{D> z2A-Z{!FSrg&3$B_fzv*ZQ@?@pUXf@62F_;$!iNl;_MPdpJfr87G>Et|aN0X_$}w=- z$8(xv;Jmjdn#aJ+P3U3+=Y28p=NUMgnDCVbZtj)625#ajSaP#jvhAoj8ZhrZ2w-xMd5u(TC@veH=EmjOSAMf~q5J!lf z+gg;oI8`JsTA~wJ7x4+E8s_)o?Nd;a67M9&`UhS&Br?2qUA&`WKjPXH@Z4@q1`3)-|%`-a_|9n z$Hx~$;(~R3XSw^H-a+6N_^C|++VokoB)-BDU9D($9`thyyh-A3Z7E9jS+hG^pd(I< zFOdGkpS!n6j)(NVxUryP@Bv@PaPwR|2Jwd5ijw7MKbT4({98&dp(LHD zFVcZ@ZhZ|lgWfcT!I>gC2z^Qa3%YNzCD}r}BbKwyzW$=57jjQ(y)ChQKIz?O&FpOL zDoUO=+K#FFOtSgdx~p-|?99EQ#{H2V%fJ^gmeXJCSZJ&=j*lvzknJgk;JXw3bH%U+ zHfLW@ERjFR7i4$x#|k&c3GAigg!qZ>5~3})dsV`BM)}Tb#83ZR?=bs;__Mv`^IcZU z32NtiQ}I(w^a++fyEC^(`Gex>1Nees?|Rqv1Lw}Q&Y*VTcu~@gK7B*05)^|Z7cnMj zzGF&eFWxP}&mrC^)_vUyCt2qazl2G>dvX)uN)^jm4&EK;c1ZCrm?Iu?xkcFsTDzi2 z`C2DmmBMzpMY3gzc+mshcTW+?<0__R{A-MuqW6}3K|Y~(h5XTiagoo+7v!VdI%N~G z74=K+ObEPESN+c1F^Z4aH`GS*euj^BoAbet>XY=Kw(NH)bM5NHd)hbTQ+nsm!KUFp ziW$s3(7$^)C4FG`H;pOdZ_GQoAM*EGWBL2g>TC4(tWMuy)8DhF=)9vu@)~0zmGVuvakkwlOJ2IB{a8D zJkZ#?-QoG=Zf`Por+WVyvqTrqTZGH^Os~Jh-{KA*F!_mhA;lMXLny@cVw|A*p%2;z3^%_d_fr4AmRDU05#na1^Ka|55GVb_2g0wmSBrY)Ag2VL}J?^lkf!@)KU` zp57;c&ACYXgc~t;sWlU4fwt!FkpS%k z;p7Lh|EP9-g7tE+dpVEW!R~EL&)EH{aph!edfpm2E~U2`lZ=~Z^Aq;5ozrdPxE^yQ zxGu-#6fbY^xE%BzVtPh5vNh==<1zoPvE#9o=??ObG2KDFxcjPjkp24aPLK|y3;Er& zkKHc3Ceaz_a=5S~xy|44Nkaf@)a!DMvY#6_G{!x(TRhc~>RS3r%by|fiCTS?t`zHP z@bx!Z>ETbg9{w1tzgSi`#^O=>8Z3Mp{ND}w0!8cAi;{xUvf#S)>&nF1K!HC{8eCs+ zUx1&#T~NF!pjZrUe?eJ+0t4&)N)|l?HmnhW($Wp3_&gu2sf;v>JBU*A}g?+pY>M)1qF z>7fSYcjkJLG1FBD(ezjTt9~v}U#Ww^tzqGsYu&j+sNad#;sA&FR6K|BD9Rp`IIN3S z9PqH;5+(mkrRa0syo;&SW|W6;czpt8`^TwNH_CpL@1V@ToJwUQaQaZ@p=`xQWHXh? zRB9)c*wi0INhwDa!u3e0$ZDGJnmcWQ@Bo{G{M?7BR3*{<(>?pM4p{zO?hR+(J}Ezk z(Ko1kp@~Rd5?f+9_;a1ym{a|gM19UJ7JTn^_Z~My&2Lcu1zaL zZF0by1N#mbZL}_XtU|OQjf^%PwDE)YP56Swjeex9*6ecM$F?G#D)3wxgNJNLzFdk8 z$ss&LPTu~B2POn|Wwl$>r#1rdb%F2jXYi39&VX+bVuE64guLDEJ+6APY}yU-clT+C ziA&eW?}f;G@V|x_lfTgwOfTO*?Z8z3Q&~?VP^Pp`F5I2HXOi#9iF+p$KI7i!a{vA8 z+i#uu>)-xzTJ?(d<7wOi;}#gVz_&wG vK1nHk*iN77dX<)!ve9vTlKM~AbR6|$-MkaPvj3OmQ97WGr_XPJJlDSg6LYvg literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/map_helper.so b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/map_helper.so new file mode 100755 index 0000000000000000000000000000000000000000..476133b93f16e285bb65773792cbb288d8c734f3 GIT binary patch literal 33161 zcmeHw3wTu3wf~-(01*%(q99On5_1h z#YaYIZ-wlJ&AAzbZSV7*%FU@x|{)%1Kma zL?FE7$C6K#O6`@X$L*B#Zz{4RgC+H)Yaa_pMp;U;Ss~DtB%LqIc__b)-$4BO;+Kuz z2>j^iEedvyEa%8_G|DUS%f;_J{0#i`qd)@Zq8yLkMLJQQkCEkAl+)$;nX;TD%U&qS zW;!O|H<_P_|AwNt9KQ?jyI7zY`DZSEITEJanI+FJMA=`Sk3=~`o~J65`%=+NASq@P z!#?=w#{dAcCE^U7kmCF_i5rBnAAaA$ZlX~!^qjx>(>)ve zU3o$5d(qSLo?J8ej9XUC%KPoLHP%NTzPe-HM=S5RbWz69e+xbT;D_73dg;6kUo`X| zHu%!rRsVRe|H5ON-@Ebg4}UP{qrDaJPyc(@)ukoAO}Cfc3bkpE0KA(;$>}))>b2s?a*rThtH;tW}z?6!g z2J=+ydNz%mWk{q{{AZ<+voej|UTNeQY4lP*Oy!5S)A(U+8hc(#Bflh#JyX;0k4?k> zL>m0*Y4De)!M~Ws{&&*Yb36_IYWOErymq9K^ZPXRyp@K3OB(r$((w08ga2KcIPOU! ze|wtt-js&_&NS^>lg6H7Y4H8h$ay=BJx`{Q^I;k}N7C>=oW?(&V)z<>t6uXWISPP} zjs+6mCMO6l$ziS+Xag{-7*@gA_;rEK{v`R|UMTR2pQ;Ve#!r~dkB23H2VJ0Wd@SXE zg>$keOHL-rC4M50OCPrPTF%beq@8C-IT1PenIjdPEAhp2LBe6niNb3*C;1_o$lzEk zUmP#+s$Ksf<+qgxd|zn~&AU#+bz!r-E~xfCBl+K^3mlFwq+Vhp zd+O*Sh-0h7XG^`!R}1`LDgSAJ#NQz&aCMUZh{W%u$r}#Uu1lr-n4C0aNjcX@`HM>h z|4Y))dZjm4;Ny~?!b*A%(BuTiA>G@}csfQu4PI34S$CXThqPst5~KpkTpZ+3Hd{Ev>G)J`AutTpwOq)es5S2P^7J zmxsYzQ44%3Y<2C@qN;|{CDmb8-B?;%7p@5|FRcq!hO6ts^=xT4QWRcURTj2t1w*i| zv7xf8th%rX=8O7@}jziS(Ql@<~3GFs`BgR*A)|W$|lC-d{x~gVbeqBkpI$Rd1s;wbo zt;U*2)$;Iy+PYdaYz?6btE-^Du(YmpNmX@Kq$=D%*n-N!+L{_su_RI&X>4Gnb#>Ki zAdd{U8XBq=R0>pKWhpqT7e-M1`cl%QCz9<3a_1-z&E-Cjuo7BQR*R5AlGI)iuCEW5D;wa> zU>U2R1Ys3*jgc~j{HU+3XBCtTJQd~!%PPZV%c>iY35XNYDOgq?MlM8ndLgaKk%`F4 ziqf)huq;wv9pvJOkKeQP6}h%% ziAp|?;0B(2!6o5_NU*dH^{cCE%h>Ypax{Q7)YQQdkqU-}Rl{wby4(Tv;T4Uj%qkkf z;bp8M60WXh6>wTP{U7#LQ3jQVgXI+VGNCWL8e@S+I^u|NK(M7CmGNE?EMG##)s&T{ ztY1%^-cuQ_?9y{QENFYM9vvtcsjLq-RC*;4xv-MDQ#TCtyhnKiQ=6clJ31+sh)|J= zK)O%^aS$EOBd{W=>v@Qc=!B>l7X1LZEjpuzONFEiTF^U#=$h#{8mK3F$YeVz6DfVP zh>qTwmZ4!;Rb8-}MxE-aB_0JL)MFcJiMF(?EZBhau@eX?Bkb(CmlYHSCyteucyXa& z^1>&ff2VgPfUCG!d2)X((q~xkqB`OFQ>AcpV=Zm{m4%_CFS@`4KKR@$7gAH z+;Ta`U=2_AIO;Goe5OL8JXgc_*6`ysyuOw=Rm1ny_)QH@Yc=Yaqv880B+6n9FK=l$ zb+Lw5V*(LE8vZoSDgLX}@cR9IorYIyf>dL*hCf4-V{7;`HT-4`PwS!TXwvYs2C0s1 z8eXkQ60KRo>uaWuYIuEVu0_MsnvXj6YIs_gRYy$2%OR4J4rq8M` zq2Whsa?aK8@>ZQw$7^_bOV06AHT(sd98<%O)$nsP{5TC?tl{LHz64omq!xL=AuT zo7qO-59$Y6$7z1E6ZglQ#^IiTb2Yd(-~tWq2Y87F4*hwbYysY&!B+v^slnF(-mk&m1AJJ6mjLz$T>TY*b2a!nzy%sy19*uB*8|?5 z!7Bmp)Zps@@7Lg)03X)i4S@X_uKs@moU6gN0WQ$sI{`1z;JX2D(BK~c-l@U&0p72{ zI{+Wn;GKZ|JzV`i2Ar$Gy8#zy@RNX-Xz3pvUzHG%8bNPDee0v1n8H&#w>d5=*Ypoq03%)*zFXr<7Rp)zH z@c9H^liwKT$UElpwfN$K?;pUye@*^om+xJjFDCdtQGCX?9C>f(do8bG2 z;)}a{+jPEW!S{&bGYv=YojPBW;M=bFVlLlCov%gk-J|%-97o>uI^XSr?^eav?DEy= zd>w*so#JD;j=Tz;Z?E8MRD2jo*X2G{i@x@#|OXoW#_(~O@Io#1Z2RN+mTdMyb zU;C8k*V(MK9plO22G)6G73(~@mUX_rj&*);Gwb|t6YGo*wWqgd+tUvZx2GQ&Wlujk z#-9HEID7gB7uwT5oMKPMc+kS$p52Kj*sa+aY(;zkTR=JOiw&o-q>06cN7q1id<HQpcP43rqN;^SCn7_Vu>$C;!j zFMBInVfrCM!tIc|89E?)MejaBKQl%|6URxf*;yrNc0WlAjfj2;TBDMgf$l68cH+Kl#mji)(~hw>XBx)hjvMc8M;mb8F)-I)ryYjf zhrt^J-|tfJVd^>X@F0xUH<lx{uW?Rsj48W7zXLejI}W6|A?_`Ci8T;jQhN#Sm*w%bukq{K@wG4N*R6v& z$bGs#-9&a|^K03`e2H5_IOJXec}UkTpTrM^t=z`u5z%ABLwSn$!M@MdOZ$EC5x6#* zoo|CqygoLP!-oJvKEEd3DrhZ!vls6ZzUGn9<}+;T9~^I6n70o+Jk;!L&qh8#{$;=; zFIwZHc%Pb3>e01N$ZPW1=Wty=1FUq_2%4+Q92Je7X^O?sZ`T7z^ltXFKP_6Ewaz+?-C_P`g^`6 zhk50|-xzx>iYNKoM_PdL)o@9 z8a!UV9v8e#KIzv^z{=j;UODiqVc6DS$f5Rs#ObO1nvi*$BgcbU@RHwjzsmS?zrIIw z)QO`Xr}FFT!WL(Ycond+(-yQAf3xe?P_Au#%JI^#u$N@F0@s~iiO&1EaX$C!vw)SJ z3PE%AunVI0aNBwVc&}d{QF5eTAJXJ3@XCQ-%@MY>A9%8HE2ryz{h2c!d&k**LKgSo zom9V@e!oHTs@%K@uxi^lL2L0Fr?|O!j%{t{c-xwc+@!vA3*hd?*D4{W$!Cn^{tE+E zI?fa{S4U`U)Es478-d6A+CV;LXR-+o%IlZ9K1hEd_T#wBVojzG=UD4Z8k^?`Jx)#+ zY5aftZ}O4Zc?stsAOBL0!H>BC{eD=XO<~fNAeXTU+$H)0v6Jzijh2LGkKa|`j z=1JRW93O)7IBel#Mzf4dXk647V_Q==P4pG-H7--iP;t%xtYYvi+Mgs3*V7o}4F}IJ zC&WMeR9vPR9_ThevjgPMH zi#7*(>|O#M^yL-TVLpfX^MId{eAFR(NLKsJm^%XQ!R&)a1HRoyAeZKc@dwP#2C@M@ zi(i=EN;1Z%xp8Pb*U6ysY2two<97)=_dyTY;Iqd^Lu1kZLB9<;3t%(sK1h5w@;YYc zQ&b`j@i9XFT;v?lp8%fp#kKkFG~meoO=Lg#Sq5Ie!8|m+1-iFjK1i|<$M{3S4wb(w zHa>a*(IEF;DL2$t*vLX7qUT7tDQqRAxK zPs|BXHqo|(Kg`Y>q%QLmS63gY>ndskoiXV%vd1rZ@T@1PKShA&ewAZc9blDD zIf}+6^1fLGxE;3Ayskpy8-(+th5qOf_>bp_IWg+XqCCmiedwRbd4jcDdTiC<3kjLr zAE?Xy0J+7QTp!MlKp&ThKEmt8CPvvLT>n8k(YY_>9Q^TF2>zh+3sG0)KgS_ADW6r0 zISzWi3mo<9<7@xWWiIEv20n>)dar?3^7_??Uyf6oN0;?oM92Je?rKKi>k)wV`~hwR4lwSn#O+Bkspc;+0sp1(pChvnG*DPX1N z0k0m6?V&u|Iu$bUY=G{4TC*{>(^#d;d`HP-Vr>7TCi6D0O!LC1aj|WE2|QhIbvZ99 zIWkB8LzA=GD<^(oG!8i*08iK3y`1h|Zxex2UOT>)xz~eiANn?n`b_)aVT@B4ORRfQ zhq1d48m#%Rak>}BJ#G&A>Ozt^6#PT;c|OsYM0V0xFk0$)5v4v4YU8p*thZBr=rbs0 zrj`@aZR>eX6LP(N+#+SD9M}X{#bzqbljI=>*rm4hW8fPoHW-Hku$_;|usfce-#Ht! z_&7i2p9Qow(IsxRpmYDrTv!HJ=^KXgBz?#QV}@-}JQwm@fJ}@%4`O|Q%ZQ8nz-yFD z8N;hInY|P(bP*qC<^yggTQMHY)%cEg!fwRkDB^|pRCkknf9xV2I~r?#20H0h*QCi# z9tpX5kVUapW6dN@_6MkY1Ui%2Mq^FBZIyrz&-Mm#z3>^=Yff_XKH$+i*wHK6KGdUk zkf!$l>4nTBz4oN2QD9qSAH|FOLpewGi}A?Zg*=YZI&|WfJ&@17UB?S?y~X@6@f3cK z;g@P&v+gv^ZwBCZI)0ejti#-99p*OcFt=HkiJvUIh?P<`40ogX&1qv00e^1AhbQ zmO9!!Iu0qBQb)6bjmccc{eYE@c{oqfVS{J3ZM^_|8Fc9W+3(1i!sXls_-G&yJpwz( zZ+`6>oA@$0?-!H3;N^LacCJNTWyASOuCzCzVCt9PtpT^ap_+qu&o?jE3(=9|`zJ^Zc-<$T?W%?o}S&xA~Lv<8#n?9X2(ZLu+%0?FQgZ z*q?(>(*MIw(*MRu`hUbp`v2UM^#A!M=l?+-|JQl^e~&H`-y*uakMw+ z4E>;|jMIF`#vd=f){Z(eDvhc7*gAq}G#iLI*_4d}#|S zg74s$81k0NzS3tl@|%2jKKV=fG7s{z2f~MVc0*+!sxuh*4|t+(KfA>7<$17ef=)AB zS}tf9Xx(<3E^R1iNihhyw1J={#lUuHr-GIggJzf36SSll#9Uh9@9AUkC1`pKRznWI zMlX}s=(v)TvHKJ7lE2jad6rM~KkEDHy6_drqq@|;5H~s}UtjQ7Jm)&m>-Ah3&oP(5 zH4pjFrq5+w@z^j@+Mwn#b6~%yGtJ4Nw@^ool@zZNwxih0gWS)P5Uz z1MVw&G)XZKC|bx}lpRY`W~18M_N%x1Oi> z%l5wyyo!+@*R6KcC%@bU{9@vPP1(?~T1HstY}tK@1xn6Qk|Xa~L$qFq^HEe6 z_c>RiG%&9^zBYds?z{Q@xBfhx)&vtJY>jgt*B|Sd(r&sZ{uapr58Bb;kMsLX*oQU2 zrM7i3r-^&z_2>iCRv){RuZQ%JGSvD{FTmVh$lnWlljLFj$EdU|%2%@0qS*aXu6fXU z7`1N-o;^-+pFQfjI)uD7^^E6Zz)IJ4uP*b_s8MBGWZxvxLo(fUK*#<+E17Z~@GVW| z<}Na^eg~NpPlIG$Xto~3nvH&~fIWO|IUhXwI4}tOdRu-5y1w4i3Rtz}I-)_gl&} zbWe_$-X(ceyte>Wc25(u4!@F*c!!qT)^6a*&TGhLH_LeAK5iKB-e-Ecu85H5#Jdi# z(v>4%O(Nbv^KSpL}3q6W`v% zku_JzlIw30D6WB#EJL#Q zaiRP;opg15CgAv_m*iFX5df@WwnzBHm5=H18-(esJ&uUrTI z3t*+^KCd3+MsvirG9fd?GXY2DAth7h#v7WFZOG4 z8oYAwOu$%aTkXJ8pMC~qa-V*|eI9`K&MQAHZRhVwUC8AKT&cv3C7dVL zuK}lGx`XJDjpyL}*?}|0%RYa%I& zXj}Vmtj9sew?XSJwkw59r{C8DR{DkunyU}7jjge*DDd6Iwp7WKvHh+lv!7QcVryS- zTQ>mjjqQ9TN5=MYP0m-J%kfqD0|J0DPsF8`OGePywn0zDR0v@uZ0P&%*qX+5myicW%#iuD9vcH|G>G-)< z2V!F1WLxKhhsT88iPNvWGOwh+a4i4pcH_tg|$k`@Av{ zYvVS&rwJYzYe$Y>$&s=C8g-Q)o|1A@tp5R6kM-vo@57QNW8Efwhy3(SILveCdRvOVM{~085YBTQO#nyZq~DZr42_GLci7e^0?+g0 z*Ho|T_0{pu^O9HP$#Z~}f0hcGD<64c+-X~X0G`^I^7;x|itDSRZ-$I*|*_Tk%dNcv4)~9hp6qOqmOQP3ALRnaG9E_iZZ&Jlv{)Z<&Ouwa*zwg4{WPHFCw~nqc+$QAnScSPv^gYPM^@;y22*2*i`N-9hSH)@}VCBP}g4X5_ z$y`9JLfdUC0zBDA>$mD%wglPH^}f}~i5W_d>~EK7dOk*7-p7?5#LC`*brbMRqd8gf zyKIik(MqO_)j68Xx4beDEAs(dH^Ae>zpwM2l_T>EAros9Vh(niCUbwXOw8Z?QEX;l zQ9Qlz4+uFDQUa(!b{&Sj;E7u~O zJ|_L;j=Z-!Dbh25^`-W-y ze&^MP9E3xc%~-bopfaOQ8MND-b<6YSIYF+etsWE_O+9(m`fiAO||t& zoO|A#Y~k-t%J;NuKquYm+3#fMUQ)>Yi0WeMwR_Psbi0-Z^|#JiKtkK0xT z>~Y?mMEl4$etLIO>Mi!@9W3-ZZGXk1_kgB%4e1p!+x#jg@a|;n3B3PH^$;)e59J)$ zFY@^nxh{0F`Fu-KK3iOF>UlK4NdIxHHe?@v#9}zKZ@*$ti zXKjn@qkJa+P(G9W$@%<@J3g0Te5NtF(6=VB4u8wE4u8v(=6zqrkk=TObLg2G0*7JH0VBW7|Jdd0BGtIqN{}cEo8o%Eqxv(ojJ-^r zx2<=GpT=~&KPsR5xIFQUs0p4oxJ)rFUnic+IyV0ZG;XsY{vM9>E1NArQ}W=Gu~GJd zZN-591#C`i^5^loa&COLk|oFZJ2hD|q%2=^58fv>0akV&#agAb9eL>f-OWEgNzVUA zpi^J-S*gh%rR1}oTz)NJJ-!bBPQ1!m>F@F1hrXk)ig7%EeNlMa?VfTx@3H%BkKS87 zdKW`hJA590)owNDeG$Bio30JN@yJ@~ku?*tl)wKD-ZwtL(;=YgKK(Y}#LJpbUrz7S zO&+~`JyESuqb&i*;y!&@`t*zRK8<)}<#^iA0lM<(gE)_qUF1{Ps{F%!3cS~+-zWZ? z3%d3tJVOob!@J$ElltlF#D_lG1F#swekZTV$$McTKCCP#_F<(hl!JUbRl*Wp?Hii3 zqb;`04c68KOZkq;$$U$&adMD$P8K`adN^Hq*v6J`g(^h2_~o>ZGPX`7`Oa>(njMXN zJ7r=Jd)m^5%F6zFv8!@0qPA6*?UI|wmg##dlhvvoc3v%Wc3Fnqe4AxS=i4E3b+nbQ z+Ao$9d3`~bo2~{M-OZP=X)MVX^d$`qqyk$oV{>cTgIONJ?`RB6z~A0xs3ec%Zynkr6*l|Ln$m-SXel-q=b3xm3W8GV;8cL?_k}< zB(I!rt|wG@_*7B>9mIgZ)zpV0jrBF*a^r#tIbG~Mf%?L8vPEh5-Ho!ls^k&+hW07D z)60>LFRWcov2nubm77dg(ZV-)WW5F0B)>dt+vNTR!7hR zI(3G@;K~Wc$a177WA!rsD(WKjMpc7R(^y?iIXN%9ytaOg!MEw=TgS^pm+VYBHBxKP zp5Mm2{9;25vIcG1U%#B!Qt3^5s;j;2vCVuf8(A(SdGK?w+c-bG?UH$UG4(0(ikIHS zKhkwxK9%1p!J%Kd0&FEqe4GV@CR)|ZfV^`D~Vqbsc z_Db;Z4d$WAYiBAxYEn((@*u8WVvs~VoYP+DY~=Wfi!V<1ANc0wW7Qo;K}pGFc(0Td z&0jEK!sWq|g~i45tpziS&NqlE@mHn6TV3(<3g#yvuNarl-UhC1uXOcTqX0c_RaJSU(v4S19rClZ+9-)2f0sl4>^awy4<^a-@|xOJ zHN2vbH?mxKNZ<>_^$Jn&#?8sEC5;u3P5H_*5#tWNN5MZv!3BEjsr;q9375;{1*ey< zp|Pwi+|Xc@3t^O7oEdWo`85b(uiWZBtnw3|%8)Y}f%3{#b~|#CvX)`*_cH!%h;;RE zp(_ib&8oo*Xz*BCT2+%~oFaNY(PdN7&!khS84XTHOo8Ve(8a5-K!_@-@y@W)#h=bL z`P8~A8kh5CJH555|MMFowaQRzUEifvYC689*0mlFl$6CrHi)6bYmbN~_9a)_`SU0+ z^%aeIslQp`(9?ajMAg?mTOu^c3t88Xmqw zuoxGefrGwxBKX|zxNtbd7f>+Y>-r58u9xGblaoJ$A`Iqq&*xBx0(XKDo4Ol2?f4kGA0>TvDTdNSx$VhBB7}1FPZ9|m<*}y|iDs0G_aqWAlr7ID z5^Dy3D9YR4 zg#G~b;y;u~j6nI-9}|fpl*j&@NR*>|Z7D1OBBp^y~Q$CpY0pkH=7y2VSSLDk0&POL@(-RY|q#cD7wc#|J`?9 z??qLdb*sMpvlS!1He@<%_!hFW!BJDiWRMTjQUAkI+Vz->=EPJ$FZr z|2fV;^sV@HJe5e)!H1xm+cS6c@?YcD-vhJ>->0L$0eTZ+!oL-z>MwBXe*yY|XVTLL zl3v8HT<9Oaz2}Y|{zY#6i-0eBE|K8h20L%a@nsZ68yVY`TdHw_ug@e zztOEf74&I;NF>Ns=oj+KT>4_rTadr}dsq4WT6*PgU%8{vznYpr_OAy02IO@*`Zmz# z{3((6LdX~T*Shlef+>ZV< zSf_7v=~F@9h<>JG=-WPc#~}aHoPpYhy$;w#AEc+R2L0$U*hl>kIpo_uWXD;7aXg2B z`PcC;5B$pm|MI~9nFk)m!$UeuS<Q`kv-l z5@zx{P3qew*dLf5^heHgsP8z@7)pnt%jpU|h~vlKzE0HANe9MJI(mqLZI*^ev)Cb7 zQ7yDwB?)~bKh1OKkYd;|`8^&Q_UW)?{ec2xy<|B^mQ81h6NP{DbO9^7k4ZtNNPea7 z>x8g>v*b@mSoyg_!tdhc89MM{3vaKoQ(*u1{#W0P|KF4N|Cb~~Cgi_L5x4(qKdA4r G`Thq=o+A+e literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/mime.lua b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/mime.lua new file mode 100644 index 000000000000..d3abac51cad5 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/mime.lua @@ -0,0 +1,89 @@ +----------------------------------------------------------------------------- +-- MIME support for the Lua language. +-- Author: Diego Nehab +-- Conforming to RFCs 2045-2049 +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local ltn12 = require("ltn12") +local mime = require("mime.core") +local string = require("string") +local _M = mime + +-- encode, decode and wrap algorithm tables +local encodet, decodet, wrapt = {},{},{} + +_M.encodet = encodet +_M.decodet = decodet +_M.wrapt = wrapt + +-- creates a function that chooses a filter by name from a given table +local function choose(table) + return function(name, opt1, opt2) + if base.type(name) ~= "string" then + name, opt1, opt2 = "default", name, opt1 + end + local f = table[name or "nil"] + if not f then + base.error("unknown key (" .. base.tostring(name) .. ")", 3) + else return f(opt1, opt2) end + end +end + +-- define the encoding filters +encodet['base64'] = function() + return ltn12.filter.cycle(_M.b64, "") +end + +encodet['quoted-printable'] = function(mode) + return ltn12.filter.cycle(_M.qp, "", + (mode == "binary") and "=0D=0A" or "\r\n") +end + +-- define the decoding filters +decodet['base64'] = function() + return ltn12.filter.cycle(_M.unb64, "") +end + +decodet['quoted-printable'] = function() + return ltn12.filter.cycle(_M.unqp, "") +end + +local function format(chunk) + if chunk then + if chunk == "" then return "''" + else return string.len(chunk) end + else return "nil" end +end + +-- define the line-wrap filters +wrapt['text'] = function(length) + length = length or 76 + return ltn12.filter.cycle(_M.wrp, length, length) +end +wrapt['base64'] = wrapt['text'] +wrapt['default'] = wrapt['text'] + +wrapt['quoted-printable'] = function() + return ltn12.filter.cycle(_M.qpwrp, 76, 76) +end + +-- function that choose the encoding, decoding or wrap algorithm +_M.encode = choose(encodet) +_M.decode = choose(decodet) +_M.wrap = choose(wrapt) + +-- define the end-of-line normalization filter +function _M.normalize(marker) + return ltn12.filter.cycle(_M.eol, 0, marker) +end + +-- high level stuffing filter +function _M.stuff() + return ltn12.filter.cycle(_M.dot, 2) +end + +return _M diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/mime.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/mime.luac new file mode 100644 index 0000000000000000000000000000000000000000..fa9a18267509f798630f1f755976c6c2ff7e5b34 GIT binary patch literal 3856 zcmcInZExFD6h7DS+6FdY0y1b>=}Mst26SV*Ghvs~^0G<{G$tfqN)u;|nmB3dsFY83 zmUh5z=*RvLr`nLZb{or8Yg;k?>YDQo_p?jaWZkeUA!U695ct{F^S2l zSL@E3wRSP#IT)vx@O=vS%n}KFkso|f+nD!4kzLFmV6K#se=z6gOC|C<@E4G`gHAqB zck{jA?u;Q3mFGG5{4&0WvxCBcQp)Ng$efyVRh@Xri&EpDUy5crk^*BxIsR33tm1RY8>B^RWP#kBt%VK%+et>!VY zEIY{+n|OxgR%>Z)IIUKrCFT?HlC<45x3TF?tvOp$uZl@CB(I?}gt@X%FBVK$+OhNxJE1PIToi81N5qm5%aR48z@i^Y5b_$4 z7Fc6i(B*zjR;o46L2R|%(pHIuNFCA&>y_5~L@TeH0G^11Q}rE+`%AwSEk_Fcx(HkX z&`D1{iE#l~lu#C}P`Wtoj&G@6C~{70DfsXMRWWWSl@loW5s#umBiBYTp5&T7!a7<+ z-?RQhD^<7H+DbRMHn(tX&Q5#inYcmnpsyW)*C8JS>Qhc>N*M9?%=r3!RB#@3uQl3c@j<*V- z$Z5a;^hF%mwHBfDd8{1$W}*{=8gDO*-eJbWG+%9ey-6?oR|5;tPnIQ&CxJ&J4du(% zQ2d6<2qN2C6gc^B2Obo-VdLdAYT6T60MXEmA;5!WgH9W)EG~i1 zf*m{ycEc>#D2Vw(_!K + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. +]] +require("datconf") +local ioctl_help = require "ioctl_helper" +local mtkwifi = {} +local logDisable = 1 +function debug_write(...) + -- luci.http.write(...) + if logDisable == 1 then + return + end + local syslog_msg = ""; + local ff = io.open("/tmp/mtkwifi", "a") + local nargs = select('#',...) + + + + for n=1, nargs do + local v = select(n,...) + if (type(v) == "string" or type(v) == "number") then + ff:write(v.." ") + syslog_msg = syslog_msg..v.." "; + elseif (type(v) == "boolean") then + if v then + ff:write("true ") + syslog_msg = syslog_msg.."true "; + else + ff:write("false ") + syslog_msg = syslog_msg.."false "; + end + elseif (type(v) == "nil") then + ff:write("nil ") + syslog_msg = syslog_msg.."nil "; + else + ff:write(" ") + syslog_msg = syslog_msg.." "; + end + end + ff:write("\n") + ff:close() + nixio.syslog("debug", syslog_msg) +end + +function mtkwifi.get_table_length(T) + local count = 0 + for _ in pairs(T) do + count = count + 1 + end + return count +end + +function mtkwifi.get_file_lines(fileName) + local fd = io.open(fileName, "r") + if not fd then return end + local content = fd:read("*all") + fd:close() + return mtkwifi.__lines(content) +end + +function mtkwifi.__split(s, delimiter) + if s == nil then s = "0" end + local result = {}; + for match in (s..delimiter):gmatch("(.-)"..delimiter) do + table.insert(result, match); + end + return result; +end + +function string:split(sep) + local sep, fields = sep or ":", {} + local pattern = string.format("([^%s]+)", sep) + self:gsub(pattern, function(c) fields[#fields+1] = c end) + return fields +end + +function mtkwifi.__trim(s) + if s then return (s:gsub("^%s*(.-)%s*$", "%1")) end +end + +function mtkwifi.__handleSpecialChars(s) + s = s:gsub("\\", "\\\\") + s = s:gsub("\"", "\\\"") + return s +end + +function mtkwifi.__spairs(t, order) + -- collect the keys + local keys = {} + for k in pairs(t) do keys[#keys+1] = k end + -- if order function given, sort by it by passing the table and keys a, b, + -- otherwise just sort the keys + --[[ + if order then + table.sort(keys, function(a,b) return order(t, a, b) end) + -- table.sort(keys, order) + else + table.sort(keys) + end + ]] + table.sort(keys, order) + -- return the iterator function + local i = 0 + return function() + i = i + 1 + if keys[i] then + return keys[i], t[keys[i]] + end + end +end + +function mtkwifi.__lines(str) + local t = {} + local function helper(line) table.insert(t, line) return "" end + helper((str:gsub("(.-)\r?\n", helper))) + return t +end + +function mtkwifi.__get_l1dat() + if not pcall(require, "l1dat_parser") then + return + end + + local parser = require("l1dat_parser") + local l1dat = parser.load_l1_profile(parser.L1_DAT_PATH) + + return l1dat, parser +end + +function mtkwifi.sleep(s) + local ntime = os.clock() + s + repeat until os.clock() > ntime +end + +function mtkwifi.deepcopy(orig) + local orig_type = type(orig) + local copy + if orig_type == 'table' then + copy = {} + for orig_key, orig_value in next, orig, nil do + copy[mtkwifi.deepcopy(orig_key)] = mtkwifi.deepcopy(orig_value) + end + setmetatable(copy, mtkwifi.deepcopy(getmetatable(orig))) + else -- number, string, boolean, etc + copy = orig + end + return copy +end + +function mtkwifi.read_pipe(pipe) + local retry_count = 10 + local fp, txt, err + repeat -- fp:read() may return error, "Interrupted system call", and can be recovered by doing it again + fp = io.popen(pipe) + txt, err = fp:read("*a") + fp:close() + retry_count = retry_count - 1 + until err == nil or retry_count == 0 + return txt +end + +function mtkwifi.detect_triband() + local devs = mtkwifi.get_all_devs() + local l1dat, l1 = mtkwifi.__get_l1dat() + local dridx = l1.DEV_RINDEX + local main_ifname + local bands = 0 + for _,dev in ipairs(devs) do + main_ifname = l1dat and l1dat[dridx][dev.devname].main_ifname or dbdc_prefix[mainidx][subidx].."0" + if mtkwifi.exists("/sys/class/net/"..main_ifname) then + bands = bands + 1 + end + end + return bands +end + +function mtkwifi.detect_first_card() + local devs = mtkwifi.get_all_devs() + local first_card_profile + + for i,dev in ipairs(devs) do + first_card_profile = dev.profile + if i == 1 then break end + end + + return first_card_profile +end + +function mtkwifi.load_profile(path, raw) + local cfgs = {} + + cfgobj = datconf.openfile(path) + if cfgobj then + cfgs = cfgobj:getall() + cfgobj:close() + elseif raw then + cfgs = datconf.parse(raw) + end + + return cfgs +end + +function mtkwifi.save_profile(cfgs, path) + + if not cfgs then + debug_write("configuration was empty, nothing saved") + return + end + + -- Keep a backup of last profile settings + -- if string.match(path, "([^/]+)\.dat") then + -- os.execute("cp -f "..path.." "..mtkwifi.__profile_previous_settings_path(path)) + -- end + local datobj = datconf.openfile(path) + datobj:merge(cfgs) + datobj:close(true) -- means close and commit + + if pcall(require, "mtknvram") then + local nvram = require("mtknvram") + local l1dat, l1 = mtkwifi.__get_l1dat() + local zone = l1 and l1.l1_path_to_zone(path) + + if pcall(require, "map_helper") and zone == "dev1" then + mtkwifi.save_easymesh_profile_to_nvram() + else + if not l1dat then + debug_write("save_profile: no l1dat", path) + nvram.nvram_save_profile(path) + else + if zone then + debug_write("save_profile:", path, zone) + nvram.nvram_save_profile(path, zone) + else + debug_write("save_profile:", path) + nvram.nvram_save_profile(path) + end + end + end + end + os.execute("sync >/dev/null 2>&1") +end + +function mtkwifi.split_profile(path, path_2g, path_5g) + assert(path) + assert(path_2g) + assert(path_5g) + local cfgs = mtkwifi.load_profile(path) + local dirty = { + "Channel", + "WirelessMode", + "TxRate", + "WmmCapable", + "NoForwarding", + "HideSSID", + "IEEE8021X", + "PreAuth", + "AuthMode", + "EncrypType", + "RekeyMethod", + "RekeyInterval", + "PMKCachePeriod", + "DefaultKeyId", + "Key{n}Type", + "HT_EXTCHA", + "RADIUS_Server", + "RADIUS_Port", + } + local cfg5g = mtkwifi.deepcopy(cfgs) + for _,v in ipairs(dirty) do + cfg5g[v] = mtkwifi.token_get(cfgs[v], 1, 0) + assert(cfg5g[v]) + end + mtkwifi.save_profile(cfg5g, path_5g) + + local cfg2g = mtkwifi.deepcopy(cfgs) + for _,v in ipairs(dirty) do + cfg2g[v] = mtkwifi.token_get(cfgs[v], 1, 0) + assert(cfg2g[v]) + end + mtkwifi.save_profile(cfg2g, path_2g) +end + +function mtkwifi.merge_profile(path, path_2g, path_5g) + local cfg2g = mtkwifi.load_profile(path_2g) + local cfg5g = mtkwifi.load_profile(path_5g) + local dirty = { + "Channel", + "WirelessMode", + "TxRate", + "WmmCapable", + "NoForwarding", + "HideSSID", + "IEEE8021X", + "PreAuth", + "AuthMode", + "EncrypType", + "RekeyMethod", + "RekeyInterval", + "PMKCachePeriod", + "DefaultKeyId", + "Key{n}Type", + "HT_EXTCHA", + "RADIUS_Server", + "RADIUS_Port", + } + local cfgs = mtkwifi.deepcopy(cfg2g) + for _,v in dirty do + -- TODO + end + mtkwifi.save_profile(cfgs, path) +end + +-- update path1 by path2 +function mtkwifi.update_profile(path1, path2) + local cfg1 = datconf.openfile(path1) + local cfg2 = datconf.openfile(path2) + + cfg1:merge(cfg2:getall()) + cfg1:close(true) + cfg2:close() + os.execute("sync >/dev/null 2>&1") +end + +function mtkwifi.__child_info_path() + local path = "/tmp/mtk/wifi/child_info.dat" + os.execute("mkdir -p /tmp/mtk/wifi") + return path +end + +function mtkwifi.__profile_previous_settings_path(profile) + assert(type(profile) == "string") + local bak = "/tmp/mtk/wifi/"..string.match(profile, "([^/]+)\.dat")..".last" + os.execute("mkdir -p /tmp/mtk/wifi") + return bak +end + +function mtkwifi.__profile_applied_settings_path(profile) + assert(type(profile) == "string") + local bak + if string.match(profile, "([^/]+)\.dat") then + os.execute("mkdir -p /tmp/mtk/wifi") + bak = "/tmp/mtk/wifi/"..string.match(profile, "([^/]+)\.dat")..".applied" + elseif string.match(profile, "([^/]+)\.txt") then + os.execute("mkdir -p /tmp/mtk/wifi") + bak = "/tmp/mtk/wifi/"..string.match(profile, "([^/]+)\.txt")..".applied" + elseif string.match(profile, "([^/]+)$") then + os.execute("mkdir -p /tmp/mtk/wifi") + bak = "/tmp/mtk/wifi/"..string.match(profile, "([^/]+)$")..".applied" + else + bak = "" + end + + return bak +end + +-- if path2 is not given, use backup of path1. +function mtkwifi.diff_profile(path1, path2) + assert(path1) + if not path2 then + path2 = mtkwifi.__profile_applied_settings_path(path1) + if not mtkwifi.exists(path2) then + return {} + end + end + assert(path2) + + local cfg1 + local cfg2 + local diff = {} + if path1 == mtkwifi.__easymesh_bss_cfgs_path() then + cfg1 = mtkwifi.get_file_lines(path1) or {} + cfg2 = mtkwifi.get_file_lines(path2) or {} + else + cfg1 = mtkwifi.load_profile(path1) or {} + cfg2 = mtkwifi.load_profile(path2) or {} + end + + for k,v in pairs(cfg1) do + if cfg2[k] ~= cfg1[k] then + diff[k] = {cfg1[k] or "", cfg2[k] or ""} + end + end + + for k,v in pairs(cfg2) do + if cfg2[k] ~= cfg1[k] then + diff[k] = {cfg1[k] or "", cfg2[k] or ""} + end + end + + return diff +end + +function mtkwifi.__fork_exec(command) + if type(command) ~= type("") or command == "" then + debug_write("__fork_exec : Incorrect command! Expected non-empty string type, got ",type(command)) + nixio.syslog("err", "__fork_exec : Incorrect command! Expected non-empty string type, got "..type(command)) + else + local nixio = require("nixio") + -- If nixio.exec() fails, then child process will be reaped automatically and + -- it will be achieved by ignoring SIGCHLD signal here in parent process! + if not nixio.signal(17,"ign") then + nixio.syslog("warning", "__fork_exec : Failed to set SIG_IGN for SIGCHLD!") + debug_write("__fork_exec : Failed to set SIG_IGN for SIGCHLD!") + end + local pid = nixio.fork() + if pid < 0 then + nixio.syslog("err", "__fork_exec : [Fork Failure] "..command) + debug_write("__fork_exec : [Fork Failure] "..command) + elseif pid == 0 then + -- change to root dir to flush out any opened directory streams of parent process. + nixio.chdir("/") + + -- As file descriptors are inherited by child process, all unused file descriptors must be closed. + -- Make stdin, out, err file descriptors point to /dev/null using dup2. + -- As a result, it will not corrupt stdin, out, err file descriptors of parent process. + local null = nixio.open("/dev/null", "w+") + if null then + nixio.dup(null, nixio.stderr) + nixio.dup(null, nixio.stdout) + nixio.dup(null, nixio.stdin) + if null:fileno() > 2 then + null:close() + end + end + debug_write("__fork_exec : cmd = "..command) + -- replaces the child process image with the new process image generated by provided command + nixio.exec("/bin/sh", "-c", command) + os.exit(true) + end + end +end + +function mtkwifi.is_child_active() + local fd = io.open(mtkwifi.__child_info_path(), "r") + if not fd then + os.execute("rm -f "..mtkwifi.__child_info_path()) + return false + end + local content = fd:read("*all") + fd:close() + if not content then + os.execute("rm -f "..mtkwifi.__child_info_path()) + return false + end + local active_pid_list = {} + for _,pid in ipairs(mtkwifi.__lines(content)) do + pid = pid:match("CHILD_PID=%s*(%d+)%s*") + if pid then + if tonumber(mtkwifi.read_pipe("ps | grep -v grep | grep -cw "..pid)) == 1 then + table.insert(active_pid_list, pid) + end + end + end + if next(active_pid_list) ~= nil then + return true + else + os.execute("rm -f "..mtkwifi.__child_info_path()) + return false + end + os.execute("sync >/dev/null 2>&1") +end + +function mtkwifi.__run_in_child_env(cbFn,...) + if type(cbFn) ~= "function" then + debug_write("__run_in_child_env : Function type expected, got ", type(cbFn)) + nixio.syslog("err", "__run_in_child_env : Function type expected, got "..type(cbFn)) + else + local unpack = unpack or table.unpack + local cbArgs = {...} + local nixio = require("nixio") + -- Let child process reap automatically! + if not nixio.signal(17,"ign") then + nixio.syslog("warning", "__run_in_child_env : Failed to set SIG_IGN for SIGCHLD!") + debug_write("__run_in_child_env : Failed to set SIG_IGN for SIGCHLD!") + end + local pid = nixio.fork() + if pid < 0 then + debug_write("__run_in_child_env : Fork failure") + nixio.syslog("err", "__run_in_child_env : Fork failure") + elseif pid == 0 then + -- Change to root dir to flush out any opened directory streams of parent process. + nixio.chdir("/") + + -- As file descriptors are inherited by child process, all unnecessary file descriptors must be closed. + -- Make stdin, out, err file descriptors point to /dev/null using dup2. + -- As a result, it will not corrupt stdin, out, err file descriptors of parent process. + local null = nixio.open("/dev/null", "w+") + if null then + nixio.dup(null, nixio.stderr) + nixio.dup(null, nixio.stdout) + nixio.dup(null, nixio.stdin) + if null:fileno() > 2 then + null:close() + end + end + local fd = io.open(mtkwifi.__child_info_path(), "a") + if fd then + fd:write("CHILD_PID=",nixio.getpid(),"\n") + fd:close() + end + cbFn(unpack(cbArgs)) + os.exit(true) + end + end + os.execute("sync >/dev/null 2>&1") +end + +-- Mode 12 and 13 are only available for STAs. +local WirelessModeList = { + [0] = "B/G mixed", + [1] = "B only", + [2] = "A only", + -- [3] = "A/B/G mixed", + [4] = "G only", + -- [5] = "A/B/G/GN/AN mixed", + [6] = "N in 2.4G only", + [7] = "G/GN", -- i.e., no CCK mode + [8] = "A/N in 5 band", + [9] = "B/G/GN mode", + -- [10] = "A/AN/G/GN mode", --not support B mode + [11] = "only N in 5G band", + -- [12] = "B/G/GN/A/AN/AC mixed", + -- [13] = "G/GN/A/AN/AC mixed", -- no B mode + [14] = "A/AC/AN mixed", + [15] = "AC/AN mixed", --but no A mode + [16] = "HE_2G mode", --HE Wireless Mode + [17] = "HE_5G mode", --HE Wireless Mode + [18] = "HE_6G mode", --HE Wireless Mode +} + +local DevicePropertyMap = { + -- 2.4G + { + device="MT7622", + band={"0", "1", "4", "9"}, + isPowerBoostSupported=true, + isMultiAPSupported=true, + isWPA3_192bitSupported=true + }, + + { + device="MT7620", + band={"0", "1", "4", "9"}, + maxTxStream=2, + maxRxStream=2, + maxVif=8 + }, + + { + device="MT7628", + band={"0", "1", "4", "6", "7", "9"}, + maxTxStream=2, + maxRxStream=2, + maxVif=8, + isMultiAPSupported=true, + isWPA3_192bitSupported=true + }, + + { + device="MT7603", + band={"0", "1", "4", "6", "7", "9"}, + maxTxStream=2, + maxRxStream=2, + maxVif=8, + isMultiAPSupported=true, + isWPA3_192bitSupported=true + }, + + -- 5G + { + device="MT7612", + band={"2", "8", "11", "14", "15"}, + maxTxStream=2, + maxRxStream=2, + }, + + { + device="MT7662", + band={"2", "8", "11", "14", "15"}, + maxTxStream=2, + maxRxStream=2, + }, + + -- Mix + { + device="MT7615", + band={"0", "1", "4", "9", "2", "8", "14", "15"}, + isPowerBoostSupported=false, + isMultiAPSupported=true, + isWPA3_192bitSupported=true, + maxVif=16, + maxDBDCVif=8 + }, + + { + device="MT7915", + band={"0", "1", "4", "9", "2", "8", "14", "15", "16", "17", "18"}, + isPowerBoostSupported=false, + isMultiAPSupported=true, + isWPA3_192bitSupported=true, + maxVif=16, + maxDBDCVif=16, + invalidChBwList={161} + }, + + { + device="MT7916", + band={"0", "1", "4", "9", "2", "8", "14", "15", "16", "17", "18"}, + isPowerBoostSupported=false, + isMultiAPSupported=true, + isWPA3_192bitSupported=true, + maxVif=16, + maxDBDCVif=16, + invalidChBwList={161}, + maxTxStream=2, + maxRxStream=2, + }, + + { + device="MT7981", + band={"0", "1", "4", "9", "2", "8", "14", "15", "16", "17", "18"}, + isPowerBoostSupported=false, + isMultiAPSupported=true, + isWPA3_192bitSupported=true, + maxVif=16, + maxDBDCVif=16, + invalidChBwList={161}, + maxTxStream=2, + maxRxStream=2, + }, + + { + device="MT7986", + band={"0", "1", "4", "9", "2", "8", "14", "15", "16", "17", "18"}, + isPowerBoostSupported=false, + isMultiAPSupported=true, + isWPA3_192bitSupported=true, + maxVif=16, + maxDBDCVif=16, + invalidChBwList={161}, + maxTxStream=4, + maxRxStream=4, + }, + + { + device="MT7663", + band={"0", "1", "4", "9", "2", "8", "14", "15"}, + maxTxStream=2, + maxRxStream=2, + invalidChBwList={160,161}, + isMultiAPSupported=true, + isWPA3_192bitSupported=true + }, + + { + device="MT7613", + band={"0", "1", "4", "9", "2", "8", "14", "15"}, + maxTxStream=2, + maxRxStream=2, + invalidChBwList={160,161}, + isMultiAPSupported=true, + isWPA3_192bitSupported=true + }, + + { + device="MT7626", + band={"0", "1", "4", "9", "2", "8", "14", "15"}, + maxTxStream=3, + maxRxStream=3, + invalidChBwList={160,161}, + wdsBand="2.4G", + mimoBand="5G", + maxDBDCVif=8 + }, + + { + device="MT7629", + band={"0", "1", "4", "9", "2", "8", "14", "15"}, + maxTxStream=3, + maxRxStream=3, + invalidChBwList={160,161}, + wdsBand="2.4G", + mimoBand="5G", + maxDBDCVif=8, + isMultiAPSupported=true + } +} + +mtkwifi.CountryRegionList_6G_All = { + {region=0, text="0: Ch1~233"}, + {region=1, text="1: Ch1~97"}, + {region=2, text="2: Ch101~117"}, + {region=3, text="3: Ch121~185"}, + {region=4, text="4: Ch189~233"}, + {region=5, text="5: Ch1~97"}, + {region=6, text="6: Ch1~97"}, + {region=7, text="7: Ch1~97, Ch101~109"}, +} + +mtkwifi.CountryRegionList_5G_All = { + {region=0, text="0: Ch36~64, Ch149~165"}, + {region=1, text="1: Ch36~64, Ch100~140"}, + {region=2, text="2: Ch36~64"}, + {region=3, text="3: Ch52~64, Ch149~161"}, + {region=4, text="4: Ch149~165"}, + {region=5, text="5: Ch149~161"}, + {region=6, text="6: Ch36~48"}, + {region=7, text="7: Ch36~64, Ch100~140, Ch149~165"}, + {region=8, text="8: Ch52~64"}, + {region=9, text="9: Ch36~64, Ch100~116, Ch132~140, Ch149~165"}, + {region=10, text="10: Ch36~48, Ch149~165"}, + {region=11, text="11: Ch36~64, Ch100~120, Ch149~161"}, + {region=12, text="12: Ch36~64, Ch100~144"}, + {region=13, text="13: Ch36~64, Ch100~144, Ch149~165"}, + {region=14, text="14: Ch36~64, Ch100~116, Ch132~144, Ch149~165"}, + {region=15, text="15: Ch149~173"}, + {region=16, text="16: Ch52~64, Ch149~165"}, + {region=17, text="17: Ch36~48, Ch149~161"}, + {region=18, text="18: Ch36~64, Ch100~116, Ch132~140"}, + {region=19, text="19: Ch56~64, Ch100~140, Ch149~161"}, + {region=20, text="20: Ch36~64, Ch100~124, Ch149~161"}, + {region=21, text="21: Ch36~64, Ch100~140, Ch149~161"}, + {region=22, text="22: Ch100~140"}, + {region=30, text="30: Ch36~48, Ch52~64, Ch100~140, Ch149~165"}, + {region=31, text="31: Ch52~64, Ch100~140, Ch149~165"}, + {region=32, text="32: Ch36~48, Ch52~64, Ch100~140, Ch149~161"}, + {region=33, text="33: Ch36~48, Ch52~64, Ch100~140"}, + {region=34, text="34: Ch36~48, Ch52~64, Ch149~165"}, + {region=35, text="35: Ch36~48, Ch52~64"}, + {region=36, text="36: Ch36~48, Ch100~140, Ch149~165"}, + {region=37, text="37: Ch36~48, Ch52~64, Ch149~165, Ch173"} +} + +mtkwifi.CountryRegionList_2G_All = { + {region=0, text="0: Ch1~11"}, + {region=1, text="1: Ch1~13"}, + {region=2, text="2: Ch10~11"}, + {region=3, text="3: Ch10~13"}, + {region=4, text="4: Ch14"}, + {region=5, text="5: Ch1~14"}, + {region=6, text="6: Ch3~9"}, + {region=7, text="7: Ch5~13"}, + {region=31, text="31: Ch1~11, Ch12~14"}, + {region=32, text="32: Ch1~11, Ch12~13"}, + {region=33, text="33: Ch1~14"} +} + +mtkwifi.ChannelList_6G_All = { + {channel= 0 , text="Channel 0 (Auto )", region={}}, + {channel= 1 , text="Channel 1 (5.955 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1}}, + {channel= 5 , text="Channel 5 (5.975 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1}}, + {channel= 9 , text="Channel 9 (5.995 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1}}, + {channel= 13 , text="Channel 13 (6.015 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1}}, + {channel= 17 , text="Channel 17 (6.035 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1}}, + {channel= 21 , text="Channel 21 (6.055 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1}}, + {channel= 25 , text="Channel 25 (6.075 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1}}, + {channel= 29 , text="Channel 29 (6.095 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1}}, + {channel= 33 , text="Channel 33 (6.115 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1}}, + {channel= 37 , text="Channel 37 (6.135 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1}}, + {channel= 41 , text="Channel 41 (6.155 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1}}, + {channel= 45 , text="Channel 45 (6.175 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1}}, + {channel= 49 , text="Channel 49 (6.195 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1}}, + {channel= 53 , text="Channel 53 (6.215 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1}}, + {channel= 57 , text="Channel 57 (6.235 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1}}, + {channel= 61 , text="Channel 61 (6.255 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1}}, + {channel= 65 , text="Channel 65 (6.275 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1}}, + {channel= 69 , text="Channel 69 (6.295 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1}}, + {channel= 73 , text="Channel 73 (6.315 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1}}, + {channel= 77 , text="Channel 77 (6.335 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1}}, + {channel= 81 , text="Channel 81 (6.355 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1}}, + {channel= 85 , text="Channel 85 (6.375 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1}}, + {channel= 89 , text="Channel 89 (6.395 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1}}, + {channel= 93 , text="Channel 93 (6.415 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1}}, + {channel= 97 , text="Channel 97 (6.435 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1}}, + {channel= 101, text="Channel 101 (6.455 GHz)", region={[0]=1, [2]=1, [7]=1}}, + {channel= 105, text="Channel 105 (6.475 GHz)", region={[0]=1, [2]=1, [7]=1}}, + {channel= 109, text="Channel 109 (6.495 GHz)", region={[0]=1, [2]=1, [7]=1}}, + {channel= 113, text="Channel 113 (6.515 GHz)", region={[0]=1, [2]=1}}, + {channel= 117, text="Channel 117 (6.535 GHz)", region={[0]=1, [2]=1}}, + {channel= 121, text="Channel 121 (6.555 GHz)", region={[0]=1, [3]=1}}, + {channel= 125, text="Channel 125 (6.575 GHz)", region={[0]=1, [3]=1}}, + {channel= 129, text="Channel 129 (6.595 GHz)", region={[0]=1, [3]=1}}, + {channel= 133, text="Channel 133 (6.615 GHz)", region={[0]=1, [3]=1}}, + {channel= 137, text="Channel 137 (6.635 GHz)", region={[0]=1, [3]=1}}, + {channel= 141, text="Channel 141 (6.655 GHz)", region={[0]=1, [3]=1}}, + {channel= 145, text="Channel 145 (6.675 GHz)", region={[0]=1, [3]=1}}, + {channel= 149, text="Channel 149 (6.695 GHz)", region={[0]=1, [3]=1}}, + {channel= 153, text="Channel 153 (6.715 GHz)", region={[0]=1, [3]=1}}, + {channel= 157, text="Channel 157 (6.735 GHz)", region={[0]=1, [3]=1}}, + {channel= 161, text="Channel 161 (6.755 GHz)", region={[0]=1, [3]=1}}, + {channel= 165, text="Channel 165 (6.775 GHz)", region={[0]=1, [3]=1}}, + {channel= 169, text="Channel 169 (6.795 GHz)", region={[0]=1, [3]=1}}, + {channel= 173, text="Channel 173 (6.815 GHz)", region={[0]=1, [3]=1}}, + {channel= 177, text="Channel 177 (6.835 GHz)", region={[0]=1, [3]=1}}, + {channel= 181, text="Channel 181 (6.855 GHz)", region={[0]=1, [3]=1}}, + {channel= 185, text="Channel 185 (6.875 GHz)", region={[0]=1, [3]=1}}, + {channel= 189, text="Channel 189 (6.895 GHz)", region={[0]=1, [4]=1}}, + {channel= 193, text="Channel 193 (6.915 GHz)", region={[0]=1, [4]=1}}, + {channel= 197, text="Channel 197 (6.935 GHz)", region={[0]=1, [4]=1}}, + {channel= 201, text="Channel 201 (6.955 GHz)", region={[0]=1, [4]=1}}, + {channel= 205, text="Channel 205 (6.975 GHz)", region={[0]=1, [4]=1}}, + {channel= 209, text="Channel 209 (6.995 GHz)", region={[0]=1, [4]=1}}, + {channel= 213, text="Channel 213 (7.015 GHz)", region={[0]=1, [4]=1}}, + {channel= 217, text="Channel 217 (7.035 GHz)", region={[0]=1, [4]=1}}, + {channel= 221, text="Channel 221 (7.055 GHz)", region={[0]=1, [4]=1}}, + {channel= 225, text="Channel 225 (7.075 GHz)", region={[0]=1, [4]=1}}, + {channel= 229, text="Channel 229 (7.095 GHz)", region={[0]=1, [4]=1}}, + {channel= 233, text="Channel 233 (7.115 GHz)", region={[0]=1, [4]=1}}, +} + +mtkwifi.ChannelList_5G_All = { + {channel=0, text="Channel 0 (Auto )", region={}}, + {channel= 36, text="Channel 36 (5.180 GHz)", region={[0]=1, [1]=1, [2]=1, [6]=1, [7]=1, [9]=1, [10]=1, [11]=1, [12]=1, [13]=1, [14]=1, [17]=1, [18]=1, [20]=1, [21]=1, [30]=1, [32]=1, [33]=1, [34]=1, [35]=1, [36]=1, [37]=1}}, + {channel= 40, text="Channel 40 (5.200 GHz)", region={[0]=1, [1]=1, [2]=1, [6]=1, [7]=1, [9]=1, [10]=1, [11]=1, [12]=1, [13]=1, [14]=1, [17]=1, [18]=1, [20]=1, [21]=1, [30]=1, [32]=1, [33]=1, [34]=1, [35]=1, [36]=1, [37]=1}}, + {channel= 44, text="Channel 44 (5.220 GHz)", region={[0]=1, [1]=1, [2]=1, [6]=1, [7]=1, [9]=1, [10]=1, [11]=1, [12]=1, [13]=1, [14]=1, [17]=1, [18]=1, [20]=1, [21]=1, [30]=1, [32]=1, [33]=1, [34]=1, [35]=1, [36]=1, [37]=1}}, + {channel= 48, text="Channel 48 (5.240 GHz)", region={[0]=1, [1]=1, [2]=1, [6]=1, [7]=1, [9]=1, [10]=1, [11]=1, [12]=1, [13]=1, [14]=1, [17]=1, [18]=1, [20]=1, [21]=1, [30]=1, [32]=1, [33]=1, [34]=1, [35]=1, [36]=1, [37]=1}}, + {channel= 52, text="Channel 52 (5.260 GHz)", region={[0]=1, [1]=1, [2]=1, [3]=1, [7]=1, [8]=1, [9]=1, [11]=1, [12]=1, [13]=1, [14]=1, [16]=1, [18]=1, [20]=1, [21]=1, [30]=1, [31]=1, [32]=1, [33]=1, [34]=1, [35]=1, [37]=1}}, + {channel= 56, text="Channel 56 (5.280 GHz)", region={[0]=1, [1]=1, [2]=1, [3]=1, [7]=1, [8]=1, [9]=1, [11]=1, [12]=1, [13]=1, [14]=1, [16]=1, [18]=1, [19]=1, [20]=1, [21]=1, [30]=1, [31]=1, [32]=1, [33]=1, [34]=1, [35]=1, [37]=1}}, + {channel= 60, text="Channel 60 (5.300 GHz)", region={[0]=1, [1]=1, [2]=1, [3]=1, [7]=1, [8]=1, [9]=1, [11]=1, [12]=1, [13]=1, [14]=1, [16]=1, [18]=1, [19]=1, [20]=1, [21]=1, [30]=1, [31]=1, [32]=1, [33]=1, [34]=1, [35]=1, [37]=1}}, + {channel= 64, text="Channel 64 (5.320 GHz)", region={[0]=1, [1]=1, [2]=1, [3]=1, [7]=1, [8]=1, [9]=1, [11]=1, [12]=1, [13]=1, [14]=1, [16]=1, [18]=1, [19]=1, [20]=1, [21]=1, [30]=1, [31]=1, [32]=1, [33]=1, [34]=1, [35]=1, [37]=1}}, + {channel=100, text="Channel 100 (5.500 GHz)", region={[1]=1, [7]=1, [9]=1, [11]=1, [12]=1, [13]=1, [14]=1, [18]=1, [19]=1, [20]=1, [21]=1, [22]=1, [30]=1, [31]=1, [32]=1, [33]=1, [36]=1}}, + {channel=104, text="Channel 104 (5.520 GHz)", region={[1]=1, [7]=1, [9]=1, [11]=1, [12]=1, [13]=1, [14]=1, [18]=1, [19]=1, [20]=1, [21]=1, [22]=1, [30]=1, [31]=1, [32]=1, [33]=1, [36]=1}}, + {channel=108, text="Channel 108 (5.540 GHz)", region={[1]=1, [7]=1, [9]=1, [11]=1, [12]=1, [13]=1, [14]=1, [18]=1, [19]=1, [20]=1, [21]=1, [22]=1, [30]=1, [31]=1, [32]=1, [33]=1, [36]=1}}, + {channel=112, text="Channel 112 (5.560 GHz)", region={[1]=1, [7]=1, [9]=1, [11]=1, [12]=1, [13]=1, [14]=1, [18]=1, [19]=1, [20]=1, [21]=1, [22]=1, [30]=1, [31]=1, [32]=1, [33]=1, [36]=1}}, + {channel=116, text="Channel 116 (5.580 GHz)", region={[1]=1, [7]=1, [9]=1, [11]=1, [12]=1, [13]=1, [14]=1, [18]=1, [19]=1, [20]=1, [21]=1, [22]=1, [30]=1, [31]=1, [32]=1, [33]=1, [36]=1}}, + {channel=120, text="Channel 120 (5.600 GHz)", region={[1]=1, [7]=1, [11]=1, [12]=1, [13]=1, [19]=1, [20]=1, [21]=1, [22]=1, [30]=1, [31]=1, [32]=1, [33]=1, [36]=1}}, + {channel=124, text="Channel 124 (5.620 GHz)", region={[1]=1, [7]=1, [12]=1, [13]=1, [19]=1, [20]=1, [21]=1, [22]=1, [30]=1, [31]=1, [32]=1, [33]=1, [36]=1}}, + {channel=128, text="Channel 128 (5.640 GHz)", region={[1]=1, [7]=1, [12]=1, [13]=1, [19]=1, [21]=1, [22]=1, [30]=1, [31]=1, [32]=1, [33]=1, [36]=1}}, + {channel=132, text="Channel 132 (5.660 GHz)", region={[1]=1, [7]=1, [9]=1, [12]=1, [13]=1, [14]=1, [18]=1, [19]=1, [21]=1, [22]=1, [30]=1, [31]=1, [32]=1, [33]=1, [36]=1}}, + {channel=136, text="Channel 136 (5.680 GHz)", region={[1]=1, [7]=1, [9]=1, [12]=1, [13]=1, [14]=1, [18]=1, [19]=1, [21]=1, [22]=1, [30]=1, [31]=1, [32]=1, [33]=1, [36]=1}}, + {channel=140, text="Channel 140 (5.700 GHz)", region={[1]=1, [7]=1, [9]=1, [12]=1, [13]=1, [14]=1, [18]=1, [19]=1, [21]=1, [22]=1, [30]=1, [31]=1, [32]=1, [33]=1, [36]=1}}, + {channel=144, text="Channel 144 (5.720 GHz)", region={[12]=1, [13]=1, [14]=1}}, + {channel=149, text="Channel 149 (5.745 GHz)", region={[0]=1, [3]=1, [4]=1, [5]=1, [7]=1, [9]=1, [10]=1, [11]=1, [13]=1, [14]=1, [15]=1, [16]=1, [17]=1, [19]=1, [20]=1, [21]=1, [30]=1, [31]=1, [32]=1, [34]=1, [36]=1, [37]=1}}, + {channel=153, text="Channel 153 (5.765 GHz)", region={[0]=1, [3]=1, [4]=1, [5]=1, [7]=1, [9]=1, [10]=1, [11]=1, [13]=1, [14]=1, [15]=1, [16]=1, [17]=1, [19]=1, [20]=1, [21]=1, [30]=1, [31]=1, [32]=1, [34]=1, [36]=1, [37]=1}}, + {channel=157, text="Channel 157 (5.785 GHz)", region={[0]=1, [3]=1, [4]=1, [5]=1, [7]=1, [9]=1, [10]=1, [11]=1, [13]=1, [14]=1, [15]=1, [16]=1, [17]=1, [19]=1, [20]=1, [21]=1, [30]=1, [31]=1, [32]=1, [34]=1, [36]=1, [37]=1}}, + {channel=161, text="Channel 161 (5.805 GHz)", region={[0]=1, [3]=1, [4]=1, [5]=1, [7]=1, [9]=1, [10]=1, [11]=1, [13]=1, [14]=1, [15]=1, [16]=1, [17]=1, [19]=1, [20]=1, [21]=1, [30]=1, [31]=1, [32]=1, [34]=1, [36]=1, [37]=1}}, + {channel=165, text="Channel 165 (5.825 GHz)", region={[0]=1, [4]=1, [7]=1, [9]=1, [10]=1, [13]=1, [14]=1, [15]=1, [16]=1, [30]=1, [31]=1, [34]=1, [36]=1, [37]=1}}, + {channel=169, text="Channel 169 (5.845 GHz)", region={[15]=1}}, + {channel=173, text="Channel 173 (5.865 GHz)", region={[15]=1, [37]=1}} +} + +mtkwifi.ChannelList_2G_All = { + {channel=0, text="Channel 0 (Auto )", region={}}, + {channel= 1, text="Channel 1 (2412 GHz)", region={[0]=1, [1]=1, [5]=1, [31]=1, [32]=1, [33]=1}}, + {channel= 2, text="Channel 2 (2417 GHz)", region={[0]=1, [1]=1, [5]=1, [31]=1, [32]=1, [33]=1}}, + {channel= 3, text="Channel 3 (2422 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [31]=1, [32]=1, [33]=1}}, + {channel= 4, text="Channel 4 (2427 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [31]=1, [32]=1, [33]=1}}, + {channel= 5, text="Channel 5 (2432 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1, [31]=1, [32]=1, [33]=1}}, + {channel= 6, text="Channel 6 (2437 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1, [31]=1, [32]=1, [33]=1}}, + {channel= 7, text="Channel 7 (2442 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1, [31]=1, [32]=1, [33]=1}}, + {channel= 8, text="Channel 8 (2447 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1, [31]=1, [32]=1, [33]=1}}, + {channel= 9, text="Channel 9 (2452 GHz)", region={[0]=1, [1]=1, [5]=1, [6]=1, [7]=1, [31]=1, [32]=1, [33]=1}}, + {channel=10, text="Channel 10 (2457 GHz)", region={[0]=1, [1]=1, [2]=1, [3]=1, [5]=1, [7]=1, [31]=1, [32]=1, [33]=1}}, + {channel=11, text="Channel 11 (2462 GHz)", region={[0]=1, [1]=1, [2]=1, [3]=1, [5]=1, [7]=1, [31]=1, [32]=1, [33]=1}}, + {channel=12, text="Channel 12 (2467 GHz)", region={[1]=1, [3]=1, [5]=1, [7]=1, [31]=1, [32]=1, [33]=1}}, + {channel=13, text="Channel 13 (2472 GHz)", region={[1]=1, [3]=1, [5]=1, [7]=1, [31]=1, [32]=1, [33]=1}}, + {channel=14, text="Channel 14 (2477 GHz)", region={[4]=1, [5]=1, [31]=1, [33]=1}} +} + +mtkwifi.ChannelList_5G_2nd_80MHZ_ALL = { + {channel=36, text="Ch36(5.180 GHz) - Ch48(5.240 GHz)", chidx=2}, + {channel=52, text="Ch52(5.260 GHz) - Ch64(5.320 GHz)", chidx=6}, + {channel=-1, text="Channel between 64 100", chidx=-1}, + {channel=100, text="Ch100(5.500 GHz) - Ch112(5.560 GHz)", chidx=10}, + {channel=112, text="Ch116(5.580 GHz) - Ch128(5.640 GHz)", chidx=14}, + {channel=-1, text="Channel between 128 132", chidx=-1}, + {channel=132, text="Ch132(5.660 GHz) - Ch144(5.720 GHz)", chidx=18}, + {channel=-1, text="Channel between 144 149", chidx=-1}, + {channel=149, text="Ch149(5.745 GHz) - Ch161(5.805 GHz)", chidx=22} +} + +local AuthModeList = { + "Disable", + "OPEN",--OPENWEP + "Enhanced Open", + "SHARED",--SHAREDWEP + "WEPAUTO", + "WPA2", + "WPA3", + "WPA3-192-bit", + "WPA2PSK", + "WPA3PSK", + "WPAPSKWPA2PSK", + "WPA2PSKWPA3PSK", + "WPA1WPA2", + "IEEE8021X" +} + +local AuthModeList_6G = { + "Enhanced Open", + "WPA3PSK" +} + +local WpsEnableAuthModeList = { + "Disable", + "OPEN",--OPENWEP + "WPA2PSK", + "WPAPSKWPA2PSK" +} + +local WpsEnableAuthModeList_6G = { + "Disable", + "WPA2PSK", + "WPAPSKWPA2PSK" +} + +local ApCliAuthModeList = { + "Disable", + "OPEN", + "SHARED", + "Enhanced Open", + "WPAPSK", + "WPA2PSK", + "WPA3PSK", + -- "WPAPSKWPA2PSK", + -- "WPA2PSKWPA3PSK", + -- "WPA", + -- "WPA2", + -- "WPAWPA2", + -- "8021X", +} + +local EncryptionTypeList = { + "WEP", + "TKIP", + "TKIPAES", + "AES", + "GCMP256" +} + +local EncryptionTypeList_6G = { + "WEP", + "AES", + "GCMP256" +} + +local dbdc_prefix = { + {"ra", "rax"}, + {"rai", "ray"}, + {"rae", "raz"} +} + +local dbdc_apcli_prefix = { + {"apcli", "apclix"}, + {"apclii", "apcliy"}, + {"apclie", "apcliz"} +} + +function mtkwifi.band(mode) + local i = tonumber(mode) + if i == 0 + or i == 1 + or i == 4 + or i == 6 + or i == 7 + or i == 9 + or i == 16 then + return "2.4G" + elseif i == 18 then + return "6G" + else + return "5G" + end +end + + +function mtkwifi.__cfg2list(str) + -- delimeter == ";" + local i = 1 + local list = {} + for k in string.gmatch(str, "([^;]+)") do + list[i] = k + i = i + 1 + end + return list +end + +function mtkwifi.token_set(str, n, v) + -- n start from 1 + -- delimeter == ";" + if not str then return end + local tmp = mtkwifi.__cfg2list(str) + if type(v) ~= type("") and type(v) ~= type(0) then + nixio.syslog("err", "invalid value type in token_set, "..type(v)) + return + end + if #tmp < tonumber(n) then + for i=#tmp, tonumber(n) do + if not tmp[i] then + tmp[i] = v -- pad holes with v ! + end + end + else + tmp[n] = v + end + return table.concat(tmp, ";"):gsub("^;*(.-);*$", "%1"):gsub(";+",";") +end + + +function mtkwifi.token_get(str, n, v) + -- n starts from 1 + -- v is the backup in case token n is nil + if not str then return v end + local tmp = mtkwifi.__cfg2list(str) + return tmp[tonumber(n)] or v +end + +function mtkwifi.search_dev_and_profile_orig() + local nixio = require("nixio") + local dir = io.popen("ls /etc/wireless/") + if not dir then return end + local result = {} + -- case 1: mt76xx.dat (best) + -- case 2: mt76xx.n.dat (multiple card of same dev) + -- case 3: mt76xx.n.nG.dat (case 2 plus dbdc and multi-profile, bloody hell....) + for line in dir:lines() do + -- nixio.syslog("debug", "scan "..line) + local tmp = io.popen("find /etc/wireless/"..line.." -type f -name \"*.dat\"") + for datfile in tmp:lines() do + -- nixio.syslog("debug", "test "..datfile) + + repeat do + -- for case 1 + local devname = string.match(datfile, "("..line..").dat") + if devname then + result[devname] = datfile + -- nixio.syslog("debug", "yes "..devname.."="..datfile) + break + end + -- for case 2 + local devname = string.match(datfile, "("..line.."%.%d)%.dat") + if devname then + result[devname] = datfile + -- nixio.syslog("debug", "yes "..devname.."="..datfile) + break + end + -- for case 3 + local devname = string.match(datfile, "("..line.."%.%d%.%dG)%.dat") + if devname then + result[devname] = datfile + -- nixio.syslog("debug", "yes "..devname.."="..datfile) + break + end + end until true + end + end + + for k,v in pairs(result) do + nixio.syslog("debug", "search_dev_and_profile_orig: "..k.."="..v) + end + + return result +end + +function mtkwifi.search_dev_and_profile_l1() + local l1dat = mtkwifi.__get_l1dat() + + if not l1dat then return end + + local nixio = require("nixio") + local result = {} + local dbdc_2nd_if = "" + + for k, dev in ipairs(l1dat) do + dbdc_2nd_if = mtkwifi.token_get(dev.main_ifname, 2, nil) + if dbdc_2nd_if then + result[dev["INDEX"].."."..dev["mainidx"]..".1"] = mtkwifi.token_get(dev.profile_path, 1, nil) + result[dev["INDEX"].."."..dev["mainidx"]..".2"] = mtkwifi.token_get(dev.profile_path, 2, nil) + else + result[dev["INDEX"].."."..dev["mainidx"]] = dev.profile_path + end + end + + for k,v in pairs(result) do + nixio.syslog("debug", "search_dev_and_profile_l1: "..k.."="..v) + end + + return result +end + +function mtkwifi.search_dev_and_profile() + return mtkwifi.search_dev_and_profile_l1() or mtkwifi.search_dev_and_profile_orig() +end + +function mtkwifi.__setup_vifs(cfgs, devname, mainidx, subidx) + local l1dat, l1 = mtkwifi.__get_l1dat() + local dridx = l1dat and l1.DEV_RINDEX + + local prefix + local main_ifname + local vifs = {} + local dev_idx = "" + + + prefix = l1dat and l1dat[dridx][devname].ext_ifname or dbdc_prefix[mainidx][subidx] + + dev_idx = string.match(devname, "(%w+)") + + vifs["__prefix"] = prefix + if (cfgs.BssidNum == nil) then + debug_write("BssidNum configuration value not found.") + nixio.syslog("debug","BssidNum configuration value not found.") + return + end + + for j=1,tonumber(cfgs.BssidNum) do + vifs[j] = {} + vifs[j].vifidx = j -- start from 1 + dev_idx = string.match(devname, "(%w+)") + main_ifname = l1dat and l1dat[dridx][devname].main_ifname or dbdc_prefix[mainidx][subidx].."0" + vifs[j].vifname = j == 1 and main_ifname or prefix..(j-1) + if mtkwifi.exists("/sys/class/net/"..vifs[j].vifname) then + local flags = tonumber(mtkwifi.read_pipe("cat /sys/class/net/"..vifs[j].vifname.."/flags 2>/dev/null")) or 0 + vifs[j].state = flags%2 == 1 and "up" or "down" + end + vifs[j].__ssid = cfgs["SSID"..j] + local rd_pipe_output = mtkwifi.read_pipe("cat /sys/class/net/"..prefix..(j-1).."/address 2>/dev/null") + vifs[j].__bssid = rd_pipe_output and string.match(rd_pipe_output, "%x%x:%x%x:%x%x:%x%x:%x%x:%x%x") or "?" + + vifs[j].__temp_ssid = mtkwifi.__trim(mtkwifi.read_pipe("iwconfig "..vifs[j].vifname.." | grep ESSID | cut -d : -f 2")) + vifs[j].__temp_channel = mtkwifi.read_pipe("iwconfig "..vifs[j].vifname.." | grep Channel | cut -d = -f 2 | cut -d \" \" -f 1") + if string.gsub(vifs[j].__temp_channel, "^%s*(.-)%s*$", "%1") == "" then + vifs[j].__temp_channel = mtkwifi.read_pipe("iwconfig "..vifs[j].vifname.." | grep Channel | cut -d : -f 3 | cut -d \" \" -f 1") + end + vifs[j].__wirelessmode_table = c_getWMode(vifs[j].vifname) + vifs[j].__temp_wirelessmode = vifs[j].__wirelessmode_table['getwmode'] + + if (vifs[j].__temp_ssid ~= "") then + vifs[j].__ssid = vifs[j].__temp_ssid:gsub("^\"(.-)\"$","%1") + else + vifs[j].__ssid = cfgs["SSID"..j] + end + + if (vifs[j].__temp_channel ~= "" ) then + vifs[j].__channel = vifs[j].__temp_channel + else + vifs[j].__channel = cfgs.Channel + end + + if (vifs[j].__temp_wirelessmode ~= "" and vifs[j].__temp_wirelessmode ~= "0") then + vifs[j].__wirelessmode = vifs[j].__temp_wirelessmode + else + vifs[j].__wirelessmode = mtkwifi.token_get(cfgs.WirelessMode, j, 0) + end + + vifs[j].__authmode = mtkwifi.token_get(cfgs.AuthMode, j, mtkwifi.__split(cfgs.AuthMode,";")[1]) + vifs[j].__encrypttype = mtkwifi.token_get(cfgs.EncrypType, j, mtkwifi.__split(cfgs.EncrypType,";")[1]) + vifs[j].__hidessid = mtkwifi.token_get(cfgs.HideSSID, j, mtkwifi.__split(cfgs.HideSSID,";")[1]) + vifs[j].__noforwarding = mtkwifi.token_get(cfgs.NoForwarding, j, mtkwifi.__split(cfgs.NoForwarding,";")[1]) + vifs[j].__wmmcapable = mtkwifi.token_get(cfgs.WmmCapable, j, mtkwifi.__split(cfgs.WmmCapable,";")[1]) + vifs[j].__txrate = mtkwifi.token_get(cfgs.TxRate, j, mtkwifi.__split(cfgs.TxRate,";")[1]) + vifs[j].__ieee8021x = mtkwifi.token_get(cfgs.IEEE8021X, j, mtkwifi.__split(cfgs.IEEE8021X,";")[1]) + vifs[j].__preauth = mtkwifi.token_get(cfgs.PreAuth, j, mtkwifi.__split(cfgs.PreAuth,";")[1]) + vifs[j].__rekeymethod = mtkwifi.token_get(cfgs.RekeyMethod, j, mtkwifi.__split(cfgs.RekeyMethod,";")[1]) + vifs[j].__rekeyinterval = mtkwifi.token_get(cfgs.RekeyInterval, j, mtkwifi.__split(cfgs.RekeyInterval,";")[1]) + vifs[j].__pmkcacheperiod = mtkwifi.token_get(cfgs.PMKCachePeriod, j, mtkwifi.__split(cfgs.PMKCachePeriod,";")[1]) + vifs[j].__ht_extcha = mtkwifi.token_get(cfgs.HT_EXTCHA, j, mtkwifi.__split(cfgs.HT_EXTCHA,";")[1]) + vifs[j].__radius_server = mtkwifi.token_get(cfgs.RADIUS_Server, j, mtkwifi.__split(cfgs.RADIUS_Server,";")[1]) + vifs[j].__radius_port = mtkwifi.token_get(cfgs.RADIUS_Port, j, mtkwifi.__split(cfgs.RADIUS_Port,";")[1]) + vifs[j].__wepkey_id = mtkwifi.token_get(cfgs.DefaultKeyID, j, mtkwifi.__split(cfgs.DefaultKeyID,";")[1]) + vifs[j].__wscconfmode = mtkwifi.token_get(cfgs.WscConfMode, j, mtkwifi.__split(cfgs.WscConfMode,";")[1]) + vifs[j].__wepkeys = { + cfgs["Key1Str"..j], + cfgs["Key2Str"..j], + cfgs["Key3Str"..j], + cfgs["Key4Str"..j], + } + vifs[j].__wpapsk = cfgs["WPAPSK"..j] + vifs[j].__ht_stbc = mtkwifi.token_get(cfgs.HT_STBC, j, mtkwifi.__split(cfgs.HT_STBC,";")[1]) + vifs[j].__ht_ldpc = mtkwifi.token_get(cfgs.HT_LDPC, j, mtkwifi.__split(cfgs.HT_LDPC,";")[1]) + vifs[j].__vht_stbc = mtkwifi.token_get(cfgs.VHT_STBC, j, mtkwifi.__split(cfgs.VHT_STBC,";")[1]) + vifs[j].__vht_ldpc = mtkwifi.token_get(cfgs.VHT_LDPC, j, mtkwifi.__split(cfgs.VHT_LDPC,";")[1]) + vifs[j].__dls_capable = mtkwifi.token_get(cfgs.DLSCapable, j, mtkwifi.__split(cfgs.DLSCapable,";")[1]) + vifs[j].__apsd_capable = mtkwifi.token_get(cfgs.APSDCapable, j, mtkwifi.__split(cfgs.APSDCapable,";")[1]) + vifs[j].__frag_threshold = mtkwifi.token_get(cfgs.FragThreshold, j, mtkwifi.__split(cfgs.FragThreshold,";")[1]) + vifs[j].__rts_threshold = mtkwifi.token_get(cfgs.RTSThreshold, j, mtkwifi.__split(cfgs.RTSThreshold,";")[1]) + vifs[j].__vht_sgi = mtkwifi.token_get(cfgs.VHT_SGI, j, mtkwifi.__split(cfgs.VHT_SGI,";")[1]) + vifs[j].__vht_bw_signal = mtkwifi.token_get(cfgs.VHT_BW_SIGNAL, j, mtkwifi.__split(cfgs.VHT_BW_SIGNAL,";")[1]) + vifs[j].__ht_protect = mtkwifi.token_get(cfgs.HT_PROTECT, j, mtkwifi.__split(cfgs.HT_PROTECT,";")[1]) + vifs[j].__ht_gi = mtkwifi.token_get(cfgs.HT_GI, j, mtkwifi.__split(cfgs.HT_GI,";")[1]) + vifs[j].__ht_opmode = mtkwifi.token_get(cfgs.HT_OpMode, j, mtkwifi.__split(cfgs.HT_OpMode,";")[1]) + vifs[j].__ht_amsdu = mtkwifi.token_get(cfgs.HT_AMSDU, j, mtkwifi.__split(cfgs.HT_AMSDU,";")[1]) + vifs[j].__ht_autoba = mtkwifi.token_get(cfgs.HT_AutoBA, j, mtkwifi.__split(cfgs.HT_AutoBA,";")[1]) + vifs[j].__igmp_snenable = mtkwifi.token_get(cfgs.IgmpSnEnable, j, mtkwifi.__split(cfgs.IgmpSnEnable,";")[1]) + vifs[j].__wdsenable = mtkwifi.token_get(cfgs.WdsEnable, j, mtkwifi.__split(cfgs.WdsEnable,";")[1]) + + -- VoW + vifs[j].__atc_tp = mtkwifi.token_get(cfgs.VOW_Rate_Ctrl_En, j, mtkwifi.__split(cfgs.VOW_Rate_Ctrl_En,";")[1]) + vifs[j].__atc_min_tp = mtkwifi.token_get(cfgs.VOW_Group_Min_Rate, j, mtkwifi.__split(cfgs.VOW_Group_Min_Rate,";")[1]) + vifs[j].__atc_max_tp = mtkwifi.token_get(cfgs.VOW_Group_Max_Rate, j, mtkwifi.__split(cfgs.VOW_Group_Max_Rate,";")[1]) + vifs[j].__atc_at = mtkwifi.token_get(cfgs.VOW_Airtime_Ctrl_En, j, mtkwifi.__split(cfgs.VOW_Airtime_Ctrl_En,";")[1]) + vifs[j].__atc_min_at = mtkwifi.token_get(cfgs.VOW_Group_Min_Ratio, j, mtkwifi.__split(cfgs.VOW_Group_Min_Ratio,";")[1]) + vifs[j].__atc_max_at = mtkwifi.token_get(cfgs.VOW_Group_Max_Ratio, j, mtkwifi.__split(cfgs.VOW_Group_Max_Ratio,";")[1]) + + -- TODO index by vifname + vifs[vifs[j].vifname] = vifs[j] + + -- OFDMA and MU-MIMO + vifs[j].__muofdma_dlenable = mtkwifi.token_get(cfgs.MuOfdmaDlEnable, j, mtkwifi.__split(cfgs.MuOfdmaDlEnable,";")[1]) + vifs[j].__muofdma_ulenable = mtkwifi.token_get(cfgs.MuOfdmaUlEnable, j, mtkwifi.__split(cfgs.MuOfdmaUlEnable,";")[1]) + vifs[j].__mumimo_dlenable = mtkwifi.token_get(cfgs.MuMimoDlEnable, j, mtkwifi.__split(cfgs.MuMimoDlEnable,";")[1]) + vifs[j].__mumimo_ulenable = mtkwifi.token_get(cfgs.MuMimoUlEnable, j, mtkwifi.__split(cfgs.MuMimoUlEnable,";")[1]) + + end + + return vifs +end + +function mtkwifi.__setup_apcli(cfgs, devname, mainidx, subidx) + local l1dat, l1 = mtkwifi.__get_l1dat() + local dridx = l1dat and l1.DEV_RINDEX + + local apcli = {} + local dev_idx = string.match(devname, "(%w+)") + local apcli_prefix = l1dat and l1dat[dridx][devname].apcli_ifname or + dbdc_apcli_prefix[mainidx][subidx] + + local apcli_name = apcli_prefix.."0" + + if mtkwifi.exists("/sys/class/net/"..apcli_name) then + apcli.vifname = apcli_name + apcli.devname = apcli_name + apcli.vifidx = "1" + local rd_pipe_output = mtkwifi.read_pipe("iwconfig "..apcli_name.." | grep ESSID 2>/dev/null") + local ssid = rd_pipe_output and string.match(rd_pipe_output, "ESSID:\"(.*)\"") + if not ssid or ssid == "" then + apcli.status = "Disconnected" + else + apcli.ssid = ssid + apcli.status = "Connected" + end + local flags = tonumber(mtkwifi.read_pipe("cat /sys/class/net/"..apcli_name.."/flags 2>/dev/null")) or 0 + apcli.state = flags%2 == 1 and "up" or "down" + rd_pipe_output = mtkwifi.read_pipe("cat /sys/class/net/"..apcli_name.."/address 2>/dev/null") + apcli.mac_addr = rd_pipe_output and string.match(rd_pipe_output, "%x%x:%x%x:%x%x:%x%x:%x%x:%x%x") or "?" + rd_pipe_output = mtkwifi.read_pipe("iwconfig "..apcli_name.." | grep 'Access Point' 2>/dev/null") + apcli.bssid = rd_pipe_output and string.match(rd_pipe_output, "%x%x:%x%x:%x%x:%x%x:%x%x:%x%x") or "Not-Associated" + return apcli + else + return + end +end + +function mtkwifi.__setup_eths() + local etherInfo = {} + local all_eth_devs = mtkwifi.read_pipe("ls /sys/class/net/ | grep eth | grep -v grep") + if not all_eth_devs or all_eth_devs == "" then + return + end + for ethName in string.gmatch(all_eth_devs, "(eth%d)") do + local ethInfo = {} + ethInfo['ifname'] = ethName + local flags = tonumber(mtkwifi.read_pipe("cat /sys/class/net/"..ethName.."/flags 2>/dev/null")) or 0 + ethInfo['state'] = flags%2 == 1 and "up" or "down" + ethInfo['mac_addr'] = mtkwifi.read_pipe("cat /sys/class/net/"..ethName.."/address 2>/dev/null") or "?" + table.insert(etherInfo,ethInfo) + end + return etherInfo +end + +function mtkwifi.__is_6890_project() + local str = mtkwifi.read_pipe("cat /etc/vendor_info | grep \"PLATFORM=\"") + str = string.gsub(str, "PLATFORM=", "") + if str:find("6890") then + return true + end + return false +end + +function mtkwifi.get_all_devs() + local nixio = require("nixio") + local devs = {} + local i = 1 -- dev idx + local profiles = mtkwifi.search_dev_and_profile() + local wpa_support = 0 + local wapi_support = 0 + + for devname,profile in mtkwifi.__spairs(profiles, function(a,b) return string.upper(a) < string.upper(b) end) do + local fd = io.open(profile,"r") + if not fd then + nixio.syslog("debug", "cannot find "..profile) + else + fd:close() + local cfgs = mtkwifi.load_profile(profile) + if not cfgs then + debug_write("error loading profile"..profile) + nixio.syslog("err", "error loading "..profile) + return + end + devs[i] = {} + devs[i].vifs = {} + devs[i].apcli = {} + devs[i].devname = devname + devs[i].profile = profile + local tmp = "" + tmp = string.split(devname, ".") + devs[i].maindev = tmp[1] + devs[i].mainidx = tonumber(tmp[2]) or 1 + devs[i].subdev = devname + devs[i].subidx = string.match(tmp[3] or "", "(%d+)")=="2" and 2 or 1 + devs[i].devband = tonumber(tmp[3]) + if devs[i].devband then + devs[i].multiprofile = true + devs[i].dbdc = true + devs[i].dbdcBandName = (profile:match("2[gG]") and "2.4G") or (profile:match("5[gG]") and "5G") + if not devs[i].dbdcBandName then + -- Make 1st band as 2.4G and 2nd band as 5G. + devs[i].dbdcBandName = (devs[i].devband == 1) and "2.4G" or "5G" + end + end + + devs[i].ApCliEnable = cfgs.ApCliEnable + devs[i].WirelessMode = string.split(cfgs.WirelessMode,";")[1] + devs[i].WirelessModeList = {} + for key, value in pairs(DevicePropertyMap) do + local found = string.find(string.upper(devname), string.upper(value.device)) + if found then + for k=1,#value.band do + devs[i].WirelessModeList[tonumber(value.band[k])] = WirelessModeList[tonumber(value.band[k])] + end + + if mtkwifi.__is_6890_project() then + if devs[i].dbdc then + nixio.syslog("debug", "6890 MiFi, change maxVif to 4") + devs[i].maxVif = 4 + else + nixio.syslog("debug", "6890 CPE, change maxVif to 8") + devs[i].maxVif = 8 + end + elseif devs[i].dbdc == true then + devs[i].maxVif = value.maxDBDCVif or value.maxVif/2 + else + devs[i].maxVif = value.maxVif or 16 + end + + devs[i].maxTxStream = value.maxTxStream + devs[i].maxRxStream = value.maxRxStream + devs[i].invalidChBwList = value.invalidChBwList + devs[i].isPowerBoostSupported = value.isPowerBoostSupported + devs[i].wdsBand = value.wdsBand + devs[i].mimoBand = value.mimoBand + devs[i].isMultiAPSupported = value.isMultiAPSupported + devs[i].isWPA3_192bitSupported = value.isWPA3_192bitSupported + end + end + devs[i].WscConfMode = cfgs.WscConfMode + devs[i].AuthModeList = AuthModeList + devs[i].AuthModeList_6G = AuthModeList_6G + devs[i].WpsEnableAuthModeList = WpsEnableAuthModeList + devs[i].WpsEnableAuthModeList_6G = WpsEnableAuthModeList_6G + + if wpa_support == 1 then + table.insert(devs[i].AuthModeList,"WPAPSK") + table.insert(devs[i].AuthModeList,"WPA") + end + + if wapi_support == 1 then + table.insert(devs[i].AuthModeList,"WAIPSK") + table.insert(devs[i].AuthModeList,"WAICERT") + end + devs[i].ApCliAuthModeList = ApCliAuthModeList + devs[i].EncryptionTypeList = EncryptionTypeList + devs[i].EncryptionTypeList_6G = EncryptionTypeList_6G + devs[i].Channel = tonumber(cfgs.Channel) + devs[i].DBDC_MODE = tonumber(cfgs.DBDC_MODE) + devs[i].band = devs[i].devband or mtkwifi.band(string.split(cfgs.WirelessMode,";")[1]) + + if cfgs.MUTxRxEnable then + if tonumber(cfgs.ETxBfEnCond)==1 + and tonumber(cfgs.MUTxRxEnable)==0 + and tonumber(cfgs.ITxBfEn)==0 + then devs[i].__mimo = 0 + elseif tonumber(cfgs.ETxBfEnCond)==0 + and tonumber(cfgs.MUTxRxEnable)==0 + and tonumber(cfgs.ITxBfEn)==1 + then devs[i].__mimo = 1 + elseif tonumber(cfgs.ETxBfEnCond)==1 + and tonumber(cfgs.MUTxRxEnable)==0 + and tonumber(cfgs.ITxBfEn)==1 + then devs[i].__mimo = 2 + elseif tonumber(cfgs.ETxBfEnCond)==1 + and tonumber(cfgs.MUTxRxEnable)>0 + and tonumber(cfgs.ITxBfEn)==0 + then devs[i].__mimo = 3 + elseif tonumber(cfgs.ETxBfEnCond)==1 + and tonumber(cfgs.MUTxRxEnable)>0 + and tonumber(cfgs.ITxBfEn)==1 + then devs[i].__mimo = 4 + else devs[i].__mimo = 5 + end + end + + if cfgs.HT_BW == "0" or not cfgs.HT_BW then + devs[i].__bw = "20" + elseif cfgs.HT_BW == "1" and cfgs.VHT_BW == "0" or not cfgs.VHT_BW then + if cfgs.HT_BSSCoexistence == "0" or not cfgs.HT_BSSCoexistence then + devs[i].__bw = "40" + else + devs[i].__bw = "60" -- 20/40 coexist + end + elseif cfgs.HT_BW == "1" and cfgs.VHT_BW == "1" then + devs[i].__bw = "80" + elseif cfgs.HT_BW == "1" and cfgs.VHT_BW == "2" then + devs[i].__bw = "160" + elseif cfgs.HT_BW == "1" and cfgs.VHT_BW == "3" then + devs[i].__bw = "161" + end + + devs[i].vifs = mtkwifi.__setup_vifs(cfgs, devname, devs[i].mainidx, devs[i].subidx) + devs[i].apcli = mtkwifi.__setup_apcli(cfgs, devname, devs[i].mainidx, devs[i].subidx) + + if mtkwifi.exists("cat /etc/wireless/"..devs[i].maindev.."/version") then + local version = mtkwifi.read_pipe("cat /etc/wireless/"..devs[i].maindev.."/version 2>/dev/null") + devs[i].version = (type(version) == "string" and version ~= "") and version or "Unknown: Empty version file!" + else + local vif_name = nil + if devs[i].apcli and devs[i].apcli["state"] == "up" then + vif_name = devs[i].apcli["vifname"] + elseif devs[i].vifs then + for _,vif in ipairs(devs[i].vifs) do + if vif["state"] == "up" then + vif_name = vif["vifname"] + break + end + end + end + if not vif_name then + if tonumber(cfgs.BssidNum) >= 1 then + devs[i].version = "Enable an interface to get the driver version." + elseif devs[i].apcli and devs[i].apcli["state"] ~= "up" then + devs[i].version = "Enable ApCli interface i.e. "..devs[i].apcli["vifname"].." to get the driver version." + else + devs[i].version = "Add an interface to get the driver version." + end + else + local version = mtkwifi.read_pipe("iwpriv "..vif_name.." get_driverinfo") + version = version and version:match("Driver version: (.-)\n") or "" + devs[i].version = version ~= "" and version or "Unknown: Incorrect response from version command!" + end + end + + -- Setup reverse indices by devname + devs[devname] = devs[i] + + if devs[i].apcli then + devs[i][devs[i].apcli.devname] = devs[i].apcli + end + + i = i + 1 + end + end + devs['etherInfo'] = mtkwifi.__setup_eths() + return devs +end + +function mtkwifi.exists(path) + local fp = io.open(path, "rb") + if fp then fp:close() end + return fp ~= nil +end + +function mtkwifi.parse_mac(str) + local macs = {} + local pat = "^[0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]$" + + local function ismac(str) + if str:match(pat) then return str end + end + + if not str then return macs end + local t = str:split("\n") + for _,v in pairs(t) do + local mac = ismac(mtkwifi.__trim(v)) + if mac then + table.insert(macs, mac) + end + end + + return macs + -- body +end + + +function mtkwifi.scan_ap(vifname) + os.execute("iwpriv "..vifname.." set SiteSurvey=0") + os.execute("sleep 10") -- depends on your env + local op = c_scanResult(vifname, 0) + local scan_result = op["scanresult"] + local next_line_index = 0 + local cur_index + local total_index = 0 + local ap_list = {} + local xx = {} + local tmp + + while (1) do + for i, line in ipairs(mtkwifi.__lines(scan_result)) do + local is_mac_addr_present = string.match(line, "%s+%x%x:%x%x:%x%x:%x%x:%x%x:%x%x%s+") + -- If the line does not contain any MAC address and length is greater than 40 bytes, + -- then, the line is the header of the get_site_survey page. + local total_str = string.find(line, "Total=") + if total_str == 1 then + total_index = tonumber(line:match("%d+")) + end + + if #line>40 and not is_mac_addr_present then + xx.Ch = {string.find(line, "Ch "),3} + xx.SSID = {string.find(line, "SSID "),32} + local fidx = string.find(line, "SSID_Len") + if fidx then + xx.SSID_len = {fidx,2} + end + xx.BSSID = {string.find(line, "BSSID "),17} + xx.Security = {string.find(line, "Security "),22} + xx.Signal = {string.find(line, "Sig%a%al"),4} + xx.Mode = {string.find(line, "W-Mode"),5} + xx.ExtCh = {string.find(line, "ExtCH"),6} + xx.WPS = {string.find(line, "WPS"),3} + xx.NT = {string.find(line, "NT"),2} + fidx = string.find(line, "OWETranIe") + if fidx then + xx.OWETranIe = {fidx,9} + end + end + + if #line>40 and is_mac_addr_present then + tmp = {} + tmp.channel = mtkwifi.__trim(string.sub(line, xx.Ch[1], xx.Ch[1]+xx.Ch[2])) + if xx.SSID_len then + -- Maximum xx.SSID[2] characters are supported in SSID + tmp.ssid_len = tonumber(mtkwifi.__trim(string.sub(line, xx.SSID_len[1], xx.SSID_len[1]+xx.SSID_len[2]))) or xx.SSID[2] + if tmp.ssid_len > xx.SSID[2] or tmp.ssid_len < 0 then + tmp.ssid_len = xx.SSID[2] + tmp.ssid = string.sub(line, xx.SSID[1], xx.SSID[1]+tmp.ssid_len-1) + else + tmp.ssid = string.sub(line, xx.SSID[1], xx.BSSID[1]-1) + if string.find(tmp.ssid, "0x") == nil then + tmp.ssid = string.sub(line, xx.SSID[1], xx.SSID[1]+tmp.ssid_len-1) + end + end + else + tmp.ssid = mtkwifi.__trim(string.sub(line, xx.SSID[1], xx.SSID[1]+xx.SSID[2])) + tmp.ssid_len = tmp.ssid:len() + end + tmp.bssid = string.upper(mtkwifi.__trim(string.sub(line, xx.BSSID[1], xx.BSSID[1]+xx.BSSID[2]))) + tmp.security = mtkwifi.__trim(string.sub(line, xx.Security[1], xx.Security[1]+xx.Security[2])) + tmp.authmode = mtkwifi.__trim(string.split(tmp.security, "/")[1]) + tmp.encrypttype = mtkwifi.__trim(string.split(tmp.security, "/")[2] or "NONE") + tmp.rssi = mtkwifi.__trim(string.sub(line, xx.Signal[1], xx.Signal[1]+xx.Signal[2])) + tmp.extch = mtkwifi.__trim(string.sub(line, xx.ExtCh[1], xx.ExtCh[1]+xx.ExtCh[2])) + tmp.mode = mtkwifi.__trim(string.sub(line, xx.Mode[1], xx.Mode[1]+xx.Mode[2])) + tmp.wps = mtkwifi.__trim(string.sub(line, xx.WPS[1], xx.WPS[1]+xx.WPS[2])) + tmp.nt = mtkwifi.__trim(string.sub(line, xx.NT[1], xx.NT[1]+xx.NT[2])) + if xx.OWETranIe then + tmp.OWETranIe = mtkwifi.__trim(string.sub(line, xx.OWETranIe[1], xx.OWETranIe[1]+xx.OWETranIe[2])) + end + table.insert(ap_list, tmp) + cur_index = tonumber(line:match("^%d+")) + if cur_index == total_index - 1 then + break; + end + next_line_index = cur_index and cur_index + 1 or next_line_index + end + end + if cur_index and cur_index == next_line_index - 1 then + --scan_result = mtkwifi.read_pipe("iwpriv "..vifname.." get_site_survey "..next_line_index) + if next_line_index == total_index - 1 then + scan_result = nil + else + op = c_scanResult(vifname, next_line_index) + scan_result = op["scanresult"] + end + else + scan_result = nil + end + + if not scan_result or not string.match(scan_result, "%s+%x%x:%x%x:%x%x:%x%x:%x%x:%x%x%s+") then + break + end + end + + return ap_list +end + +function mtkwifi.__any_wsc_enabled(wsc_conf_mode) + if (wsc_conf_mode == "") then + return 0; + end + if (wsc_conf_mode == "7") then + return 1; + end + if (wsc_conf_mode == "4") then + return 1; + end + if (wsc_conf_mode == "2") then + return 1; + end + if (wsc_conf_mode == "1") then + return 1; + end + return 0; +end + +function mtkwifi.__restart_if_wps(devname, ifname, cfgs) + local devs = mtkwifi.get_all_devs() + local ssid_index = devs[devname]["vifs"][ifname].vifidx + local wsc_conf_mode = "" + + wsc_conf_mode=mtkwifi.token_get(cfgs["WscConfMode"], ssid_index, "") + + os.execute("iwpriv "..ifname.." set WscConfMode=0") + debug_write("iwpriv "..ifname.." set WscConfMode=0") + os.execute("route delete 239.255.255.250") + debug_write("route delete 239.255.255.250") + if(mtkwifi.__any_wsc_enabled(wsc_conf_mode)) then + os.execute("iwpriv "..ifname.." set WscConfMode=7") + debug_write("iwpriv "..ifname.." set WscConfMode=7") + os.execute("route add -host 239.255.255.250 dev br0") + debug_write("route add -host 239.255.255.250 dev br0") + end + + -- execute wps_action.lua file to send signal for current interface + os.execute("lua wps_action.lua "..ifname) + debug_write("lua wps_action.lua "..ifname) + return cfgs +end + +function mtkwifi.restart_8021x(devname, devices) + local l1dat, l1 = mtkwifi.__get_l1dat() + local dridx = l1dat and l1.DEV_RINDEX + + local devs = devices or mtkwifi.get_all_devs() + local dev = devs[devname] + local main_ifname = l1dat and l1dat[dridx][devname].main_ifname or dbdc_prefix[mainidx][subidx].."0" + local prefix = l1dat and l1dat[dridx][devname].ext_ifname or dbdc_prefix[mainidx][subidx] + + local ps_cmd = "ps | grep -v grep | grep rt2860apd | grep "..main_ifname.." | awk '{print $1}'" + local pid_cmd = "cat /var/run/rt2860apd_"..devs[devname].vifs[1].vifname..".pid" + local apd_pid = mtkwifi.read_pipe(pid_cmd) or mtkwifi.read_pipe(ps_cmd) + if tonumber(apd_pid) then + os.execute("kill "..apd_pid) + end + + local cfgs = mtkwifi.load_profile(devs[devname].profile) + local auth_mode = cfgs['AuthMode'] + local ieee8021x = cfgs['IEEE8021X'] + local pat_auth_mode = {"WPA$", "WPA;", "WPA2$", "WPA2;", "WPA1WPA2$", "WPA1WPA2;"} + local pat_ieee8021x = {"1$", "1;"} + local apd_en = false + + for _, pat in ipairs(pat_auth_mode) do + if string.find(auth_mode, pat) then + apd_en = true + end + end + + for _, pat in ipairs(pat_ieee8021x) do + if string.find(ieee8021x, pat) then + apd_en = true + end + end + + if not apd_en then + return + end + if prefix == "ra" then + mtkwifi.__fork_exec("rt2860apd -i "..main_ifname.." -p "..prefix) + elseif prefix == "rae" then + mtkwifi.__fork_exec("rtwifi3apd -i "..main_ifname.." -p "..prefix) + elseif prefix == "rai" then + mtkwifi.__fork_exec("rtinicapd -i "..main_ifname.." -p "..prefix) + elseif prefix == "rax" or prefix == "ray" or prefix == "raz" then + mtkwifi.__fork_exec("rt2860apd_x -i "..main_ifname.." -p "..prefix) + end +end + +function mtkwifi.dat2uci(datfile, ucifile) + local shuci = require("shuci") + local cfgs = mtkwifi.load_profile(datfile) + + local uci = {} + + uci["wifi-device"]={} + uci["wifi-device"][".name"] = device + uci["wifi-device"]["type"] = device + uci["wifi-device"]["vendor"] = "ralink" + uci["wifi-device"]["iface"] = {} + + local i = 1 -- index of wifi-iface + + uci["iface"] = {} + while i <= tonumber(cfgs.BssidNum) do + uci["iface"][i] = {} + local iface = uci["iface"][i] + iface["ssid"] = cfgs["SSID"..(i)] + iface["mode"] = "ap" + iface["network"] = "lan" + iface["ifname"] = "ra0" + iface[".name"] = device.."."..iface["ifname"] + + i=i+1 + end + + shuci.encode(uci, ucifile) +end + +function mtkwifi.uci2dat(ucifile, devname, datfile) + local shuci = require("shuci") + local uci = shuci.decode(ucifile) + local cfgs = mtkwifi.load_profile(datfile) or {} + + if not ucifile or not devname then return end + + for _,dev in ipairs(uci["wifi-device"][devname]) do + for k,v in pairs(dev) do + if string.byte(k) ~= string.byte(".") + and string.byte(k) ~= string.byte("_") then + cfgs.k = v + end + end + end + if datfile then + save_profile(cfgs, datfile) + end +end + +function mtkwifi.get_referer_url() + local to_url + local script_name = luci.http.getenv('SCRIPT_NAME') + local http_referer = luci.http.getenv('HTTP_REFERER') + if script_name and http_referer then + local fIdx = http_referer:find(script_name,1,true) + if fIdx then + to_url = http_referer:sub(fIdx) + end + end + if not to_url or to_url == "" then + to_url = luci.dispatcher.build_url("admin", "mtk", "wifi") + end + return to_url +end + +function mtkwifi.save_read_easymesh_profile(easymesh_cfgs) + if not easymesh_cfgs then + return + end + local easymesh_applied_path = mtkwifi.__profile_applied_settings_path(mtkwifi.__read_easymesh_profile_path()) + if not mtkwifi.exists(easymesh_applied_path) then + os.execute("cp -f "..mtkwifi.__read_easymesh_profile_path().." "..easymesh_applied_path) + end + + local fd = io.open(mtkwifi.__read_easymesh_profile_path(), "w") + if not fd then return end + table.sort(easymesh_cfgs, function(a,b) return avalue:"..v..",") + end + fd:close() + + mtkwifi.save_easymesh_profile_to_nvram() + os.execute("sync >/dev/null 2>&1") +end + +function mtkwifi.save_easymesh_profile_to_nvram() + if not pcall(require, "mtknvram") then + return + end + local nvram = require("mtknvram") + local merged_easymesh_dev1_path = "/tmp/mtk/wifi/merged_easymesh_dev1.dat" + local l1dat, l1 = mtkwifi.__get_l1dat() + local dev1_profile_paths + local dev1_profile_path_table = l1 and l1.l1_zone_to_path("dev1") + if not next(dev1_profile_path_table) then + return + end + dev1_profile_paths = table.concat(dev1_profile_path_table, " ") + -- Uncomment below two statements when there is sufficient space in dev1 NVRAM zone to store EasyMesh Agent's BSS Cfgs Settings. + -- mtkwifi.__prepare_easymesh_bss_nvram_cfgs() + -- os.execute("cat "..dev1_profile_paths.." "..mtkwifi.__read_easymesh_profile_path().." "..mtkwifi.__easymesh_bss_cfgs_nvram_path().." > "..merged_easymesh_dev1_path.." 2>/dev/null") + -- Comment or remove below line once above requirement is met. + os.execute("cat "..dev1_profile_paths.." "..mtkwifi.__read_easymesh_profile_path().." > "..merged_easymesh_dev1_path.." 2>/dev/null") + nvram.nvram_save_profile(merged_easymesh_dev1_path, "dev1") + os.execute("sync >/dev/null 2>&1") +end + +function mtkwifi.save_easymesh_mapd_profile(easymesh_mapd_cfgs) + if not easymesh_mapd_cfgs then + return + end + local fd = io.open(mtkwifi.__easymesh_mapd_profile_path(), "w") + if not fd then return end + table.sort(easymesh_mapd_cfgs, function(a,b) return a/dev/null 2>&1") +end + +function mtkwifi.save_write_easymesh_profile(easymesh_mapd_cfgs) + if not easymesh_mapd_cfgs then + return + end + local fd = io.open(mtkwifi.__write_easymesh_profile_path(), "w") + if not fd then return end + table.sort(easymesh_mapd_cfgs, function(a,b) return a/dev/null 2>&1") +end + +function mtkwifi.__read_easymesh_profile_path() + return "/etc/map/mapd_cfg" +end + +function mtkwifi.__write_easymesh_profile_path() + return "/etc/map/mapd_user.cfg" +end + +function mtkwifi.__easymesh_mapd_profile_path() + return "/etc/mapd_strng.conf" +end + +function mtkwifi.__easymesh_bss_cfgs_path() + return "/etc/map/wts_bss_info_config" +end + +function mtkwifi.__easymesh_bss_cfgs_nvram_path() + local p = "/tmp/mtk/wifi/wts_bss_info_config.nvram" + os.execute("mkdir -p /tmp/mtk/wifi") + return p +end + +function mtkwifi.get_easymesh_al_mac(devRole) + local r = {} + local mapd_app_cfgs = mtkwifi.load_profile("/etc/map/1905d.cfg") + if not mapd_app_cfgs then + r['status'] = "Failed to load /etc/map/1905d.cfg file!" + else + r['status'] = 'SUCCESS' + if tonumber(devRole) == 1 then + r['al_mac'] = mapd_app_cfgs['map_controller_alid'] + else + r['al_mac'] = mapd_app_cfgs['map_agent_alid'] + end + --local easymesh_cfgs = mtkwifi.load_profile(mtkwifi.__read_easymesh_profile_path()) + --if easymesh_cfgs['MapAlMac'] ~= r['al_mac'] then + -- easymesh_cfgs['MapAlMac'] = r['al_mac'] + -- mtkwifi.save_write_easymesh_profile(easymesh_cfgs) + --end + end + return r +end + +function mtkwifi.get_easymesh_on_boarded_iface_info() + local r = {} + r['status'] = "ERROR" + r['staBhInfStr'] = "" + r['profile'] = "" + local devs = mtkwifi.get_all_devs() + for _, dev in ipairs(devs) do + if dev.apcli and dev.apcli.status == "Connected" then + r['status'] = "SUCCESS" + r['staBhInfStr'] = r['staBhInfStr']..dev.apcli.vifname..';' + r['profile'] = r['profile']..dev.profile..';' + end + end + return r +end + +function mtkwifi.load_easymesh_bss_cfgs() + local fd = io.open(mtkwifi.__easymesh_bss_cfgs_path(), "r") + if not fd then + return + end + local content = fd:read("*all") + fd:close() + + local cfgs = {} + cfgs['wildCardAlMacCfgs'] = {} + cfgs['distinctAlMacCfgs'] = {} + local tmp = {} + + -- convert profile into lua table + for _,line in ipairs(mtkwifi.__lines(content)) do + -- Trim only leading space characters + line = line:gsub("^%s*(.-)$","%1") + if string.byte(line) ~= string.byte("#") then + local b,e,lineNo,alMac,band = string.find(line, "^(%d+),(%x%x:%x%x:%x%x:%x%x:%x%x:%x%x)%s+(%d+x)%s+") + if band then + alMac = alMac:upper() + local bssInfoIdx + if tmp[alMac] then + if tmp[alMac][band] then + bssInfoIdx = mtkwifi.get_table_length(tmp[alMac][band]) + 1 + tmp[alMac][band][bssInfoIdx] = {} + else + bssInfoIdx = 1 + tmp[alMac][band] = {} + tmp[alMac][band][bssInfoIdx] = {} + end + else + bssInfoIdx = 1 + tmp[alMac] = {} + tmp[alMac][band] = {} + tmp[alMac][band][bssInfoIdx] = {} + end + local tokIdx, token = 0, nil + local bssLineStr = line:sub(e+1) + local ssid = string.gsub(bssLineStr, "(%s0x%d+).*", "") + tmp[alMac][band][bssInfoIdx]['ssid'] = ssid + local security = string.match(bssLineStr, "0x%S+ 0x%S+") + tmp[alMac][band][bssInfoIdx]['authMode'] = security:sub(1,6) + tmp[alMac][band][bssInfoIdx]['encType'] = security:sub(8,13) + local newBssLineStr = string.match(bssLineStr, "0x%S+ %S.*") + local updateBssLineStr = string.gsub(newBssLineStr, "0x%S+ 0x%S+%s", "") + local passPhrase = string.gsub(updateBssLineStr, "%s%d %d %S+ %d+ %S+ %S+", "") + tmp[alMac][band][bssInfoIdx]['passPhrase'] = passPhrase + local restBssLineStr = string.match(updateBssLineStr, "%d %d %S+ %d+ %S+ %S+") + for token in string.gmatch(restBssLineStr, "(%S+)%s?") do + tokIdx = tokIdx + 1 + if tokIdx == 1 then + tmp[alMac][band][bssInfoIdx]['isBhBssSupported'] = token + elseif tokIdx == 2 then + tmp[alMac][band][bssInfoIdx]['isFhBssSupported'] = token + elseif tokIdx == 3 then + tmp[alMac][band][bssInfoIdx]['isHidden'] = token + elseif tokIdx == 4 then + tmp[alMac][band][bssInfoIdx]['fhVlanId'] = token + elseif tokIdx == 5 then + tmp[alMac][band][bssInfoIdx]['primVlan'] = token + elseif tokIdx == 6 then + tmp[alMac][band][bssInfoIdx]['defPCP'] = token + else + nixio.syslog("warning", "load_easymesh_bss_cfgs: Extra Unknown Parameters "..line) + end + end + if tokIdx == 6 then + if alMac == "FF:FF:FF:FF:FF:FF" then + cfgs['wildCardAlMacCfgs']['FF:FF:FF:FF:FF:FF'] = tmp[alMac] + else + cfgs['distinctAlMacCfgs'][alMac] = tmp[alMac] + end + else + tmp[alMac][band][bssInfoIdx] = nil + nixio.syslog("warning", "load_easymesh_bss_cfgs: skip invalid line "..line) + end + else + nixio.syslog("warning", "load_easymesh_bss_cfgs: skip line without 'LineNumber,AL-MAC Band' "..line) + end + else + nixio.syslog("warning", "load_easymesh_bss_cfgs: skip comment line "..line) + end + end + return cfgs +end + +function mtkwifi.save_easymesh_bss_cfgs(cfgs) + if not cfgs or not cfgs['wildCardAlMacCfgs'] or not cfgs['distinctAlMacCfgs'] then + return + end + local easymesh_bss_cfg_applied_path = mtkwifi.__profile_applied_settings_path(mtkwifi.__easymesh_bss_cfgs_path()) + if not mtkwifi.exists(easymesh_bss_cfg_applied_path) then + os.execute("cp -f "..mtkwifi.__easymesh_bss_cfgs_path().." "..easymesh_bss_cfg_applied_path) + end + + local fd = io.open(mtkwifi.__easymesh_bss_cfgs_path(), "w") + if not fd then + return + end + + local lineIdx = 0 + -- First write distinct AL-MAC cfgs; then write wildcard AL-MAC(FF:FF:FF:FF:FF:FF) cfgs + for alMac,alMacTbl in pairs(cfgs['distinctAlMacCfgs']) do + for band,bssInfoTbl in pairs(alMacTbl) do + for _,bssInfo in pairs(bssInfoTbl) do + lineIdx = lineIdx + 1 + fd:write(lineIdx..','..alMac..' '.. + band..' '.. + bssInfo['ssid']..' '.. + bssInfo['authMode']..' '.. + bssInfo['encType']..' '.. + bssInfo['passPhrase']..' '.. + bssInfo['isBhBssSupported']..' '.. + bssInfo['isFhBssSupported']..' '.. + bssInfo['isHidden']..' '.. + bssInfo['fhVlanId']..' '.. + bssInfo['primVlan']..' '.. + bssInfo['defPCP'].. + '\n') + end + end + end + for alMac,alMacTbl in pairs(cfgs['wildCardAlMacCfgs']) do + for band,bssInfoTbl in pairs(alMacTbl) do + for _,bssInfo in pairs(bssInfoTbl) do + lineIdx = lineIdx + 1 + fd:write(lineIdx..','..alMac..' '.. + band..' '.. + bssInfo['ssid']..' '.. + bssInfo['authMode']..' '.. + bssInfo['encType']..' '.. + bssInfo['passPhrase']..' '.. + bssInfo['isBhBssSupported']..' '.. + bssInfo['isFhBssSupported']..' '.. + bssInfo['isHidden']..' '.. + bssInfo['fhVlanId']..' '.. + bssInfo['primVlan']..' '.. + bssInfo['defPCP'].. + '\n') + end + end + end + fd:close() + os.execute("sync "..mtkwifi.__easymesh_bss_cfgs_path().." >/dev/null 2>&1") + + -- Uncomment below line when there is sufficient space in dev1 NVRAM zone to store EasyMesh Agent's BSS Cfgs Settings. + -- mtkwifi.save_easymesh_profile_to_nvram() +end + +function mtkwifi.__prepare_easymesh_bss_nvram_cfgs() + local fd = io.open(mtkwifi.__easymesh_bss_cfgs_nvram_path(), "w") + if not fd then + return + end + local cfgs = mtkwifi.load_easymesh_bss_cfgs() + local lineIdx = 0 + -- First write distinct AL-MAC cfgs; then write wildcard AL-MAC(FF:FF:FF:FF:FF:FF) cfgs + for alMac,alMacTbl in pairs(cfgs['distinctAlMacCfgs']) do + for band,bssInfoTbl in pairs(alMacTbl) do + for _,bssInfo in pairs(bssInfoTbl) do + lineIdx = lineIdx + 1 + fd:write('EasyMeshBssCfgsLine'..lineIdx..'='..lineIdx..','..alMac..' '.. + band..' '.. + bssInfo['ssid']..' '.. + bssInfo['authMode']..' '.. + bssInfo['encType']..' '.. + bssInfo['passPhrase']..' '.. + bssInfo['isBhBssSupported']..' '.. + bssInfo['isFhBssSupported']..' '.. + bssInfo['isHidden']..' '.. + bssInfo['fhVlanId']..' '.. + bssInfo['primVlan']..' '.. + bssInfo['defPCP'].. + '\n') + end + end + end + for alMac,alMacTbl in pairs(cfgs['wildCardAlMacCfgs']) do + for band,bssInfoTbl in pairs(alMacTbl) do + for _,bssInfo in pairs(bssInfoTbl) do + lineIdx = lineIdx + 1 + fd:write('EasyMeshBssCfgsLine'..lineIdx..'='..lineIdx..','..alMac..' '.. + band..' '.. + bssInfo['ssid']..' '.. + bssInfo['authMode']..' '.. + bssInfo['encType']..' '.. + bssInfo['passPhrase']..' '.. + bssInfo['isBhBssSupported']..' '.. + bssInfo['isFhBssSupported']..' '.. + bssInfo['isHidden']..' '.. + bssInfo['fhVlanId']..' '.. + bssInfo['primVlan']..' '.. + bssInfo['defPCP'].. + '\n') + end + end + end + fd:write('EasyMeshTotalBssCfgsLines='..lineIdx..'\n') + fd:close() + os.execute("sync >/dev/null 2>&1") +end + +return mtkwifi diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/mtkwifi.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/mtkwifi.luac new file mode 100644 index 0000000000000000000000000000000000000000..d9d87499d339dcaf61806434327e0dc5c45b7d6c GIT binary patch literal 115987 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 im|y#b_Kh6=)v>8_xATZK(`Xnovr)R*_`0 + +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 + +$Id$ +]]-- + +local table = require "table" +local nixio = require "nixio" +local type, ipairs, setmetatable = type, ipairs, setmetatable +require "nixio.util" + + +module ("nixio.fs", function(m) setmetatable(m, {__index = nixio.fs}) end) + + +function readfile(path, limit) + local fd, code, msg = nixio.open(path, "r") + local data + if not fd then + return nil, code, msg + end + + data, code, msg = fd:readall(limit) + + fd:close() + return data, code, msg +end + + +function writefile(path, data) + local fd, code, msg, stat = nixio.open(path, "w") + if not fd then + return nil, code, msg + end + + stat, code, msg = fd:writeall(data) + + fd:close() + return stat, code, msg +end + +function datacopy(src, dest, size) + local fdin, code, msg = nixio.open(src, "r") + if not fdin then + return nil, code, msg + end + + local fdout, code, msg = nixio.open(dest, "w") + if not fdout then + return nil, code, msg + end + + local stat, code, msg, sent = fdin:copy(fdout, size) + fdin:close() + fdout:close() + + return stat, code, msg, sent +end + +function copy(src, dest) + local stat, code, msg, res = nixio.fs.lstat(src) + if not stat then + return nil, code, msg + end + + if stat.type == "dir" then + if nixio.fs.stat(dest, type) ~= "dir" then + res, code, msg = nixio.fs.mkdir(dest) + else + stat = true + end + elseif stat.type == "lnk" then + res, code, msg = nixio.fs.symlink(nixio.fs.readlink(src), dest) + elseif stat.type == "reg" then + res, code, msg = datacopy(src, dest) + end + + if not res then + return nil, code, msg + end + + nixio.fs.utimes(dest, stat.atime, stat.mtime) + + if nixio.fs.lchown then + nixio.fs.lchown(dest, stat.uid, stat.gid) + end + + if stat.type ~= "lnk" then + nixio.fs.chmod(dest, stat.modedec) + end + + return true +end + +function move(src, dest) + local stat, code, msg = nixio.fs.rename(src, dest) + if not stat and code == nixio.const.EXDEV then + stat, code, msg = copy(src, dest) + if stat then + stat, code, msg = nixio.fs.unlink(src) + end + end + return stat, code, msg +end + +function mkdirr(dest, mode) + if nixio.fs.stat(dest, "type") == "dir" then + return true + else + local stat, code, msg = nixio.fs.mkdir(dest, mode) + if not stat and code == nixio.const.ENOENT then + stat, code, msg = mkdirr(nixio.fs.dirname(dest), mode) + if stat then + stat, code, msg = nixio.fs.mkdir(dest, mode) + end + end + return stat, code, msg + end +end + +local function _recurse(cb, src, dest) + local type = nixio.fs.lstat(src, "type") + if type ~= "dir" then + return cb(src, dest) + else + local stat, se, code, msg, s, c, m = true, nixio.const.sep + if dest then + s, c, m = cb(src, dest) + stat, code, msg = stat and s, c or code, m or msg + end + + for e in nixio.fs.dir(src) do + if dest then + s, c, m = _recurse(cb, src .. se .. e, dest .. se .. e) + else + s, c, m = _recurse(cb, src .. se .. e) + end + stat, code, msg = stat and s, c or code, m or msg + end + + if not dest then -- Postfix + s, c, m = cb(src) + stat, code, msg = stat and s, c or code, m or msg + end + + return stat, code, msg + end +end + +function copyr(src, dest) + return _recurse(copy, src, dest) +end + +function mover(src, dest) + local stat, code, msg = nixio.fs.rename(src, dest) + if not stat and code == nixio.const.EXDEV then + stat, code, msg = _recurse(copy, src, dest) + if stat then + stat, code, msg = _recurse(nixio.fs.remove, src) + end + end + return stat, code, msg +end + +function remover(src) + return _recurse(nixio.fs.remove, src) +end \ No newline at end of file diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/nixio/fs.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/nixio/fs.luac new file mode 100644 index 0000000000000000000000000000000000000000..554fa66c6514b140c55a68d210049f49859f76ae GIT binary patch literal 7395 zcmcgx`)?dq6+U-n){jEzOWhPw3Vo6a6si;?Kmum{ibSfEQfN@+CfV+eoh7?ov%7AR zN+@?z#{m@(LMlj>DNzX^fq;Ku9J!X@PaqZ&5`P1~!uQ>oGc&v1ja`s>qtEx=@1FZU z=iD=!qc5ygUlJ2c1yeF5NeY*`$D3=_w8qSv`U3uzfJ!Lxs1-Aha^H$9qI^9Td0&i_ zDn{lF_`Z?F3S_p8^o=L~0*B;@Sn$@=E38^MS@ERaPs@Fjx6|@A%7&dR33Jl;Qpeay zACxh^EB8^x9EqIuZecEzy&K!U+``z(-!OsP!uV_>ccT)>W5^!_gKa0N6McWJ(TOCO zNQqwc^=6^E(zx7cOTaT=>Rnky<785i#%i_E=}K^KTJJ`^R@AGeLwqD<-4MsudW~k@ zv(>JzahmA?nI(*+Oed<=ml~A0CzZL>Y4oCmQlUELs|Ewc2kKmfN~mZ*)5O zRw-MkQW(|zWD-KR!Bsq*!o#goZVs@(-$ERzu`*E!y}ydFi=j04B%JT#QjGbvu zdo@~-K=(0l*428o3H{K8*P88agsvi7=qP*w{LvJTA}``osE@+q;E5-Oz(e88s^zQI z-UU>X6VoL%8?8n!W3xM~yIwq6?Okivqm1PKnOfcDj8j2subY>>dBZx@;I%!2mggve zYop}8YYY44L%Om{xqUio9CK$}y zbX`Tc?7Bz}UsqAE3(w|8CprGQPM~#$623g}6!INlS7KU>R4O8Su(H30MGK>SS!ZGE zkl8RHtxn7}SR^cM6Mblnvz`sCXZ_fd;4FHax*Z#oef%?vGsZjRZ=1zgF{k{B&ph-Y z?@KB6Z3+9DIO|)F7RmD&Z;@)8xM@Ae#HFfk=9bCLJwF0`Gl?$_;Y+9w+d(MafK8Na{Rl z+T`?=n2YT7VNRT9=qW0PUNS~FgS8oZ9q|uL z51ZUC`)Z|Vgf@HT@_A?6ekQzGvoJHuC(g^-`m_Ef#^oGEE;d#sZbROibMoBJ8MmK; zTxBj9_ZnraxrRP?UR}WO5SChu53&cb=~N=K$s`U_B|un{n`s7w1_=$P7;`0z%6FpWyg84yGyp_U*NPCd>1p;~w5;?w8sx66I(>a`Jpw*>i{9OeQJ5AA-t^-9MCkB225AtRR zTMkQh4mQ2ekG+NM*gJx_sw{z>hr5DzfFHqkobxxd{=E^yfPYGKRm|^m2Ho* zi_XX8av|*7;$I9ea~9x3>`u_f08IS!Q9J2GE7hVI)!Hj~wKC(F|L)xUE4jTV&$ISj zTX6==A-P|Mv7^ucKMvnR+c;cA8__%fZL}A`JKzQB^{U1^k@CoLjVY2&V|Y?U(rJv6 zIwUnlIo!aIc5niw4saEqLNIt4RtVD<*|L_8n2K$Hs^8O7NoT<`w-Wkd z_Pjp6>Fq@5!JIr>x`XeIvIu2@qq8<(S?~pNry`|t{GY3sX9DXe?O=>uFD~ugj!PVC z&ogkP)7H&leY*oru=OyoVgu{NflPj9XMM^Bung3tB$Mw1rRhE2;LFHPVg4!1KgsZJ zT;Dz4tu>RqgFw-()yG=r(zONyYn{ScC;vHvGm(E^bx%O-XZz=xG4}`FJI=;^m4&|b z|AF#ISFaX!a9F33sV%C$bR)Xd#8F>EJ_>JvC)USc1N<;_4716oStPOFbUMPhPE=d# zq`w@J!_zG{0X+8;lgMcb?*r->e}&N~LnDQ69Ez$a$X>r5gPY*F7(GUfYPdK(@9eQ4 z$tlLJ^*uEH5y1EO^wY?>*0Hf_JR(-L0YV2W*AcJAoXKOnik%zl$HjOR4V25m68Bxs zn6=MGB$EwU`!HhGPUL>4$BN}?;5TD%3;a0z7HxOK@6b;C{t);BWQyROlz`z|ek$dW z<*{p#bSBAbFOpg8N-|(yB#m9keE{wL6U6_*F8DE?LD(FyE_Og`Uc=*m7CbfF>8kpM z!7q;N1~|^2?*h)}ECo4}>OAd)1oEf=9?xouW#o+Ohz>yithk9XLC3($SG+;`T;AkD#;{tMy2v~~ah literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/nixio/util.lua b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/nixio/util.lua new file mode 100644 index 000000000000..63d2f6214796 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/nixio/util.lua @@ -0,0 +1,270 @@ +--[[ +nixio - Linux I/O library for lua + +Copyright 2009 Steven Barth + +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 + +$Id$ +]]-- + +local table = require "table" +local nixio = require "nixio" +local getmetatable, assert, pairs, type = getmetatable, assert, pairs, type +local tostring = tostring + +module "nixio.util" + +local BUFFERSIZE = nixio.const.buffersize +local ZIOBLKSIZE = 65536 +local socket = nixio.meta_socket +local tls_socket = nixio.meta_tls_socket +local file = nixio.meta_file +local uname = nixio.uname() +local ZBUG = uname.sysname == "Linux" and uname.release:sub(1, 3) == "2.4" + +function consume(iter, append) + local tbl = append or {} + if iter then + for obj in iter do + tbl[#tbl+1] = obj + end + end + return tbl +end + +local meta = {} + +function meta.is_socket(self) + return (getmetatable(self) == socket) +end + +function meta.is_tls_socket(self) + return (getmetatable(self) == tls_socket) +end + +function meta.is_file(self) + return (getmetatable(self) == file) +end + +function meta.readall(self, len) + local block, code, msg = self:read(len or BUFFERSIZE) + + if not block then + return nil, code, msg, "" + elseif #block == 0 then + return "", nil, nil, "" + end + + local data, total = {block}, #block + + while not len or len > total do + block, code, msg = self:read(len and (len - total) or BUFFERSIZE) + + if not block then + return nil, code, msg, table.concat(data) + elseif #block == 0 then + break + end + + data[#data+1], total = block, total + #block + end + + local data = #data > 1 and table.concat(data) or data[1] + return data, nil, nil, data +end +meta.recvall = meta.readall + +function meta.writeall(self, data) + data = tostring(data) + local sent, code, msg = self:write(data) + + if not sent then + return nil, code, msg, 0 + end + + local total = sent + + while total < #data do + sent, code, msg = self:write(data, total) + + if not sent then + return nil, code, msg, total + end + + total = total + sent + end + + return total, nil, nil, total +end +meta.sendall = meta.writeall + +function meta.linesource(self, limit) + limit = limit or BUFFERSIZE + local buffer = "" + local bpos = 0 + return function(flush) + local line, endp, _ + + if flush then + line = buffer:sub(bpos + 1) + buffer = type(flush) == "string" and flush or "" + bpos = 0 + return line + end + + while not line do + _, endp, line = buffer:find("(.-)\r?\n", bpos + 1) + if line then + bpos = endp + return line + elseif #buffer < limit + bpos then + local newblock, code, msg = self:read(limit + bpos - #buffer) + if not newblock then + return nil, code, msg + elseif #newblock == 0 then + return nil + end + buffer = buffer:sub(bpos + 1) .. newblock + bpos = 0 + else + return nil, 0 + end + end + end +end + +function meta.blocksource(self, bs, limit) + bs = bs or BUFFERSIZE + return function() + local toread = bs + if limit then + if limit < 1 then + return nil + elseif limit < toread then + toread = limit + end + end + + local block, code, msg = self:read(toread) + + if not block then + return nil, code, msg + elseif #block == 0 then + return nil + else + if limit then + limit = limit - #block + end + + return block + end + end +end + +function meta.sink(self, close) + return function(chunk, src_err) + if not chunk and not src_err and close then + if self.shutdown then + self:shutdown() + end + self:close() + elseif chunk and #chunk > 0 then + return self:writeall(chunk) + end + return true + end +end + +function meta.copy(self, fdout, size) + local source = self:blocksource(nil, size) + local sink = fdout:sink() + local sent, chunk, code, msg = 0 + + repeat + chunk, code, msg = source() + sink(chunk, code, msg) + sent = chunk and (sent + #chunk) or sent + until not chunk + return not code and sent or nil, code, msg, sent +end + +function meta.copyz(self, fd, size) + local sent, lsent, code, msg = 0 + local splicable + + if not ZBUG and self:is_file() then + local ftype = self:stat("type") + if nixio.sendfile and fd:is_socket() and ftype == "reg" then + repeat + lsent, code, msg = nixio.sendfile(fd, self, size or ZIOBLKSIZE) + if lsent then + sent = sent + lsent + size = size and (size - lsent) + end + until (not lsent or lsent == 0 or (size and size == 0)) + if lsent or (not lsent and sent == 0 and + code ~= nixio.const.ENOSYS and code ~= nixio.const.EINVAL) then + return lsent and sent, code, msg, sent + end + elseif nixio.splice and not fd:is_tls_socket() and ftype == "fifo" then + splicable = true + end + end + + if nixio.splice and fd:is_file() and not splicable then + splicable = not self:is_tls_socket() and fd:stat("type") == "fifo" + end + + if splicable then + repeat + lsent, code, msg = nixio.splice(self, fd, size or ZIOBLKSIZE) + if lsent then + sent = sent + lsent + size = size and (size - lsent) + end + until (not lsent or lsent == 0 or (size and size == 0)) + if lsent or (not lsent and sent == 0 and + code ~= nixio.const.ENOSYS and code ~= nixio.const.EINVAL) then + return lsent and sent, code, msg, sent + end + end + + return self:copy(fd, size) +end + +if tls_socket then + function tls_socket.close(self) + return self.socket:close() + end + + function tls_socket.getsockname(self) + return self.socket:getsockname() + end + + function tls_socket.getpeername(self) + return self.socket:getpeername() + end + + function tls_socket.getsockopt(self, ...) + return self.socket:getsockopt(...) + end + tls_socket.getopt = tls_socket.getsockopt + + function tls_socket.setsockopt(self, ...) + return self.socket:setsockopt(...) + end + tls_socket.setopt = tls_socket.setsockopt +end + +for k, v in pairs(meta) do + file[k] = v + socket[k] = v + if tls_socket then + tls_socket[k] = v + end +end diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/nixio/util.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/nixio/util.luac new file mode 100644 index 0000000000000000000000000000000000000000..72364bd97243666875e8ef38ac8d79ab218c3b55 GIT binary patch literal 10102 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 literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/shuci.lua b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/shuci.lua new file mode 100755 index 000000000000..c90e06f82a16 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/shuci.lua @@ -0,0 +1,128 @@ +#!/usr/bin/env lua + +--[[ + * A pure lua library to translate between lua table and uci config + * + * For UCI: http://wiki.openwrt.org/doc/techref/uci + * http://wiki.openwrt.org/doc/uci + * + * Copyright (C) 2015 Hua Shao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. +]] + + +local shuci = {} + +function shuci.decode(path) + function file_exists(name) + if not name then return false end + local f = io.open(name,"r") + if f then io.close(f) return true else return false end + end + local function linebreaker(str) + local i,_ = string.find(str, "([^%s])") + if not i then return nil end + if string.find(str, "config%s+%w+") then + local i,j,k,v = string.find(str, "config%s+([%w-_]+)%s*['\"]*([^%s\'\"]*)") + return "section", k, v + elseif string.find(str, "option%s+%w+") then + local i,j,k,v = string.find(str, "option%s+([%w-_]+)%s*['\"]([^'\"]+)['\"]") + if not k or not v then + i,j,k,v = string.find(str, "option%s+([%w-_]+)%s*['\"]*([^%s\'\"]*)") + end + return "option", k, v + elseif string.find(str, "list%s+%w+") then + local i,j,k,v = string.find(str, "list%s+([%w-_]+)%s*['\"]([^'\"]+)['\"]") + if not k or not v then + i,j,k,v = string.find(str, "list%s+([%w-_]+)%s*['\"]*([^%s\'\"]*)") + end + return "list", k, v + else + print("invalid line!", str) + return nil + end + end + + if not file_exists(path) then + return + end + + local _sect_ = nil + local t = {} + for line in io.lines(path) do + local _type, _name, _value = linebreaker(line) + if _type == "section" then + if not t[_name] then t[_name] = {} end + -- be careful of anonymous sections + if not _value or _value == "" then _value = #t[_name]+1 end + t[_name][_value] = {} + _sect_ = t[_name][_value] + end + if _type == "option" then + if _name and _value then + _sect_[_name] = _value + end + end + if _type == "list" and _name and _value then + local idx + if not _sect_[_name] then + _sect_[_name] = {} + _sect_[_name][1] = _value + else + idx = #_sect_[_name] + _sect_[_name][idx+1] = _value + end + end + end + + return t +end + + +function shuci.encode(t, path) + local dump = io.write + if path then + local fp = io.open(path, "a+") + dump = function(str) fp:write(str) end + end + for _sect_type,_ in pairs(t) do + for _name,_sect in pairs(t[_sect_type]) do + dump(string.format("config\t%s\t'%s'\n", _sect_type, _name)) + for k,v in pairs(_sect) do + if type(v) == "table" then + for _,vv in ipairs(v) do + dump(string.format("\tlist\t%s\t'%s'\n",k,vv)) + end + elseif type(v) == "string" and k ~= ".name" then + dump(string.format("\toption\t%s\t'%s'\n",k,v)) + elseif type(v) == "number" and k ~= ".name" then + dump(string.format("\toption\t%s\t'%s'\n",k,tonumber(v))) + end + end + dump("\n") + end + end +end + + +function shuci.dump(t, indent) + if not indent then indent = 0 end + for k,v in pairs(t) do + if type(v) == "table" then + print(string.rep(" ",indent)..k..":") + shuci.dump(v, indent+4) + else + print(string.rep(" ",indent)..k..":"..v) + end + end +end + +return shuci diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/shuci.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/shuci.luac new file mode 100644 index 0000000000000000000000000000000000000000..762dcc1d79afefbe36c5906c3f1fd03cc6d9d4d0 GIT binary patch literal 5518 zcma)ATW=f36+W}Oq($3Foi?UYM|I_-sw}HXk+x`o^pagEa)8DS8U$?s-xyk?oQ-Ib zB`MiKi)u(&0s{0G7(#&n`5{ZY5|BLSDIg3KeQklFedtqxeBbO2xxC39`{T@fXXebA z+nKX^@yb^5T~XeQHzTJcPJZ*lt@ZHL%2qKUm^sc#{1PP~=Zh}+%(X6Q5J=$K=S9?c z(c+@+oiH>yl-Fye@}OR>#`TPqZ{4Yz9!XtZK;eB>lsvnJj3d6q+!xtH9svhh)C1J> zBAO%aqV*TxGl599?K{2_nNv#T<+K(@i|=T6&QWSM^1Zxx`e>!2zFI+l%;XD4GAoIOg z7P+y^cl9Ia%dR@=+$wEXnmRv`U5{LDx6>iV@|TWenO|A$X254JU|xCo@6Kb)lk=Uy zT)oryVcG~QfXX&9lG$CtEtzL%t)||#*rt6hjvy5IM#wO(FQg{*NQOtpRT5!G` z)S6K(-iZf$J`o_34awn`Ogf!MyW zQ9lYIm-7xBM|we9X(bt7X%BXJk!ZIYK$spkniuzX`dZc3Z2ln zyu<+pHcV|kWP|m__Fh|W&wFhR_+DZ_!W}>S%f~k!8r-h2aUy-MRL2Ii#CtYT6jry3 zm9R8VXLZ4FyeHqCfO&9fu=O;&1lcHP`Y1T}Y2a>;f}54bBI-}V655R761ZtthHMl+ z0GGxqs6PomM4M5(1}=>sp-#|8eFN|SCR`JfU&k+Qxk;gMfN7>M#)QTJ^O3NMu{-$H4KHBY`}`*VQj+8Q+Wi~^CcbkCd-8R%81HbR+8Nh zP6tPh=&Z~{SvfVA6{oi*^67oX4gcTC%gl(r0QwyLzO2qLp3olRnm3gk-S6hq8s6y) zI%M=r&lBeW+s)v7$R53#%STCne{%o5Lsuf`IDn1NBh*verLO<%a4p)dCz!Xo&;b|y zYv=Epw*GU6F~M^(5@Oo@zuO2~_-e^O&J;(SZd{QfVgTE)UJM%uuI*W{afPkp)ZQuL znX|_@4kU65&D`QbbMfS01asc{T3f|imDER5n2bW#%#`C(no?qodDWaxkF#RR#m{}J zp*5?zb>|jtqsLg4sa3O#y@4lr#QNzbN`ykp$2Gwc%i9v+A3}S?SzJrD?@u6JKQO}P zwtG4^&Dj9SJ~JN5+F86^wlC>xyFh{w435l})z7O6`m{Wgh+0 zvCnp&nKt@G^!PC_X$`Dw^5|A-M=zL|_ciFUCt-WLPl4?x9!H^oodhy0{xyQq)i9=NB$$7nYR ze+2)upe=s_xZ|9-Jub0}SxQ)Bf)DdDJ`|ZqPdl+(Ov)#nSSy=+Vy)DguxSihgf7c$ n_ZgfWvD;?;P--IAJXqdU`bzpt-gZumpyfKjcyBUvi%eewOd8M- literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket-3.0-rc1.so b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket-3.0-rc1.so new file mode 100755 index 0000000000000000000000000000000000000000..8adda1dbc0030aa71c93d2f98ae5627c0420d774 GIT binary patch literal 63610 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 HIKT2Y!U1{l literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket.lua b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket.lua new file mode 100644 index 000000000000..d1c0b1649245 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket.lua @@ -0,0 +1,149 @@ +----------------------------------------------------------------------------- +-- LuaSocket helper module +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local string = require("string") +local math = require("math") +local socket = require("socket.core") + +local _M = socket + +----------------------------------------------------------------------------- +-- Exported auxiliar functions +----------------------------------------------------------------------------- +function _M.connect4(address, port, laddress, lport) + return socket.connect(address, port, laddress, lport, "inet") +end + +function _M.connect6(address, port, laddress, lport) + return socket.connect(address, port, laddress, lport, "inet6") +end + +function _M.bind(host, port, backlog) + if host == "*" then host = "0.0.0.0" end + local addrinfo, err = socket.dns.getaddrinfo(host); + if not addrinfo then return nil, err end + local sock, res + err = "no info on address" + for i, alt in base.ipairs(addrinfo) do + if alt.family == "inet" then + sock, err = socket.tcp4() + else + sock, err = socket.tcp6() + end + if not sock then return nil, err end + sock:setoption("reuseaddr", true) + res, err = sock:bind(alt.addr, port) + if not res then + sock:close() + else + res, err = sock:listen(backlog) + if not res then + sock:close() + else + return sock + end + end + end + return nil, err +end + +_M.try = _M.newtry() + +function _M.choose(table) + return function(name, opt1, opt2) + if base.type(name) ~= "string" then + name, opt1, opt2 = "default", name, opt1 + end + local f = table[name or "nil"] + if not f then base.error("unknown key (".. base.tostring(name) ..")", 3) + else return f(opt1, opt2) end + end +end + +----------------------------------------------------------------------------- +-- Socket sources and sinks, conforming to LTN12 +----------------------------------------------------------------------------- +-- create namespaces inside LuaSocket namespace +local sourcet, sinkt = {}, {} +_M.sourcet = sourcet +_M.sinkt = sinkt + +_M.BLOCKSIZE = 2048 + +sinkt["close-when-done"] = function(sock) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function(self, chunk, err) + if not chunk then + sock:close() + return 1 + else return sock:send(chunk) end + end + }) +end + +sinkt["keep-open"] = function(sock) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function(self, chunk, err) + if chunk then return sock:send(chunk) + else return 1 end + end + }) +end + +sinkt["default"] = sinkt["keep-open"] + +_M.sink = _M.choose(sinkt) + +sourcet["by-length"] = function(sock, length) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function() + if length <= 0 then return nil end + local size = math.min(socket.BLOCKSIZE, length) + local chunk, err = sock:receive(size) + if err then return nil, err end + length = length - string.len(chunk) + return chunk + end + }) +end + +sourcet["until-closed"] = function(sock) + local done + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function() + if done then return nil end + local chunk, err, partial = sock:receive(socket.BLOCKSIZE) + if not err then return chunk + elseif err == "closed" then + sock:close() + done = 1 + return partial + else return nil, err end + end + }) +end + + +sourcet["default"] = sourcet["until-closed"] + +_M.source = _M.choose(sourcet) + +return _M diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket.luac new file mode 100644 index 0000000000000000000000000000000000000000..50cc6d42701559616c007e3b1e7ec1bb518ec2fe GIT binary patch literal 7052 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 literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/core.so b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/core.so new file mode 120000 index 000000000000..219b03b32ec5 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/core.so @@ -0,0 +1 @@ +../socket-3.0-rc1.so \ No newline at end of file diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/ftp.lua b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/ftp.lua new file mode 100644 index 000000000000..fd66fc463c0b --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/ftp.lua @@ -0,0 +1,329 @@ +----------------------------------------------------------------------------- +-- FTP support for the Lua language +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local table = require("table") +local string = require("string") +local math = require("math") +local socket = require("socket") +local url = require("socket.url") +local tp = require("socket.tp") +local ltn12 = require("ltn12") +socket.ftp = {} +local _M = socket.ftp +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +-- timeout in seconds before the program gives up on a connection +_M.TIMEOUT = 60 +-- default port for ftp service +local PORT = 21 +-- this is the default anonymous password. used when no password is +-- provided in url. should be changed to your e-mail. +_M.USER = "ftp" +_M.PASSWORD = "test@example.invalid" + +----------------------------------------------------------------------------- +-- Low level FTP API +----------------------------------------------------------------------------- +local metat = { __index = {} } + +function _M.open(server, port, create) + local tp = socket.try(tp.connect(server, port or PORT, _M.TIMEOUT, create)) + local f = base.setmetatable({ tp = tp }, metat) + -- make sure everything gets closed in an exception + f.try = socket.newtry(function() f:close() end) + return f +end + +function metat.__index:portconnect() + self.try(self.server:settimeout(_M.TIMEOUT)) + self.data = self.try(self.server:accept()) + self.try(self.data:settimeout(_M.TIMEOUT)) +end + +function metat.__index:pasvconnect() + self.data = self.try(socket.tcp()) + self.try(self.data:settimeout(_M.TIMEOUT)) + self.try(self.data:connect(self.pasvt.address, self.pasvt.port)) +end + +function metat.__index:login(user, password) + self.try(self.tp:command("user", user or _M.USER)) + local code, reply = self.try(self.tp:check{"2..", 331}) + if code == 331 then + self.try(self.tp:command("pass", password or _M.PASSWORD)) + self.try(self.tp:check("2..")) + end + return 1 +end + +function metat.__index:pasv() + self.try(self.tp:command("pasv")) + local code, reply = self.try(self.tp:check("2..")) + local pattern = "(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)" + local a, b, c, d, p1, p2 = socket.skip(2, string.find(reply, pattern)) + self.try(a and b and c and d and p1 and p2, reply) + self.pasvt = { + address = string.format("%d.%d.%d.%d", a, b, c, d), + port = p1*256 + p2 + } + if self.server then + self.server:close() + self.server = nil + end + return self.pasvt.address, self.pasvt.port +end + +function metat.__index:epsv() + self.try(self.tp:command("epsv")) + local code, reply = self.try(self.tp:check("229")) + local pattern = "%((.)(.-)%1(.-)%1(.-)%1%)" + local d, prt, address, port = string.match(reply, pattern) + self.try(port, "invalid epsv response") + self.pasvt = { + address = self.tp:getpeername(), + port = port + } + if self.server then + self.server:close() + self.server = nil + end + return self.pasvt.address, self.pasvt.port +end + + +function metat.__index:port(address, port) + self.pasvt = nil + if not address then + address, port = self.try(self.tp:getsockname()) + self.server = self.try(socket.bind(address, 0)) + address, port = self.try(self.server:getsockname()) + self.try(self.server:settimeout(_M.TIMEOUT)) + end + local pl = math.mod(port, 256) + local ph = (port - pl)/256 + local arg = string.gsub(string.format("%s,%d,%d", address, ph, pl), "%.", ",") + self.try(self.tp:command("port", arg)) + self.try(self.tp:check("2..")) + return 1 +end + +function metat.__index:eprt(family, address, port) + self.pasvt = nil + if not address then + address, port = self.try(self.tp:getsockname()) + self.server = self.try(socket.bind(address, 0)) + address, port = self.try(self.server:getsockname()) + self.try(self.server:settimeout(_M.TIMEOUT)) + end + local arg = string.format("|%s|%s|%d|", family, address, port) + self.try(self.tp:command("eprt", arg)) + self.try(self.tp:check("2..")) + return 1 +end + + +function metat.__index:send(sendt) + self.try(self.pasvt or self.server, "need port or pasv first") + -- if there is a pasvt table, we already sent a PASV command + -- we just get the data connection into self.data + if self.pasvt then self:pasvconnect() end + -- get the transfer argument and command + local argument = sendt.argument or + url.unescape(string.gsub(sendt.path or "", "^[/\\]", "")) + if argument == "" then argument = nil end + local command = sendt.command or "stor" + -- send the transfer command and check the reply + self.try(self.tp:command(command, argument)) + local code, reply = self.try(self.tp:check{"2..", "1.."}) + -- if there is not a pasvt table, then there is a server + -- and we already sent a PORT command + if not self.pasvt then self:portconnect() end + -- get the sink, source and step for the transfer + local step = sendt.step or ltn12.pump.step + local readt = { self.tp } + local checkstep = function(src, snk) + -- check status in control connection while downloading + local readyt = socket.select(readt, nil, 0) + if readyt[tp] then code = self.try(self.tp:check("2..")) end + return step(src, snk) + end + local sink = socket.sink("close-when-done", self.data) + -- transfer all data and check error + self.try(ltn12.pump.all(sendt.source, sink, checkstep)) + if string.find(code, "1..") then self.try(self.tp:check("2..")) end + -- done with data connection + self.data:close() + -- find out how many bytes were sent + local sent = socket.skip(1, self.data:getstats()) + self.data = nil + return sent +end + +function metat.__index:receive(recvt) + self.try(self.pasvt or self.server, "need port or pasv first") + if self.pasvt then self:pasvconnect() end + local argument = recvt.argument or + url.unescape(string.gsub(recvt.path or "", "^[/\\]", "")) + if argument == "" then argument = nil end + local command = recvt.command or "retr" + self.try(self.tp:command(command, argument)) + local code,reply = self.try(self.tp:check{"1..", "2.."}) + if (code >= 200) and (code <= 299) then + recvt.sink(reply) + return 1 + end + if not self.pasvt then self:portconnect() end + local source = socket.source("until-closed", self.data) + local step = recvt.step or ltn12.pump.step + self.try(ltn12.pump.all(source, recvt.sink, step)) + if string.find(code, "1..") then self.try(self.tp:check("2..")) end + self.data:close() + self.data = nil + return 1 +end + +function metat.__index:cwd(dir) + self.try(self.tp:command("cwd", dir)) + self.try(self.tp:check(250)) + return 1 +end + +function metat.__index:type(type) + self.try(self.tp:command("type", type)) + self.try(self.tp:check(200)) + return 1 +end + +function metat.__index:greet() + local code = self.try(self.tp:check{"1..", "2.."}) + if string.find(code, "1..") then self.try(self.tp:check("2..")) end + return 1 +end + +function metat.__index:quit() + self.try(self.tp:command("quit")) + self.try(self.tp:check("2..")) + return 1 +end + +function metat.__index:close() + if self.data then self.data:close() end + if self.server then self.server:close() end + return self.tp:close() +end + +----------------------------------------------------------------------------- +-- High level FTP API +----------------------------------------------------------------------------- +local function override(t) + if t.url then + local u = url.parse(t.url) + for i,v in base.pairs(t) do + u[i] = v + end + return u + else return t end +end + +local function tput(putt) + putt = override(putt) + socket.try(putt.host, "missing hostname") + local f = _M.open(putt.host, putt.port, putt.create) + f:greet() + f:login(putt.user, putt.password) + if putt.type then f:type(putt.type) end + f:epsv() + local sent = f:send(putt) + f:quit() + f:close() + return sent +end + +local default = { + path = "/", + scheme = "ftp" +} + +local function genericform(u) + local t = socket.try(url.parse(u, default)) + socket.try(t.scheme == "ftp", "wrong scheme '" .. t.scheme .. "'") + socket.try(t.host, "missing hostname") + local pat = "^type=(.)$" + if t.params then + t.type = socket.skip(2, string.find(t.params, pat)) + socket.try(t.type == "a" or t.type == "i", + "invalid type '" .. t.type .. "'") + end + return t +end + +_M.genericform = genericform + +local function sput(u, body) + local putt = genericform(u) + putt.source = ltn12.source.string(body) + return tput(putt) +end + +_M.put = socket.protect(function(putt, body) + if base.type(putt) == "string" then return sput(putt, body) + else return tput(putt) end +end) + +local function tget(gett) + gett = override(gett) + socket.try(gett.host, "missing hostname") + local f = _M.open(gett.host, gett.port, gett.create) + f:greet() + f:login(gett.user, gett.password) + if gett.type then f:type(gett.type) end + f:epsv() + f:receive(gett) + f:quit() + return f:close() +end + +local function sget(u) + local gett = genericform(u) + local t = {} + gett.sink = ltn12.sink.table(t) + tget(gett) + return table.concat(t) +end + +_M.command = socket.protect(function(cmdt) + cmdt = override(cmdt) + socket.try(cmdt.host, "missing hostname") + socket.try(cmdt.command, "missing command") + local f = _M.open(cmdt.host, cmdt.port, cmdt.create) + f:greet() + f:login(cmdt.user, cmdt.password) + if type(cmdt.command) == "table" then + local argument = cmdt.argument or {} + local check = cmdt.check or {} + for i,cmd in ipairs(cmdt.command) do + f.try(f.tp:command(cmd, argument[i])) + if check[i] then f.try(f.tp:check(check[i])) end + end + else + f.try(f.tp:command(cmdt.command, cmdt.argument)) + if cmdt.check then f.try(f.tp:check(cmdt.check)) end + end + f:quit() + return f:close() +end) + +_M.get = socket.protect(function(gett) + if base.type(gett) == "string" then return sget(gett) + else return tget(gett) end +end) + +return _M diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/ftp.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/ftp.luac new file mode 100644 index 0000000000000000000000000000000000000000..53635f259cad35da60eedcd49bfd337054d26c10 GIT binary patch literal 18626 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 literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/headers.lua b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/headers.lua new file mode 100644 index 000000000000..1eb8223b9ddf --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/headers.lua @@ -0,0 +1,104 @@ +----------------------------------------------------------------------------- +-- Canonic header field capitalization +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- +local socket = require("socket") +socket.headers = {} +local _M = socket.headers + +_M.canonic = { + ["accept"] = "Accept", + ["accept-charset"] = "Accept-Charset", + ["accept-encoding"] = "Accept-Encoding", + ["accept-language"] = "Accept-Language", + ["accept-ranges"] = "Accept-Ranges", + ["action"] = "Action", + ["alternate-recipient"] = "Alternate-Recipient", + ["age"] = "Age", + ["allow"] = "Allow", + ["arrival-date"] = "Arrival-Date", + ["authorization"] = "Authorization", + ["bcc"] = "Bcc", + ["cache-control"] = "Cache-Control", + ["cc"] = "Cc", + ["comments"] = "Comments", + ["connection"] = "Connection", + ["content-description"] = "Content-Description", + ["content-disposition"] = "Content-Disposition", + ["content-encoding"] = "Content-Encoding", + ["content-id"] = "Content-ID", + ["content-language"] = "Content-Language", + ["content-length"] = "Content-Length", + ["content-location"] = "Content-Location", + ["content-md5"] = "Content-MD5", + ["content-range"] = "Content-Range", + ["content-transfer-encoding"] = "Content-Transfer-Encoding", + ["content-type"] = "Content-Type", + ["cookie"] = "Cookie", + ["date"] = "Date", + ["diagnostic-code"] = "Diagnostic-Code", + ["dsn-gateway"] = "DSN-Gateway", + ["etag"] = "ETag", + ["expect"] = "Expect", + ["expires"] = "Expires", + ["final-log-id"] = "Final-Log-ID", + ["final-recipient"] = "Final-Recipient", + ["from"] = "From", + ["host"] = "Host", + ["if-match"] = "If-Match", + ["if-modified-since"] = "If-Modified-Since", + ["if-none-match"] = "If-None-Match", + ["if-range"] = "If-Range", + ["if-unmodified-since"] = "If-Unmodified-Since", + ["in-reply-to"] = "In-Reply-To", + ["keywords"] = "Keywords", + ["last-attempt-date"] = "Last-Attempt-Date", + ["last-modified"] = "Last-Modified", + ["location"] = "Location", + ["max-forwards"] = "Max-Forwards", + ["message-id"] = "Message-ID", + ["mime-version"] = "MIME-Version", + ["original-envelope-id"] = "Original-Envelope-ID", + ["original-recipient"] = "Original-Recipient", + ["pragma"] = "Pragma", + ["proxy-authenticate"] = "Proxy-Authenticate", + ["proxy-authorization"] = "Proxy-Authorization", + ["range"] = "Range", + ["received"] = "Received", + ["received-from-mta"] = "Received-From-MTA", + ["references"] = "References", + ["referer"] = "Referer", + ["remote-mta"] = "Remote-MTA", + ["reply-to"] = "Reply-To", + ["reporting-mta"] = "Reporting-MTA", + ["resent-bcc"] = "Resent-Bcc", + ["resent-cc"] = "Resent-Cc", + ["resent-date"] = "Resent-Date", + ["resent-from"] = "Resent-From", + ["resent-message-id"] = "Resent-Message-ID", + ["resent-reply-to"] = "Resent-Reply-To", + ["resent-sender"] = "Resent-Sender", + ["resent-to"] = "Resent-To", + ["retry-after"] = "Retry-After", + ["return-path"] = "Return-Path", + ["sender"] = "Sender", + ["server"] = "Server", + ["smtp-remote-recipient"] = "SMTP-Remote-Recipient", + ["status"] = "Status", + ["subject"] = "Subject", + ["te"] = "TE", + ["to"] = "To", + ["trailer"] = "Trailer", + ["transfer-encoding"] = "Transfer-Encoding", + ["upgrade"] = "Upgrade", + ["user-agent"] = "User-Agent", + ["vary"] = "Vary", + ["via"] = "Via", + ["warning"] = "Warning", + ["will-retry-until"] = "Will-Retry-Until", + ["www-authenticate"] = "WWW-Authenticate", + ["x-mailer"] = "X-Mailer", +} + +return _M \ No newline at end of file diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/headers.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/headers.luac new file mode 100644 index 0000000000000000000000000000000000000000..9603b35d555ed17892a8afe8e2447f799fe12e81 GIT binary patch literal 4792 zcmZ`-_j}vO5rxl8a!$SUa+gbT=>?a1>1AnBvSo|1Oscrs5C~GRNPq!B$;wZ<_XQsPJ)x(f!ejTYrXq;<)b#Epc`f5 zn+KD?Qs(EHgAxAT4X7*6yJ62ea18Hm8iqFi9Y;lQH|G# zZoF1h^L3(|B5Lk-(Q~gCHU9?D^KTS&t7JP{zXxzz9jn8mqnfais;i{ z6?NuoqR)I?)Y)%{KKo5k=e{NS+_y!Yzf1J_?})naUC|f5C+gz&MPK}Zs7qS(r5}n4 zek3~hv8auoh~D_AsPJc^!=H=l{z7#3m!hIyiH?3Ps`newz2A!J|4wxO_o6ocAbRtU zqT)Y^j{hv`@?S(>{;R01zlq-ZyQslGL=XNcYWOeF!+(oP{FnoQ5Ilpn;_26>bvK_2;86vm@CN-#|->y@(Ylr?&q(Nd@z;qKH72I-Cy>!rw~ z3$iTU4hDV~oq9F0b;~vjmXiu5`DU8MuL#OPldjLw9K=S5t)+0-2y_U8a5M75G|97c z;OIs{H%K3}<2RmsZ{Yn9`E)qM`e4z?G%Ds^l}(aFcFoD5gNozQnz5iQzZ;FiEFR5n ziW!knHm|0vJmc{w9moI2b6RVMJ9~?}UX2{B7n)v;Ezr&dGBvbz zk<2Wre4?bEZ@TQNJS@+aD*H6F0YOdWQEBeIZ_dN+EnbZz&09^7RgBn0FA~eqv@H_L zt36_0yE)9qy(shDqSAz(D>QMd%cu`+pZV@+Di>ml0Fkf{_I~K-3oZwIx4R-xJ zbuqAz`0avR0m)vsqTQV|>rPe1;}ye64}x*-2YDV1M<{zmnWt*esOnZVdjb?*9KFlR z!h>t*#xbb!pCenW7{<;pxZ?NHY$qtjOST1ezG7MFhS7M8Q@MyeaxKf5$fNf#9!CB) zZj!}WOSZMpn)BDlKG?+wlQkG%R(8f1O><@EHk187bCZqk3wq;o7+Kg-Eln^Tyg8U zr;Qd=R%2(bGo;!jWo|Xha%)v)BuQ&m<|0XJSr$!_*0QXf+yHAiojz1q`{^uVnYLbT z!Kko4S6jDYhWLZeI9DC4eaF(Aylvl9Gg8*l)G|&ei}DP)(8Imhk?n%4&yuj5WQjit z%;kmdur%ayK{Ee1K*}5$<#Cjudbs@TSotA#C&$Blgj!q#9O9n$bgDb8m1R`rLXFs` zx@tWS@(JRda>vSy_3>oma&b(P=#bzo*wh2Qugtl3vy>H&cQ$}qQ9LjcB7r+lNiYnq za&MyQD)%KeGa2m+y+-H8VS?34+i2}&tGHmF%Qnlg@7XEi%JJNs&H<+Hx-&sJX6 zkSn+$lq*W+6fOq^V`3%(Ol>91)*F~{o7gu?V10D{T`|b6>4ZtIT8-Ygwj|LtC+yp!pcpUI};0eGJfhPe^2A%>u6?hup12+Rt z2c7{u6L=QzY~VS-Ex>bu=K;?LUI4rhcoFbo;3dFIftLXar~`+A2G9iNfO+5ua1>Yo zjseGkMW6*N0VjZE;3UunI=~9B3akO^z$xG~a0WOFoCD4S_~(Ro5x4}He%-xUY(YD~?qc`mk!T{Dz<{tFzr0v!MV literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/http.lua b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/http.lua new file mode 100644 index 000000000000..6a3416e0a4a2 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/http.lua @@ -0,0 +1,420 @@ +----------------------------------------------------------------------------- +-- HTTP/1.1 client support for the Lua language. +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +------------------------------------------------------------------------------- +local socket = require("socket") +local url = require("socket.url") +local ltn12 = require("ltn12") +local mime = require("mime") +local string = require("string") +local headers = require("socket.headers") +local base = _G +local table = require("table") +socket.http = {} +local _M = socket.http + +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +-- connection timeout in seconds +_M.TIMEOUT = 60 +-- user agent field sent in request +_M.USERAGENT = socket._VERSION + +-- supported schemes and their particulars +local SCHEMES = { + http = { + port = 80 + , create = function(t) + return socket.tcp end } + , https = { + port = 443 + , create = function(t) + local https = assert( + require("ssl.https"), 'LuaSocket: LuaSec not found') + local tcp = assert( + https.tcp, 'LuaSocket: Function tcp() not available from LuaSec') + return tcp(t) end }} + +-- default scheme and port for document retrieval +local SCHEME = 'http' +local PORT = SCHEMES[SCHEME].port +----------------------------------------------------------------------------- +-- Reads MIME headers from a connection, unfolding where needed +----------------------------------------------------------------------------- +local function receiveheaders(sock, headers) + local line, name, value, err + headers = headers or {} + -- get first line + line, err = sock:receive() + if err then return nil, err end + -- headers go until a blank line is found + while line ~= "" do + -- get field-name and value + name, value = socket.skip(2, string.find(line, "^(.-):%s*(.*)")) + if not (name and value) then return nil, "malformed reponse headers" end + name = string.lower(name) + -- get next line (value might be folded) + line, err = sock:receive() + if err then return nil, err end + -- unfold any folded values + while string.find(line, "^%s") do + value = value .. line + line = sock:receive() + if err then return nil, err end + end + -- save pair in table + if headers[name] then headers[name] = headers[name] .. ", " .. value + else headers[name] = value end + end + return headers +end + +----------------------------------------------------------------------------- +-- Extra sources and sinks +----------------------------------------------------------------------------- +socket.sourcet["http-chunked"] = function(sock, headers) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function() + -- get chunk size, skip extention + local line, err = sock:receive() + if err then return nil, err end + local size = base.tonumber(string.gsub(line, ";.*", ""), 16) + if not size then return nil, "invalid chunk size" end + -- was it the last chunk? + if size > 0 then + -- if not, get chunk and skip terminating CRLF + local chunk, err, part = sock:receive(size) + if chunk then sock:receive() end + return chunk, err + else + -- if it was, read trailers into headers table + headers, err = receiveheaders(sock, headers) + if not headers then return nil, err end + end + end + }) +end + +socket.sinkt["http-chunked"] = function(sock) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function(self, chunk, err) + if not chunk then return sock:send("0\r\n\r\n") end + local size = string.format("%X\r\n", string.len(chunk)) + return sock:send(size .. chunk .. "\r\n") + end + }) +end + +----------------------------------------------------------------------------- +-- Low level HTTP API +----------------------------------------------------------------------------- +local metat = { __index = {} } + +function _M.open(host, port, create) + -- create socket with user connect function, or with default + local c = socket.try(create()) + local h = base.setmetatable({ c = c }, metat) + -- create finalized try + h.try = socket.newtry(function() h:close() end) + -- set timeout before connecting + h.try(c:settimeout(_M.TIMEOUT)) + h.try(c:connect(host, port)) + -- here everything worked + return h +end + +function metat.__index:sendrequestline(method, uri) + local reqline = string.format("%s %s HTTP/1.1\r\n", method or "GET", uri) + return self.try(self.c:send(reqline)) +end + +function metat.__index:sendheaders(tosend) + local canonic = headers.canonic + local h = "\r\n" + for f, v in base.pairs(tosend) do + h = (canonic[f] or f) .. ": " .. v .. "\r\n" .. h + end + self.try(self.c:send(h)) + return 1 +end + +function metat.__index:sendbody(headers, source, step) + source = source or ltn12.source.empty() + step = step or ltn12.pump.step + -- if we don't know the size in advance, send chunked and hope for the best + local mode = "http-chunked" + if headers["content-length"] then mode = "keep-open" end + return self.try(ltn12.pump.all(source, socket.sink(mode, self.c), step)) +end + +function metat.__index:receivestatusline() + local status,ec = self.try(self.c:receive(5)) + -- identify HTTP/0.9 responses, which do not contain a status line + -- this is just a heuristic, but is what the RFC recommends + if status ~= "HTTP/" then + if ec == "timeout" then + return 408 + end + return nil, status + end + -- otherwise proceed reading a status line + status = self.try(self.c:receive("*l", status)) + local code = socket.skip(2, string.find(status, "HTTP/%d*%.%d* (%d%d%d)")) + return self.try(base.tonumber(code), status) +end + +function metat.__index:receiveheaders() + return self.try(receiveheaders(self.c)) +end + +function metat.__index:receivebody(headers, sink, step) + sink = sink or ltn12.sink.null() + step = step or ltn12.pump.step + local length = base.tonumber(headers["content-length"]) + local t = headers["transfer-encoding"] -- shortcut + local mode = "default" -- connection close + if t and t ~= "identity" then mode = "http-chunked" + elseif base.tonumber(headers["content-length"]) then mode = "by-length" end + return self.try(ltn12.pump.all(socket.source(mode, self.c, length), + sink, step)) +end + +function metat.__index:receive09body(status, sink, step) + local source = ltn12.source.rewind(socket.source("until-closed", self.c)) + source(status) + return self.try(ltn12.pump.all(source, sink, step)) +end + +function metat.__index:close() + return self.c:close() +end + +----------------------------------------------------------------------------- +-- High level HTTP API +----------------------------------------------------------------------------- +local function adjusturi(reqt) + local u = reqt + -- if there is a proxy, we need the full url. otherwise, just a part. + if not reqt.proxy and not _M.PROXY then + u = { + path = socket.try(reqt.path, "invalid path 'nil'"), + params = reqt.params, + query = reqt.query, + fragment = reqt.fragment + } + end + return url.build(u) +end + +local function adjustproxy(reqt) + local proxy = reqt.proxy or _M.PROXY + if proxy then + proxy = url.parse(proxy) + return proxy.host, proxy.port or 3128 + else + return reqt.host, reqt.port + end +end + +local function adjustheaders(reqt) + -- default headers + local host = reqt.host + local port = tostring(reqt.port) + if port ~= tostring(SCHEMES[reqt.scheme].port) then + host = host .. ':' .. port end + local lower = { + ["user-agent"] = _M.USERAGENT, + ["host"] = host, + ["connection"] = "close, TE", + ["te"] = "trailers" + } + -- if we have authentication information, pass it along + if reqt.user and reqt.password then + lower["authorization"] = + "Basic " .. (mime.b64(reqt.user .. ":" .. + url.unescape(reqt.password))) + end + -- if we have proxy authentication information, pass it along + local proxy = reqt.proxy or _M.PROXY + if proxy then + proxy = url.parse(proxy) + if proxy.user and proxy.password then + lower["proxy-authorization"] = + "Basic " .. (mime.b64(proxy.user .. ":" .. proxy.password)) + end + end + -- override with user headers + for i,v in base.pairs(reqt.headers or lower) do + lower[string.lower(i)] = v + end + return lower +end + +-- default url parts +local default = { + path ="/" + , scheme = "http" +} + +local function adjustrequest(reqt) + -- parse url if provided + local nreqt = reqt.url and url.parse(reqt.url, default) or {} + -- explicit components override url + for i,v in base.pairs(reqt) do nreqt[i] = v end + -- default to scheme particulars + local schemedefs, host, port, method + = SCHEMES[nreqt.scheme], nreqt.host, nreqt.port, nreqt.method + if not nreqt.create then nreqt.create = schemedefs.create(nreqt) end + if not (port and port ~= '') then nreqt.port = schemedefs.port end + if not (method and method ~= '') then nreqt.method = 'GET' end + if not (host and host ~= "") then + socket.try(nil, "invalid host '" .. base.tostring(nreqt.host) .. "'") + end + -- compute uri if user hasn't overriden + nreqt.uri = reqt.uri or adjusturi(nreqt) + -- adjust headers in request + nreqt.headers = adjustheaders(nreqt) + -- ajust host and port if there is a proxy + nreqt.host, nreqt.port = adjustproxy(nreqt) + return nreqt +end + +local function shouldredirect(reqt, code, headers) + local location = headers.location + if not location then return false end + location = string.gsub(location, "%s", "") + if location == "" then return false end + local scheme = url.parse(location).scheme + if scheme and (not SCHEMES[scheme]) then return false end + -- avoid https downgrades + if ('https' == reqt.scheme) and ('https' ~= scheme) then return false end + return (reqt.redirect ~= false) and + (code == 301 or code == 302 or code == 303 or code == 307) and + (not reqt.method or reqt.method == "GET" or reqt.method == "HEAD") + and ((false == reqt.maxredirects) + or ((reqt.nredirects or 0) + < (reqt.maxredirects or 5))) +end + +local function shouldreceivebody(reqt, code) + if reqt.method == "HEAD" then return nil end + if code == 204 or code == 304 then return nil end + if code >= 100 and code < 200 then return nil end + return 1 +end + +-- forward declarations +local trequest, tredirect + +--[[local]] function tredirect(reqt, location) + -- the RFC says the redirect URL has to be absolute, but some + -- servers do not respect that + local newurl = url.absolute(reqt.url, location) + -- if switching schemes, reset port and create function + if url.parse(newurl).scheme ~= reqt.scheme then + reqt.port = nil + reqt.create = nil end + -- make new request + local result, code, headers, status = trequest { + url = newurl, + source = reqt.source, + sink = reqt.sink, + headers = reqt.headers, + proxy = reqt.proxy, + maxredirects = reqt.maxredirects, + nredirects = (reqt.nredirects or 0) + 1, + create = reqt.create + } + -- pass location header back as a hint we redirected + headers = headers or {} + headers.location = headers.location or location + return result, code, headers, status +end + +--[[local]] function trequest(reqt) + -- we loop until we get what we want, or + -- until we are sure there is no way to get it + local nreqt = adjustrequest(reqt) + local h = _M.open(nreqt.host, nreqt.port, nreqt.create) + -- send request line and headers + h:sendrequestline(nreqt.method, nreqt.uri) + h:sendheaders(nreqt.headers) + -- if there is a body, send it + if nreqt.source then + h:sendbody(nreqt.headers, nreqt.source, nreqt.step) + end + local code, status = h:receivestatusline() + -- if it is an HTTP/0.9 server, simply get the body and we are done + if not code then + h:receive09body(status, nreqt.sink, nreqt.step) + return 1, 200 + elseif code == 408 then + return 1, code + end + local headers + -- ignore any 100-continue messages + while code == 100 do + headers = h:receiveheaders() + code, status = h:receivestatusline() + end + headers = h:receiveheaders() + -- at this point we should have a honest reply from the server + -- we can't redirect if we already used the source, so we report the error + if shouldredirect(nreqt, code, headers) and not nreqt.source then + h:close() + return tredirect(reqt, headers.location) + end + -- here we are finally done + if shouldreceivebody(nreqt, code) then + h:receivebody(headers, nreqt.sink, nreqt.step) + end + h:close() + return 1, code, headers, status +end + +-- turns an url and a body into a generic request +local function genericform(u, b) + local t = {} + local reqt = { + url = u, + sink = ltn12.sink.table(t), + target = t + } + if b then + reqt.source = ltn12.source.string(b) + reqt.headers = { + ["content-length"] = string.len(b), + ["content-type"] = "application/x-www-form-urlencoded" + } + reqt.method = "POST" + end + return reqt +end + +_M.genericform = genericform + +local function srequest(u, b) + local reqt = genericform(u, b) + local _, code, headers, status = trequest(reqt) + return table.concat(reqt.target), code, headers, status +end + +_M.request = socket.protect(function(reqt, body) + if base.type(reqt) == "string" then return srequest(reqt, body) + else return trequest(reqt) end +end) + +_M.schemes = SCHEMES +return _M diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/http.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/http.luac new file mode 100644 index 0000000000000000000000000000000000000000..79fee13be6953826131078b5d6125c30628c3ce1 GIT binary patch literal 19619 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 literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/tp.lua b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/tp.lua new file mode 100644 index 000000000000..b8ebc56d16a2 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/tp.lua @@ -0,0 +1,134 @@ +----------------------------------------------------------------------------- +-- Unified SMTP/FTP subsystem +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local string = require("string") +local socket = require("socket") +local ltn12 = require("ltn12") + +socket.tp = {} +local _M = socket.tp + +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +_M.TIMEOUT = 60 + +----------------------------------------------------------------------------- +-- Implementation +----------------------------------------------------------------------------- +-- gets server reply (works for SMTP and FTP) +local function get_reply(c) + local code, current, sep + local line, err = c:receive() + local reply = line + if err then return nil, err end + code, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)")) + if not code then return nil, "invalid server reply" end + if sep == "-" then -- reply is multiline + repeat + line, err = c:receive() + if err then return nil, err end + current, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)")) + reply = reply .. "\n" .. line + -- reply ends with same code + until code == current and sep == " " + end + return code, reply +end + +-- metatable for sock object +local metat = { __index = {} } + +function metat.__index:getpeername() + return self.c:getpeername() +end + +function metat.__index:getsockname() + return self.c:getpeername() +end + +function metat.__index:check(ok) + local code, reply = get_reply(self.c) + if not code then return nil, reply end + if base.type(ok) ~= "function" then + if base.type(ok) == "table" then + for i, v in base.ipairs(ok) do + if string.find(code, v) then + return base.tonumber(code), reply + end + end + return nil, reply + else + if string.find(code, ok) then return base.tonumber(code), reply + else return nil, reply end + end + else return ok(base.tonumber(code), reply) end +end + +function metat.__index:command(cmd, arg) + cmd = string.upper(cmd) + if arg then + return self.c:send(cmd .. " " .. arg.. "\r\n") + else + return self.c:send(cmd .. "\r\n") + end +end + +function metat.__index:sink(snk, pat) + local chunk, err = self.c:receive(pat) + return snk(chunk, err) +end + +function metat.__index:send(data) + return self.c:send(data) +end + +function metat.__index:receive(pat) + return self.c:receive(pat) +end + +function metat.__index:getfd() + return self.c:getfd() +end + +function metat.__index:dirty() + return self.c:dirty() +end + +function metat.__index:getcontrol() + return self.c +end + +function metat.__index:source(source, step) + local sink = socket.sink("keep-open", self.c) + local ret, err = ltn12.pump.all(source, sink, step or ltn12.pump.step) + return ret, err +end + +-- closes the underlying c +function metat.__index:close() + self.c:close() + return 1 +end + +-- connect with server and return c object +function _M.connect(host, port, timeout, create) + local c, e = (create or socket.tcp)() + if not c then return nil, e end + c:settimeout(timeout or _M.TIMEOUT) + local r, e = c:connect(host, port) + if not r then + c:close() + return nil, e + end + return base.setmetatable({c = c}, metat) +end + +return _M diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/tp.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/tp.luac new file mode 100644 index 0000000000000000000000000000000000000000..5375c373c9c0ecba4114078bad55e1cabf19309f GIT binary patch literal 5764 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 M&d)rZ-c|X30gHqo`Tzg` literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/url.lua b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/url.lua new file mode 100644 index 000000000000..0a3a80a67289 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/url.lua @@ -0,0 +1,331 @@ +----------------------------------------------------------------------------- +-- URI parsing, composition and relative URL resolution +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module +----------------------------------------------------------------------------- +local string = require("string") +local base = _G +local table = require("table") +local socket = require("socket") + +socket.url = {} +local _M = socket.url + +----------------------------------------------------------------------------- +-- Module version +----------------------------------------------------------------------------- +_M._VERSION = "URL 1.0.3" + +----------------------------------------------------------------------------- +-- Encodes a string into its escaped hexadecimal representation +-- Input +-- s: binary string to be encoded +-- Returns +-- escaped representation of string binary +----------------------------------------------------------------------------- +function _M.escape(s) + return (string.gsub(s, "([^A-Za-z0-9_])", function(c) + return string.format("%%%02x", string.byte(c)) + end)) +end + +----------------------------------------------------------------------------- +-- Protects a path segment, to prevent it from interfering with the +-- url parsing. +-- Input +-- s: binary string to be encoded +-- Returns +-- escaped representation of string binary +----------------------------------------------------------------------------- +local function make_set(t) + local s = {} + for i,v in base.ipairs(t) do + s[t[i]] = 1 + end + return s +end + +-- these are allowed within a path segment, along with alphanum +-- other characters must be escaped +local segment_set = make_set { + "-", "_", ".", "!", "~", "*", "'", "(", + ")", ":", "@", "&", "=", "+", "$", ",", +} + +local function protect_segment(s) + return string.gsub(s, "([^A-Za-z0-9_])", function (c) + if segment_set[c] then return c + else return string.format("%%%02X", string.byte(c)) end + end) +end + +----------------------------------------------------------------------------- +-- Unencodes a escaped hexadecimal string into its binary representation +-- Input +-- s: escaped hexadecimal string to be unencoded +-- Returns +-- unescaped binary representation of escaped hexadecimal binary +----------------------------------------------------------------------------- +function _M.unescape(s) + return (string.gsub(s, "%%(%x%x)", function(hex) + return string.char(base.tonumber(hex, 16)) + end)) +end + +----------------------------------------------------------------------------- +-- Removes '..' and '.' components appropriately from a path. +-- Input +-- path +-- Returns +-- dot-normalized path +local function remove_dot_components(path) + local marker = string.char(1) + repeat + local was = path + path = path:gsub('//', '/'..marker..'/', 1) + until path == was + repeat + local was = path + path = path:gsub('/%./', '/', 1) + until path == was + repeat + local was = path + path = path:gsub('[^/]+/%.%./([^/]+)', '%1', 1) + until path == was + path = path:gsub('[^/]+/%.%./*$', '') + path = path:gsub('/%.%.$', '/') + path = path:gsub('/%.$', '/') + path = path:gsub('^/%.%./', '/') + path = path:gsub(marker, '') + return path +end + +----------------------------------------------------------------------------- +-- Builds a path from a base path and a relative path +-- Input +-- base_path +-- relative_path +-- Returns +-- corresponding absolute path +----------------------------------------------------------------------------- +local function absolute_path(base_path, relative_path) + if string.sub(relative_path, 1, 1) == "/" then + return remove_dot_components(relative_path) end + base_path = base_path:gsub("[^/]*$", "") + if not base_path:find'/$' then base_path = base_path .. '/' end + local path = base_path .. relative_path + path = remove_dot_components(path) + return path +end + +----------------------------------------------------------------------------- +-- Parses a url and returns a table with all its parts according to RFC 2396 +-- The following grammar describes the names given to the URL parts +-- ::= :///;?# +-- ::= @: +-- ::= [:] +-- :: = {/} +-- Input +-- url: uniform resource locator of request +-- default: table with default values for each field +-- Returns +-- table with the following fields, where RFC naming conventions have +-- been preserved: +-- scheme, authority, userinfo, user, password, host, port, +-- path, params, query, fragment +-- Obs: +-- the leading '/' in {/} is considered part of +----------------------------------------------------------------------------- +function _M.parse(url, default) + -- initialize default parameters + local parsed = {} + for i,v in base.pairs(default or parsed) do parsed[i] = v end + -- empty url is parsed to nil + if not url or url == "" then return nil, "invalid url" end + -- remove whitespace + -- url = string.gsub(url, "%s", "") + -- get scheme + url = string.gsub(url, "^([%w][%w%+%-%.]*)%:", + function(s) parsed.scheme = s; return "" end) + -- get authority + url = string.gsub(url, "^//([^/]*)", function(n) + parsed.authority = n + return "" + end) + -- get fragment + url = string.gsub(url, "#(.*)$", function(f) + parsed.fragment = f + return "" + end) + -- get query string + url = string.gsub(url, "%?(.*)", function(q) + parsed.query = q + return "" + end) + -- get params + url = string.gsub(url, "%;(.*)", function(p) + parsed.params = p + return "" + end) + -- path is whatever was left + if url ~= "" then parsed.path = url end + local authority = parsed.authority + if not authority then return parsed end + authority = string.gsub(authority,"^([^@]*)@", + function(u) parsed.userinfo = u; return "" end) + authority = string.gsub(authority, ":([^:%]]*)$", + function(p) parsed.port = p; return "" end) + if authority ~= "" then + -- IPv6? + parsed.host = string.match(authority, "^%[(.+)%]$") or authority + end + local userinfo = parsed.userinfo + if not userinfo then return parsed end + userinfo = string.gsub(userinfo, ":([^:]*)$", + function(p) parsed.password = p; return "" end) + parsed.user = userinfo + return parsed +end + +----------------------------------------------------------------------------- +-- Rebuilds a parsed URL from its components. +-- Components are protected if any reserved or unallowed characters are found +-- Input +-- parsed: parsed URL, as returned by parse +-- Returns +-- a stringing with the corresponding URL +----------------------------------------------------------------------------- +function _M.build(parsed) + --local ppath = _M.parse_path(parsed.path or "") + --local url = _M.build_path(ppath) + local url = parsed.path or "" + if parsed.params then url = url .. ";" .. parsed.params end + if parsed.query then url = url .. "?" .. parsed.query end + local authority = parsed.authority + if parsed.host then + authority = parsed.host + if string.find(authority, ":") then -- IPv6? + authority = "[" .. authority .. "]" + end + if parsed.port then authority = authority .. ":" .. base.tostring(parsed.port) end + local userinfo = parsed.userinfo + if parsed.user then + userinfo = parsed.user + if parsed.password then + userinfo = userinfo .. ":" .. parsed.password + end + end + if userinfo then authority = userinfo .. "@" .. authority end + end + if authority then url = "//" .. authority .. url end + if parsed.scheme then url = parsed.scheme .. ":" .. url end + if parsed.fragment then url = url .. "#" .. parsed.fragment end + -- url = string.gsub(url, "%s", "") + return url +end + +----------------------------------------------------------------------------- +-- Builds a absolute URL from a base and a relative URL according to RFC 2396 +-- Input +-- base_url +-- relative_url +-- Returns +-- corresponding absolute url +----------------------------------------------------------------------------- +function _M.absolute(base_url, relative_url) + local base_parsed + if base.type(base_url) == "table" then + base_parsed = base_url + base_url = _M.build(base_parsed) + else + base_parsed = _M.parse(base_url) + end + local result + local relative_parsed = _M.parse(relative_url) + if not base_parsed then + result = relative_url + elseif not relative_parsed then + result = base_url + elseif relative_parsed.scheme then + result = relative_url + else + relative_parsed.scheme = base_parsed.scheme + if not relative_parsed.authority then + relative_parsed.authority = base_parsed.authority + if not relative_parsed.path then + relative_parsed.path = base_parsed.path + if not relative_parsed.params then + relative_parsed.params = base_parsed.params + if not relative_parsed.query then + relative_parsed.query = base_parsed.query + end + end + else + relative_parsed.path = absolute_path(base_parsed.path or "", + relative_parsed.path) + end + end + result = _M.build(relative_parsed) + end + return remove_dot_components(result) +end + +----------------------------------------------------------------------------- +-- Breaks a path into its segments, unescaping the segments +-- Input +-- path +-- Returns +-- segment: a table with one entry per segment +----------------------------------------------------------------------------- +function _M.parse_path(path) + local parsed = {} + path = path or "" + --path = string.gsub(path, "%s", "") + string.gsub(path, "([^/]+)", function (s) table.insert(parsed, s) end) + for i = 1, #parsed do + parsed[i] = _M.unescape(parsed[i]) + end + if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end + if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end + return parsed +end + +----------------------------------------------------------------------------- +-- Builds a path component from its segments, escaping protected characters. +-- Input +-- parsed: path segments +-- unsafe: if true, segments are not protected before path is built +-- Returns +-- path: corresponding path stringing +----------------------------------------------------------------------------- +function _M.build_path(parsed, unsafe) + local path = "" + local n = #parsed + if unsafe then + for i = 1, n-1 do + path = path .. parsed[i] + path = path .. "/" + end + if n > 0 then + path = path .. parsed[n] + if parsed.is_directory then path = path .. "/" end + end + else + for i = 1, n-1 do + path = path .. protect_segment(parsed[i]) + path = path .. "/" + end + if n > 0 then + path = path .. protect_segment(parsed[n]) + if parsed.is_directory then path = path .. "/" end + end + end + if parsed.is_absolute then path = "/" .. path end + return path +end + +return _M diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/url.luac b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/url.luac new file mode 100644 index 0000000000000000000000000000000000000000..4f80f481622b994c8eb911fd2d13cc72bab55bb5 GIT binary patch literal 10974 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 GlK%zGZ<7`P literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/ubus.so b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/ubus.so new file mode 100755 index 0000000000000000000000000000000000000000..8ba85549618d7a9094371c45009f95148035df0e GIT binary patch literal 20483 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 T*rhYC2-jw@7L40Fm>Yp-sydR& literal 0 HcmV?d00001 From d868628e86377d433c6dcef094ce804c306f1d30 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Fri, 17 Jul 2026 02:01:10 -0400 Subject: [PATCH 087/105] test(lua2cpg): limit openwrt fixture corpus to lua inputs --- .../.gitattributes | 4 + .../usr/lib/lua/datconf.so | Bin 16530 -> 0 bytes .../usr/lib/lua/ioctl_helper.so | Bin 24906 -> 0 bytes .../usr/lib/lua/iwinfo.so | Bin 24579 -> 0 bytes .../usr/lib/lua/luci/ip.so | Bin 32771 -> 0 bytes .../usr/lib/lua/luci/jsonc.so | Bin 12291 -> 0 bytes .../usr/lib/lua/luci/template/parser.so | Bin 24706 -> 0 bytes .../lua/luci/view/admin_mtk/hwnat_status.htm | 56 - .../lib/lua/luci/view/admin_mtk/mtk_hwnat.htm | 86 - .../luci/view/admin_mtk/mtk_ipsec_view.htm | 85 - .../luci/view/admin_mtk/mtk_web_console.htm | 94 - .../luci/view/admin_mtk/mtk_wifi_apcli.htm | 1716 ---------- .../view/admin_mtk/mtk_wifi_apply_reboot.htm | 67 - .../luci/view/admin_mtk/mtk_wifi_chip_cfg.htm | 600 ---- .../luci/view/admin_mtk/mtk_wifi_dev_cfg.htm | 1380 -------- .../luci/view/admin_mtk/mtk_wifi_loading.htm | 90 - .../mtk_wifi_map_ap_capabilities.htm | 257 -- .../mtk_wifi_map_bh_link_metrics.htm | 220 -- .../admin_mtk/mtk_wifi_map_bss_cfg_renew.htm | 1156 ------- .../view/admin_mtk/mtk_wifi_map_bssinfo.htm | 363 --- .../mtk_wifi_map_channel_planning_score.htm | 407 --- .../mtk_wifi_map_channel_scan_result.htm | 510 --- .../mtk_wifi_map_client_capabilities.htm | 146 - .../admin_mtk/mtk_wifi_map_data_element.htm | 216 -- ...mtk_wifi_map_display_bootstrapping_uri.htm | 36 - .../mtk_wifi_map_runtime_topology.htm | 465 --- .../luci/view/admin_mtk/mtk_wifi_multi_ap.htm | 2522 --------------- .../luci/view/admin_mtk/mtk_wifi_overview.htm | 557 ---- .../luci/view/admin_mtk/mtk_wifi_vif_cfg.htm | 2790 ----------------- .../usr/lib/lua/luci/view/csrftoken.htm | 24 - .../lua/luci/view/empty_node_placeholder.htm | 11 - .../usr/lib/lua/luci/view/error404.htm | 12 - .../usr/lib/lua/luci/view/error500.htm | 11 - .../usr/lib/lua/luci/view/footer.htm | 27 - .../usr/lib/lua/luci/view/header.htm | 38 - .../usr/lib/lua/luci/view/indexer.htm | 7 - .../usr/lib/lua/luci/view/sysauth.htm | 75 - .../usr/lib/lua/luci/view/view.htm | 12 - .../usr/lib/lua/lucihttp.so | Bin 12291 -> 0 bytes .../usr/lib/lua/map_helper.so | Bin 33161 -> 0 bytes .../usr/lib/lua/socket-3.0-rc1.so | Bin 63610 -> 0 bytes .../usr/lib/lua/socket/unix.so | Bin 45922 -> 0 bytes .../usr/lib/lua/ubus.so | Bin 20483 -> 0 bytes .../usr/lib/lua/wps_action.lua | 50 +- 44 files changed, 29 insertions(+), 14061 deletions(-) create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/.gitattributes delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/datconf.so delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/ioctl_helper.so delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/iwinfo.so delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/ip.so delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/jsonc.so delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/template/parser.so delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/hwnat_status.htm delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_hwnat.htm delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_ipsec_view.htm delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_web_console.htm delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_apcli.htm delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_apply_reboot.htm delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_chip_cfg.htm delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_dev_cfg.htm delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_loading.htm delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_ap_capabilities.htm delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_bh_link_metrics.htm delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_bss_cfg_renew.htm delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_bssinfo.htm delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_channel_planning_score.htm delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_channel_scan_result.htm delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_client_capabilities.htm delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_data_element.htm delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_display_bootstrapping_uri.htm delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_runtime_topology.htm delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_multi_ap.htm delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_overview.htm delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_vif_cfg.htm delete mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/csrftoken.htm delete mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/empty_node_placeholder.htm delete mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/error404.htm delete mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/error500.htm delete mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/footer.htm delete mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/header.htm delete mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/indexer.htm delete mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/sysauth.htm delete mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/view.htm delete mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/lucihttp.so delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/map_helper.so delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket-3.0-rc1.so delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/unix.so delete mode 100755 joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/ubus.so diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/.gitattributes b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/.gitattributes new file mode 100644 index 000000000000..1b673dbd32d0 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/.gitattributes @@ -0,0 +1,4 @@ +usr/lib/lua/*.lua -text -diff +usr/lib/lua/**/*.lua -text -diff +usr/lib/lua/*.luac -text -diff +usr/lib/lua/**/*.luac -text -diff diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/datconf.so b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/datconf.so deleted file mode 100755 index 22b7b855eb7f59d97ab7a8d4e54cecef3a4d6993..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16530 zcmeHO4RDmldEW0%An{`?BLOl{bHYwSF#%zNBW!F=Ct=}KwK32s9W%KTrvqdTN!FcW zFl~IcX@`v63>S$XJ3us!Cj|}7C^J$*9g@gDb!cE(6;lPrKO@O@Lt{5l3>ck_e0`q% zIjuh5Id-ShPG_>r-hTUh@9w_uyYKt%?x*G9l{I&I97mYs6+aZLt-n?we)GaA{i0lq z7wQdZ$64>(kDXsqh$)5aUHy*7tKM7H``%j>52k!xcUnLqnbdm^o(cJuE7Z4K;il|Y zRbk2kMJ86i9k0t{yE5I*Rc_GjOi5a%d(ke#f`UHrw@|-bqsPl7UzwKAlzMpaIkc}r z{yg%H$k!kx}xfq!$^CO!?{%$Z8lz)s|g3P{{ZbPQJZbDv&Odgja2ap#d-;R78 z@*?C}$TuU;N1lsJHO)l+BJ!tA6Exv1SsF~zm^Oj0SrX{Kv^E*%1mg%xwmkV?` zU6(A#E9InJvhQ65VnPDL`%4mkcjWFw0pC&ol;iycziMfy*>TgIFCEJJe8s$1CoQ=3 z<>P<*yD5F~TOa#n<3F!_ZQ4uY2EVz;|Mu%Ie*bUom~#2oFaLJRD=WY8FKGIS$qP-b z(*9}_dZ`h=V?*N4z$kU+POTs_wrjs@%0d4X;Knv>`j#C1(7m(uPkWqecc7nRD6{c7 z9}3Tg|29YeXMty<=UinQ_wRvcIOm$=*Dc1y1$Hf-u z1n8K4Ecu)u+I2r%cbR6&en#Uh*Bz!sntnYOZlq^aAy#PoipJm4cvSP#rTaJhBsG0M z7b&D3&Cg2Br%x}$hRf~T;*sXyrsmphkzjqprbgh|&}d6-us+rji!|5O#%l$u zO)ar4^$qdJ=14Q^HNo(fNO)_De82Up;b6FN+qU|+;A2Zew6V5Mwuolu!C5{bYz}312J4Q)JHSVqum6<&5_!8Bwo8Q8j)N?8d|mm z8zS3PSF#4ZShYxl;t^x0RvbV!Fe81nHrk@tCrx9M9JV3yU`8>JKA4deJZ`FwL^GXk z12dx?*T=xm#xcs%faxT-aR=t4F0x(NNCT!y%7gBqzjg6*soLuc?mW)S(S|~n>9bHfh#k59@!aN!X2c>B+Y9UKv1Rv&R$xr#{N~jG=Y-yup3~<$gPZeZzY7oX z5Q22xg}=)~2-1)Xze&>%yKo;j{7kp00BXivuIUS0_&R-9Ds9@G>JzD<> z7rsE#?{?vS-TxjJZszNN3oq03?Jj(o?*E7j_g$~_+3CW~d>wb;@_>sbJuckL*GU&% zsP*6L!p(f0bKwQr9{OFlnXmIMJXEUqA9CSqw7kPEyi3ooSU%RC|6J1-xbS1T|J8Sl zr8oPfgbO$OrFIu?_DfAG#`a_OGuvIb+0X28;btFoz=fNAPNxeu`xA41HU4YrX1`Rn zS-APO#n;;{oZqBO+HK)jI5TOFh4b5&Ne3+aY6GG?V&P^j=Ic%ix4+LEw{ZKM+hgJU zerD213pZyUzU#Gcet$FRoQ2!p$NDXt-}g*9Z{gc^2+7#3;)wywt+WEqtzp`z>5AY4YtV z3%|jlUv1%LN#N`C7Osz)vQ@~!^^sEITP)ms@8pYwh3liKq~C4f`sgU}b_>@>Xo(-O zaP!@iFOFNdKFUh^9t+n;R*9dqa2={7-fQ7H#7O*{h4b6CN&OanlL1klxA2=Se8|G> zZ^XkE&Tr@@jqLpC(Gjmmj_iEyNRKzzmw+idiNLbwJYwm{&Yv881g^vPrK_q_7bc0+ z1+VA;)nG)ff4glP4Z{ z#1ZQZO~1FOBk-=`alk9)vrawDWqaxheZs2Oq)>WLa-1K@aZY6P(uVQKZ`;P|6m@gS z>vUY6B~tH0U*sdap6J>ub&@#4cBJjTyE?`9 zm+>CspD}$9qy7eg4|r#EK))T~J=H1d4sqrI$~Tbz?U_ECvKLWqWuw{Te%~ec z&s=i9@EPUq$s)J!)5`sX_Af_t(@!|xrCXF=bZg(qI=?O%sY_`qXH|c{gADHeh$$aX z^POGpXOF%K`=_nY|2Ws2WBSGAu=nUNY#?>LkamiA@iCr#w*7@GkEX8YT)dQt7mPW^ z55vQZ7eyWWAUAz56?T%ef5rE-j?&kZ9_TxL4b`c(zUtI~_gcs~qoW7*70TGejE>TS zinioRTE-j3AroI-h1~s%{D?7rIUa3O*7HW|v=i9zRH?%QN{4T|^z{#@+y2pQ*H=$T zejjxczhvw}Y&H73hbDafUu>V!0hh2M}jf94(S@wEd$r!m&SfQTLsh7rlnO zl}_*X<(da;y`WA_%*f?q7vvZ}`o#H9ZOuEHf(>wBKkZPQ<-V%ws2QSH?oj zs}D3Ki>g!Ih^u{@s#A;;)UWVD|5$5wAf|$^PCK6QnOG)<(T28GL0A8HM9t@%K1a_J zV`yk3%{VOQjrD%a|9;sg{0KPhgkzfL%ZSIYzcZ#yKi+?!Z1Rkw`yqxo=~S_K=9t(F zJL#;|bb;;4PJRx(W#hNU%k@{TXPp(=pFXyp6^jR+6Jnhs;FE~Cv@h~|81pLVOV%8k zzQDU$8ySDKf6}(_M{=&)osN?@Bkj0T@yGel@u*YFF2^u&nEEa~q5LO-wb|JoFXD9Lhw0 zpr5lpDbp@Bo)9~(V}HhvO8w-24);Wg4!U9c1ln>g*$-uBAGX|?_VZMG=zNTCQs+kB zYTXkZFOT}1vRy5YTL+Rq$WdCQbj$sT;t%VcUehx=(d+kbr4Rm?d!SCK6Kjr54C&D_ z4Sq)?DVHr1+YQX{rvj^${7OEx7E-n=x_|1#!+9-Up5!>;<)gO5Sa19pW6HH41YLF` z{`P$h`%~`S5OX*#$Kx0d?7{2}m0!$}en+2tWW1j~CvC1RAd>V+wyQ8Uq~q@(lgRbrEa&ew*J?w5P2aUX5>o zr|kPq?WY6j^bq$-MxL&Zvim9eSL>&a(+`2pr{c8a->mV{W-K44J$&3=u*cQ*V%7ob zfqpJ+1b%MEfrM9d(dWqnW&Q$W|G)5ShcRt~vM=(7HNLaWvoJ~D9>l!-wKXS!#EnTw zk2v!%aLy@tr=A^T?iDzn+2-HO@t3W3rGvYsdT8H!mM6oo2ex(l=g7`yGk!+?S}|sz zP@MtZhp&d_R?9uZz#@4*IfJv+9&z&aU|&gL+$GP)>+!4;si0;0qHJU`B;7{7~(=Mt1%ntBfDU#vT2T{G(5^x8we3;2tF_W#n3KWTw32 zD=|T>`CRL9u-bF^8+RnRPaA&BNv6KzBz>UcddmIUfVTv)&QyCf>}87)e{jYw?!Vce z;`~01I?q1gW7Vl*xi>48dmjHs>7|M3{$-*^+&kw6@xaNE^g7Z9oWvUL{}R&?uQsST zy@Ij&zvPGP{$+_P>80#j>3LR1pcgv99*?}Yf0$m%dAj(5ljIz|>76~F^JM0TdQE^X z9rWmVV%)&_SI<*>D&567F>}PZS93Hov-ec>X&sl)m)U!&Ih)ooc+c;jv&(v~^I>cy z_B%h9duGh78uy}FpL)FV+}}xk3=|~R1WKw?;U?;0j@)aI2gZcb<0>X_KIm`lj_9IZ zKHg5*vEm(PXYzhh*&+SUtdAU*F*4Kt>Hqou$s6Zr5ORef*RHPWR6@$7&g!&N+TAqR zP2aE8_?!ox)h}b6iVM+Qgm*JxW3~B=eZ|uDDF07AGv2B2igi7pQL;kLx0IZ%9`QJ_ z7Ekf1_VLi&I)$@cKlD$&24M?~GvRV&cMspcwA|>2{$`)G{poa9ah^zaqc5I4Ms^;T zK1iRE@ftCP@y7VwFk+4IGx{8T^Zd_g297zU~q0=7{2V{P02AkHeV0 z?v5mVi~P~&7}u%q4f+058|J0oJ6nDu>G6v0hdm`*pT_<+HeOuw3Unc$m-8sq9L(-8 z{zP39Bmd?sr)cO;*uU%j5qK4Eo!(Iby)S#GI<+=mbSYcJc?jnoo@M8Fi{GgoZ`&f|)3*2^Y?1n; zoF96MPPP3zd;t8zMi7stOTAJr*Lua{%e=ErG1f|Z%@;EE(jWZTzsEe&PYr;cHZutQ z_1#*XqD>4UCUXr+c#0lpU3iNgU+XP8wb3&R^fQ19iGTb4Ww7sJ>HpHcO>DYx^gNI6 zK^-qbZ=pQV&9#&K#lUX}{ZdZKz-RK?>M1$(0me*ee%FJ4T$s)9+hC=OA+R~1pPjZXZJVR;OIpedB>u0th-(bB*h9@`?#n(?@jXeX8F5B{w|fjx5(cw$=`DMyG;HrE>neSy;wD0tlBSD9V}KoEWSll zG*w3H*T(AWaNlE=_qGj=x({CdAL(=)&j(Q+K-q(m0ig*S_kNV8QRaDsIES(XW&bQ!Gl)R59a41@bvJmAKls=Sy ziLwmk9+Xd@JdSb* zC{Li=g|ZiA)rZgz{|GsPcKVNzg{U$)4Me%cPQ_$ zx2n~1&l|75EdM-M40t*670}#38lCvJmUYMl2(q2TDyEbj8h?0Ppf#_}Te;V>&sjb3 zjo1IJ@8z>oLy3w+x1X=*LuL5luY|id;J+K89#y)xHavrd3eb%}9qY+OzLtzufE5A5rG{L@xLH`W=!sdB0Y`@)w z`G6g?FwXT7UzbE+|p){75x8$fp&bkc_OyjHe)zDz34YZYh`pqUK+TdnwC zeQ4t03018HZTVGu$L|}rKktC|NzYTxS17kv+C6w{cMcUFE~;#u+ICIl zw^o8`}C1+Al!dE#_EA#PSeRi81Ce`H)M1 zl>rOk+2}y>y=GLG%Rv+U1RC}o0?iSt?=oOgv`hZCCA`yl>L}i3cX!`DDRU`QI9UaNM|6c^+Eo_*J&> z2)4`7&k)DPx}y7$x(-m+0mh3e28<{3p7MUn^H=EM)luCsR{tUWK?8r#z$a_K+&4D& zi_LvvbAQ;}KQ;G*&3#~V|JU3fHTQeX{ZMm%*WCXco~`&W_dCt~TXWym+^>!5ODpF7 zteg7<`n|a?YxpzwVa@$l-}S1c;ZNvGN`^mkU)Atu?xPz1LZzzx8a$=%5n*X0@?!1}*mvXJ@CUb9mmaZ4*65n!UGWW-| z+VMXgRl;&2(;m&(rH|5$1pIn@gj<;k^rby)!(}?6-)mSL(sdJN&03_l>0-4O%#1s% z8&1&u8G2J{SYXR9`7wI?NY~4Be`(iyEW?uiyIN5=QpkFR>F|H4H2k<#U@+AhBA>u*dbb; zJ=u1<;Ha2!>EiEDWbOW<$lv`%VS|dFatVG`hKN-;CrBz6k>w(?oQh+zDk{#D#cSRQ zHIS%SonYAGV-lxgrS?{oBYl?}vLw(W^`)y{8&w2B#nNmx4ZJx#hq0N6uf;PN5Bc~y zJhSjj!E+U!iFiigQDHU`Ie4zXbG2lC4)J)Irl~xLb|Cf^gwoye^t9U`_`_g zK6mP+-8o--`*)}QF#70M-y6N>>Bh%)|66y>U;iM~KCZR(w&_26^1l1CHoE%W**1Do zUEQA_d2hUr-FUM9>tEmV?50V#_iytp8vWB{N7{8g_Oy!)W0`|AhzqTD&aZlsDCGtz zU*!}rOVaQc0Hx}#7bt+^29tAL8adQ22a_`d_(4o(FQnm*Pb2?6$RAA3v^4lo8vM94 z^`ibTnElOZ?7Sw8oDpgGyVBU1mxfQn++g*6It@M}jo#&H@blB~E7R!xca9&yc1b6l zfWIglko*QY0PTVu6i(vF#>n$Ny+{bPa(t(EF`JEK(Q?jCyzdYLezKpdao`rtAHnv@ z&KM>j!r$W|`A4YX5ETDgNE1Kzdd_AC<-oIB+PRYkJ%kwl%a{)_;n&mPh+s%Nk0DL` zy>dW!l;<+`Tgfk{fdHXLHqygL6TV0eQVRc})Vo0XAxec1-or!q7j5|ON&LGsU?V&y zBDO{HOXc8hf~Igr@@r_4K-eiF_7P%|pGy-2!V?nDd`&G)A?9mD#C*QM=6XujH#glC z1lbVm2(D}D3gWiz zgz7p2H65*uP0c|Dq_3^3bA2Gt+}hdIAsC&(P-9cDxq*tf+iIFxDB;^!AE<9==wS72 zf#xP3aW=Je`Z`1Pp{`EJBnD-)HMIm<8-gs*+Hz;GBLq-KQ_DJELsMs4bNv?P4b@kW z@qpGhHi!>3GG1LqH4*g)bOh@|!BG8Lt)fyW)f?hO9@$ajb$5lmF#0N!sP-Oa%kVM5y$77%p@7yrox0Wh1J)~*W#e4Vp9TW8NDDu5JQzOtgY z#5Zqtk?2(XuYBP(4kZWY(yH!q{s~+mgs3qvtEmZ7jFa6Y1*>(VOAZtvIX>)_>qEs? z>smEWD!!u6`^Af)_-j^)id?ku{gTh>Q}OF1Khwrl5g7hMkSV(fIm0?5M_9eS^wGHNL)1JfZPv&8b3<#-FU1h+ozCa!BR5F^x~_?nIFN z#0me%se@-_YJ53WaDJA?mtB|hb2Yxcjy5zt&7CR~YW!h}iMUAP)7o2wB^sah11gvr zUtd#~YkYmrQKRwInx15>(fIlv!>{q>RLxbd*ZBI{y-nlGSDNEDYkb-#sSwuqv~N*i zm&TVM{^4?C*y)xL+yL^Zx#ns8Ak zG``x~QAUr(pURQq&#M}r_H!!4G`?I?@T~r=KR(p&VpjjwXAVYPzE>3fJorbjO&|No z^1iIoH(Q1?|CM~h_3!FI-jT6RvoFiZcpj&Rf}X6=!$B8o^k~p)H98ygJsN!#=*Kns z8qmizIuG<|jh+nJ=}79I4tlai&j4Ml(X&CX)#!Pk@6qT*pdZ)h>p>sa=$k;F*63o; zPG?g8GSHJX`WDc|8eIi?twwu5-=op1K|ik1p9g(hqrU|Dv_`K5?Q|veH-et5(RY9@ z*60?{Yc;w9^gV)(pgkRC+0tj6Y+?V_rw<)K+f%fxg|26U+NzW6Zt>f^(o!|Cvi&RAwTB=7n{S| zx*B@`dIrtp4f8M9b0a8UrBuMV%M5|*iOAyk8K}zX4y43rlS1Q=t~Zg zf&80*rS>D4CZ5q$wghrY%x;uzAMT*C>^w83TNgedxn^G_$C%wWLf+YXnRRB4*~j~Z zf4b#xhEK*^8HZ!QQu#Zi?zzDBLid@ONi4~F#fHNggWJe%WZAJ!wuLaU38>>Hq@jy! zKk#MCL-zexmVF00xV^mIX5Tc#RA0v9_^#%Se^>6)AqK)ADZry)RQ`u_xuUh!;XT1Kxf8XSJoS$>{ zoA4XyrT0nm+rzR>%5PMMFm$8tTVi|gUgont^BvIlgN6+9$1~&)=-WzVGuT{=2cpfQ z)2+XfI$x#uTC=-<>+=Ka@EBXF*Ws(Y%;0tCkn+&BY3ooAjNbNC2l5}U!(*Zj(fKemAQs=iQe#~5&ti!*Gb{Nn4ddIQ8(-^a2G2VP zUkp60ANsdGEBini|J8ho|6)Sk6s4czFX2B{$o)M&1%Cb;jsGad#Vq55@gPp*Y29_6`3tQ*=&L*(DYSO0 zbhOa=s!B&bXFZ_OY=(8eO8aM6J5@S5!@@f#Uu2c3w7hoM$ak>Bu~5zDl$CR*_1F=Udn#h_cc7)(n-7&9`X2;4;kx7S`b+9a&%%sB~aTE_Ie^6UTk5X zDAH_+g}tXp`GnoW<_XiZVa{F8HM-Y_mWs+gdo5Y*TaG z_Y;1Lzsc8X3HwS&&IZ#*If@No1wv9>FKe3Cl>7`>j40n2@|fv*F5^mSmC4f_bm9fdU%t%)Jaz9x3x zjduh#ee|)=@eaKspg&Lc0`Hgh-6QMcp!FEq^-G+OHBnUf_9ltP`?7_xKptWGAyfJH zDus^;{5*xnJEhuV7I+FjT;bUW-geU!{!tTCfRO)|CjS$p<1WY2n9E@~v%)92OxREU zAb&axAt#6A0HbtLzS8;oG&0ElT!vrBaM@(UH9KBInq$KPdl5AGkwudAyrAKGC?D+ z4$)5t8ud{7896^eX76IweH!D=8SDwpVoW-VesYG^`8c-I-?7Cu`*4n$L;aoaJ)eY) z*dx&Xn#NYn`yqIcmDqDFHoNm6BRNK=pXZkVm(G`yc;0|{KE>9NHd_aq=gr^3w}b2p zKln)aQuHURbIp+!lR7;VU#ss62cKsyLI$;$K4(-@S@`BS_Q$*rU~H%UsLwlZkZx^W z9|a%tJ{uK%b~4G(<{9#*dM`dkSF{Z^e=qRZ*h&47)`29S`*SAr zWF+5DC$FnM7yVOWE`ptjdFTMi+>U;RIqh_A_@o2x=c}A&_Wg$94^ao?;o%sT>#|~- za3(Uw(fv)*KW0aF?{+#9WZ27?OY`@#_89iP6u*c%=ZCQmz5+4Ej&KI6y$NOV&r8fj z)*f{t?@`3aTa3KputBuZDCji%{()>jjF#gz+^b_^0`sWCVC_V9-lb#MXf9`u4b!e; z41sw-VWh70I>r>39X59pcN4}>ltaJ# zd44W?@5OiGwM1h);O9-$&uP5NwP20-G@AM`$e`#ryFrvnG!;4>?D&Lz73k5jzkUBX%M#LF__Yia6s?L1x(@N9K@2 ziFXon#WCoK{fPBBPCanaz`NQ*^n-Z+9!#ze{f=<<*o@RM=V`*VI=asWGORN{8aU_t z9rE?@(1G%?k_;>Mb;xdY^E21O)Gwgt0{1^}4&? zkY#>HWq|uN@gWQ20w24v{+L1Q@fi5=?T+qW5pFx$kk$;~-vj<>;Cr|4=*C=kn7x1Z zDSQjyL3aMjr%W1O4EXs;*!m=7=9=C3JhoehYh$6WCJ4r5#7@7bUIr`Wa;-dOde^PR_cc-S`Q1oW#F1!FCbW9;`j*tP?* z{19(!h13W6r>ouF4&dF}921<(v7&c{o9Z=#HBa=SUMpBA&x?Akh%I+?-{X>J#qh&k z@*CALL+&FfohkQ_lpbj=<@-lUXPHaSl3yvEV=m?UMoQ{wAn)S;bkI8Te`vH>k8@v+Ix_$eZFSX@n>ghyZ>qs9TD)j z9}BTh%eXq5)~D2W@cCga>ABY#_SpNuR-2yFHa$O9dZNj?{1teMXg;92r08-cbh(BM z)aAG7`iV`~6NBj503Mys4b+v2VrKedBd~EH6>VFzQHHW@f4L${yy=veN1P{fDXJ zN3BiwT%|jl)O`_UlK!2T(CvqA1AZm{M$m6G|MoaF|Mu8)F1G2+Q92_@o&S-db5e@V zNam;OeA1>9-z`oVe?LS%_i;3-^J%5CFshHgXouwZyU!*U-w{rc+pFZpl5!6!x!0wa zd%sQYe>zg-{#?m5#wPmU!%A*Zdb#a3xv$#f{)>|9Ps-h<u`xpK0f45@iY00mtolne^e>?H-5VO znKtDabot|K^6#?AuT%2FN%?t7J|D-Decl*`d5^~I(ofg>!Jks! z?=RT&E>(J?Nxe+zE&p`+uiNC8+T<4~`Np_JyPQKAZvV+^Toa_|x!tDcxJ}PvgXn1l58tCp)q`_V+j<~H zkK3l_piR$xgXmca9i#{jT+vg6Y%S`%iuF@s@9`*+_E@ure z!ltXkrmI@%@+Wm&mC!XT(HFx1)%pK})bRm}hm<-}eR#i)B=ufI8Q!O{|KfSEoWy?Q zS2lU3P2MCWk6oE~PtGcN*uUB3J#UkT?~SI|H%!ShlJa_#yyXMsea|M3J}1`O^aJE` z8_cAK3hc>#fi&M!)42%t*nBT7_uw}(_Pcv`;v59@$H3ui0cY6mLpaBfpB4Ww zaYh(_h;_%~&O`CI3r_}~A&24{Qa{6bm~@q-p3DB0^US^{xg9t=+>Uce_1OZY@m+`m zI2WQiIM{f8<`M;#)25&k^}%_}H;~8I_A*tTUBhj6YqIlH zp0xdXMH>RUNW+dpnr#0V>)BrDB3p)or}P?MRC-0(Yh@nJqR^h=v%lZGpR89LX>KQ* z!0no#@EJ+`uQmJyg*OF$4Dh`V;Y^Cot+1!U`OeQ1=T|t#A{z}l!zxOiEjm#~`Snf6 zBinK2(;dURcN*zS$umjT8gom&fwNrnC5riZJKi-v-Y;5HM6hqI!T3RE@X?{-3?A!^ z+I&--w^tyK*EuZe@P?{WRM2{Te~3K!otqwq#Gh37n85!Mw5rdyk(S@NN#I`)_#+Pb z&Kcg1;~M@kq>F6!Jf-mToipHnpyBr*Ex&VBQI}>-)C~?-%%oHGC`5m(^xpK%1d${MU*$`U>Vl8Xseb|AaLgwPScTctxz; zT*PYM15em2ban|FjyM>;cLtxdYc^C}t_?BRP$)hdxGb9qzaHzWA^e>>;)n`cJPBJS zaa%~gYX6xk&k%Xb70m=)qG1h{=NEZ5Dq3QfXxJ-NUPNH$Dq3P^Y1rXN%kOR_Y@Vj@ zF;Tui!+&ytj{!#cwDM`+k{3L6pF3>%hd*w=x*yf$$!rO*4^ zVje+%$m4DBdz8P_xWU30f7DoHCdZ;TQBJkpF=ekQWWK6s>9dzL?Dvqy&!TcJ+h+82 zyUkI^_)Bd5zGlZmNaH;@;*d6fThS3whi_@vok-97uh=|tpv`+A<1exKKFyA`Nb5G= zt>~z*`7RAxhctdifBAmK7*mih-&ZE&HA2Q;V)Gi!j)h3;Hs7k~n9xzFVP_!ysu**R zIKpDgC=>YI4nvGzZVjI&@)B)$y`p6sF4C~$kfzVhCufS!tY?D0+`75Y?=P>vnp+$< znh%p6eAn)9ugi7#7k`xXXZien8!PQlYravMjqd>a4O{((ARqqQ?J!0C9UA@(CEFCf z{7BKV{(l0k-qBM?>-GN~=>LuSe^uEe>;ICpL)D-Bqij5k^li5KKd;K0!hg?b`2C4I zQU9kDE$jaS4Z8g_rA;UaTSc`!3f&tCUT0&2+0~(`=O| z-}PG*E!%pThMl7FmioKJ0Nn*J@X7(k`QlyyQ~|4g zerJHXUV!L9uNX-12i!8?OLNZvmrnB3g#&`Y_0{p8`f~<;N}s%c0330}K;h?%#(G?l z(7>j3igJYN5VsU?2ET5AgG7Ecu3Vs0U5$HIjdum}uCJ$`^b^eii08@tc^tsGj8(O^ z1UbJ9y8?vT6%{<;F2l#s2vw~iqpOo7iBQpi`wS53yd}mmm{?e`uBEjjXoS``bs9~L zMny{~*gU7QzT?hdbF(4thv=y9;4(gka_~k+$e_}=1tJjal+|ONU?A9ZXRyI2bJtX_ zoW)_J#~W;E!1WEpp5+O)7gmsGWk-`O@eKiF<;Y6`74Hg&W@1S$1aRv4=xvbDpg z;B}IENe;x@;S$O(VBl&8*aM+HZ>`%?E6n2gM&(>(UjwBnP(6&wc^aQLhfbF!aq~6) ze69QfjlV$SFVy%8HU1)vzewXR*7%Dx{t}J9g!5%9$tEyxQAS&9OJ~q1beF84Di6qS z?+SK?jKWg%t3mTsc?rD&OoW}hw9oDJR+q4v;*#rYtE-JlchzkqpXjP;N{Ik{+-RSttfZ`B zbs2ATWD&orx~iniS6Sh$bk~+(V@-kh6;-RLAQX8D(J!QB0 z>O9p|%YEqXT#AsB!;zAbG{;$7Nn`MRk?0dUcs+rL=fe%Z8TLO?+r! z_$>*;_kP(hJlF^GJHxUO9o8jNBc<#ot1J6!8_`M-}01rNA!VmXxH($}bHP@&6gM78pnpU)=0VJ+9 zYEc(95y_@;F7ZQ~HqwPiGJy7N>J0eWLY^tWt_ggN-YKTNq1Rq(n>v+@Qo~ zZ`^}~B@l;je)8@lPBPH)5-34?YJD}Hiq*b~>XO=(hFEQhYoA0u>`Aek->hU*W0YQA zR^_WKE3I(*N~)`>%1UZ!eNel~Yw+SUHd2^gzp?EGikm|evFYn%JZ1g@&Yv=WDUVy$ zb`o&<6gaiv1|H8x+}agt>k2VeSs!rI#aQkp;kcU4jUpMf_8;Z#{6q8RVRz4wviB`TKnp%b8a+-(`Dsf>M1#eI|wxon+ zHO8u!l~z(*$^XLNE;Z z;!5JtzC_<2&{~RC?cS~cUuENc2{pBJVI9Ydb9^ViObiUY8*gx%H?397^|ul ziNSYCsHdndE8|zBp+Dh*F#}tJ7VIO`J!2%{Ho3pxazIz{+A>D_hmOYjK+tHxJ#$7w zYfzNME-O^u)M6-nu%$zd1-?K_i0O1w!)8vav4QkazCJc!FraM`^On0kvn!kjlqm6wT`(34boYiln$BBnPl0)7~EU~W;?`K32rr-GCp!oz+Pk3S*u{rg7lAK_e=%zp&gJK?kE z25QB7OvwPVY+ON0Be&b_vn~o-^BeBoG)+*3i;gr7EOLL^3Nil-%aWsz1z8O^8Nzn zDUL8u<}1k0MPKE2e7Z-6UG9Ce_s??8&3OF{-sh-}M}T?m4fJ>7K!#&q_WrT1gD$BC z%+JDS9!T#2b#JA*rxLFO4~6Jg6coKk(zG9^pzebnCCJ2mx;S&@LEVozLDHEr#&_Cy zQ1@g~AEu!4GX#kbDMaFfPvW%wW;A|N7$TxXfuWM#E64BQk}j9{5t63yn}UR~bMih# z31dfPg2p=vSBNNamwAn>sN$cHij>`aY(yD4v!I~#eG(V;AC&mGq?KRKOS)X<_epw; z5cA*uHzMyR{cjh1nF0+t!Tw*5&-q0QtNUF2azU-`YgPBNs{2^OGGE=ds_s`+_o+r? LzPc~<=&Jt@DpD}N diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/iwinfo.so b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/iwinfo.so deleted file mode 100755 index afe1a7c61ad71cfa2978a848ebe99de1e3d8e503..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24579 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 S{*|aYNB*xQZtBHU+WrSNLc;q1 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/ip.so b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/ip.so deleted file mode 100755 index acfb90d87dc1d309b8a2cdbb3436fa339208cbf4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32771 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 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/template/parser.so b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/template/parser.so deleted file mode 100755 index d801180e33d3d0a62c190ed1b440c035a9468531..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24706 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 TU9Eh9C-I}(N8d|2e^~S%$;>l6 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/hwnat_status.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/hwnat_status.htm deleted file mode 100755 index 1e697e56d768..000000000000 --- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/hwnat_status.htm +++ /dev/null @@ -1,56 +0,0 @@ - - -
-


Warning: - Web UI (espcially auto-refresh) will decrease the performance significantly. You'd better close Web UI before any performance/throughout test.

-
- -
- <%:Active Binding Status%> - - - - - - - - - - - -
<%:No.%><%:Type%><%:FOE Entry%><%:Traffic before NAT%><%:Traffic after NAT%>

<%:Collecting data...%>
-
- - diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_hwnat.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_hwnat.htm deleted file mode 100755 index 2ca8cfb0eb94..000000000000 --- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_hwnat.htm +++ /dev/null @@ -1,86 +0,0 @@ - -<% - local mtkwifi = require("mtkwifi") - local nat = false - - if string.match(mtkwifi.read_pipe("lsmod | grep hw_nat"), "hw_nat") then - nat = true - end -%> -<%+header%> - - - - - - -
- Hardware NAT Acceleration - -
- <% if nat then %> -

- '"> - <% else %> - '"> - <% end %> -

-


warning: - Web UI (espcially auto-refresh) will decrease the performance significantly. You'd better disable Web UI before any performance/throughout test.

-
- -<% if nat then %> -

-<% end %>
-
-    
-
-<%+footer%>
diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_ipsec_view.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_ipsec_view.htm
deleted file mode 100755
index 8273135ee768..000000000000
--- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_ipsec_view.htm
+++ /dev/null
@@ -1,85 +0,0 @@
-
-<%
-        local disp = require "luci.dispatcher"
-%>
-<%
-        local section_name
-        local cur = require "luci.model.uci".cursor()
-        cur:foreach("ipsec", "remote", function(s) section_name = s['.name'] end)
-%>
-
-
-
-
-
-
-
-
- -
-
- diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_web_console.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_web_console.htm deleted file mode 100755 index 5723abf20623..000000000000 --- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_web_console.htm +++ /dev/null @@ -1,94 +0,0 @@ -<%+header%> - - - - -

Web Console

-
-
- Execute shell commands or scripts as root. Be Careful. -

Press Enter to execute. Press Shift+Enter to start a new line.

-

-

- - - - - - - - - - - -

-

-    
-
- - - -<%+footer%> - diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_apcli.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_apcli.htm deleted file mode 100755 index 97bcc6ed830e..000000000000 --- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_apcli.htm +++ /dev/null @@ -1,1716 +0,0 @@ - -<%+header%> - -<% -local disp = require "luci.dispatcher" --- local request = disp.context.path -local request = disp.context.request -local mtkwifi = require("mtkwifi") ---local devname = string.match(request[5], "(mt.+)%.") -local devname = request[5] -local devs = mtkwifi.get_all_devs() -local dev = {} -local vif = {} -local vifidx -for _,v in ipairs(devs) do - if v.devname == devname then - dev = v - end -end - -local vifname = request[6] or dev.apcli.vifname -assert(vifname) -vif = dev and dev.vifs[vifname] or nil -vifidx = vif and vif.vifidx or nil ---print(devs, dev, dev.apcli, devname, vifname) - -local cfgs = mtkwifi.load_profile(dev.profile) -local map_cfgs -local first_card_cfgs -local appliedMapModeDiff -if pcall(require, "map_helper") then - map_cfgs = mtkwifi.load_profile(mtkwifi.__read_easymesh_profile_path()) - first_card_cfgs = mtkwifi.load_profile(mtkwifi.detect_first_card()) - local appliedMapDiffTable = mtkwifi.diff_profile(mtkwifi.detect_first_card()) - appliedMapModeDiff = appliedMapDiffTable["MapMode"] and appliedMapDiffTable["MapMode"][2] or nil -end -%> - - - - - - - - -
-
" enctype="multipart/form-data" onreset="return cbi_validate_reset(this);" onsubmit="return validate_all() && cbi_validate_form(this, 'Some fields are invalid, cannot save values!')" autocomplete="off"> -
- ApCli Configurations - <%=vifname and devname.."@"..vifname or devname%> - <%local diff = mtkwifi.diff_profile(dev.profile)%> - <%if next(diff) ~= nil then%> - ( '">Click here to apply changes) - <%end%> - - - - - - - - - - - - -
- - - Available Wireless Networks - - -

- -

-
- -
-
-
- Connection Configurations - - - - - - - style="display:none;" <% end %> > - - - - - - - - - - - - - - - - - - - - - - style="display:none;"<% end %>> - - - - - - style="display:none;"<% end %>> - - - - - - style="display:none;" <% end %>> - - - - - - - - - - - style="display:none" <% end %> > - - - - - - style="display: none;"<% end %>> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ApClient Mode - checked="checked"<% end %> onclick="toggle_apcli(true)"/> Enable - checked="checked"<% end %> onclick="toggle_apcli(false)"/> Disable -
MAC Repeater Mode - checked="checked"<% end %>/> Enable - checked="checked"<% end %>/> Disable -
Root AP SSID - "/> -
Root AP Channel - - This will overwrite channel of AP!
Root AP Authentication Mode - -
Root AP Encryption - -
Root AP WPA Key - "/> -
ApCli MFPC - - checked="checked" - <% end %> - <% if cfgs.ApCliAuthMode == "WPA3PSK" then %> - disabled="disabled" - <% end %> - type="checkbox"> -
ApCli MFPR - - checked="checked" - <% end %> - <% if cfgs.ApCliAuthMode == "WPA3PSK" then %> - disabled="disabled" - <% end %> - type="checkbox"> -
ApCli MFPSHA256 - - checked="checked" - <% end %> - <% if cfgs.ApCliAuthMode == "WPA3PSK" then %> - disabled="disabled" - <% end %> - type="checkbox"> -
Root AP Encryption - -
WEP Default Key - -
Root AP WEP Key 1 - " maxlength="26"/> -
WEP Key 1 Type - -
Root AP WEP Key 2 - " maxlength="26"/> -
WEP Key 2 Type - -
Root AP WEP Key 3 - " maxlength="26"/> -
WEP Key 3 Type - -
Root AP WEP Key 4 - " maxlength="26"/> -
WEP Key 4 Type - -
-
- - -
- - - -
-
-
-
- - - - - -<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_apply_reboot.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_apply_reboot.htm deleted file mode 100755 index bf50265f3894..000000000000 --- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_apply_reboot.htm +++ /dev/null @@ -1,67 +0,0 @@ -<%# - File name : mtk_wifi_apply_reboot.htm - This file is used in WebUI based on LuCI to handle the reboot event. -%> -<%+header%> -

Reboot Device

-
-

- As the driver does not support addition or deletion of interfaces on the fly, - the settings which were changed during addition or deletion of interfaces have not been applied yet! -

- - The changed settings will be applied only after reboot of the device. - Please click on the Reboot button. - -

- Tip:
- Add or delete as many interfaces as required before reboot so that you do not have to reboot the device again.
- Please follow below instructions to add or delete an interface;
- 1. Go to Wireless Overview web-page.
- 2. Click on Add button to add a new interface or click on Remove button to delete an existing interface.
- 3. If you are are adding a new interface, then, click on Save button after filling out all the required fields such as SSID etc.
- 4. Once you are done with addition/deletion of interfaces, then please click on Reload button or - Save and Apply button on any web-page which will redirect to this web-page to perform the reboot of the device.
-

- -
-

- - -<%+footer%> \ No newline at end of file diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_chip_cfg.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_chip_cfg.htm deleted file mode 100755 index 70ccc64c3c3e..000000000000 --- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_chip_cfg.htm +++ /dev/null @@ -1,600 +0,0 @@ -<%+header%> - - -<% -local disp = require "luci.dispatcher" --- local request = disp.context.path -local request = disp.context.request -local mtkwifi = require("mtkwifi") -local devname = request[5] -local devs = mtkwifi.get_all_devs() -local dev = {} -for _,v in ipairs(devs) do - if v.devname == devname then - dev = v - end -end -local cfgs = mtkwifi.load_profile(dev.profile) -local map_cfgs -local first_card_cfgs -local appliedMapModeDiff -if pcall(require, "map_helper") then - map_cfgs = mtkwifi.load_profile(mtkwifi.__read_easymesh_profile_path()) - first_card_cfgs = mtkwifi.load_profile(mtkwifi.detect_first_card()) - local appliedMapDiffTable = mtkwifi.diff_profile(mtkwifi.detect_first_card()) - appliedMapModeDiff = appliedMapDiffTable["MapMode"] and appliedMapDiffTable["MapMode"][2] or nil -end -%> - - - - - -
" enctype="multipart/form-data" onreset="return cbi_validate_reset(this)" onsubmit="return cbi_validate_form(this, 'Some fields are invalid, cannot save values!') && ValidateAllSettings()" autocomplete="false"> -
- Chip Configurations - <%=string.split(devname,".")[1]%> - <%local diff = mtkwifi.diff_profile(dev.profile)%> - <%if next(diff) ~= nil then%> - ( '">Click here to apply changes) - <%end%> - - -
    -
  • - Basic -
  • - <% if cfgs["VOW_Airtime_Fairness_En"] then %> -
  • - VoW -
  • - <% end %> -
- - - - - - - - - - - - - - - - - - - - - - <% if cfgs.WHNAT then %> - - - - - - <% end %> - <% if cfgs.E2pAccessMode then %> - - - - - - <% end %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Decline BA Request - checked="checked"<% end %>/> Enable - checked="checked"<% end %>/> Disable -
Reverse Direction Grant (RDG) - checked="checked"<% end %>/> Enable - checked="checked"<% end %>/> Disable -
BA Win size - <% if string.split(cfgs.WirelessMode,";")[1] == "16" or string.split(cfgs.WirelessMode,";")[1] == "17" or string.split(cfgs.WirelessMode,";")[1] == "18" then %> (range 1-256) <% else %> (range 1-64) <% end %> -
HT Disallow TKIP - checked="checked"<% end %>/> Enable - checked="checked"<% end %>/> Disable -
Wi-Fi HW NAT - - Supported by MT7615
E2pAccessMode - -
Beacon Interval - tu(range 20-999, default 100) -
Data Beacon Rate (DTIM) - Beacon interval(range 1-255, default 1) -
BG Protection Mode - -
Short Preamble - checked="checked"<% end %>/> Enable - checked="checked"<% end %>/> Disable -
TX Burst - checked="checked"<% end %>/> Enable - checked="checked"<% end %>/> Disable -
Packet Aggregate - checked="checked"<% end %>/> Enable - checked="checked"<% end %>/> Disable -
Short Slot - checked="checked"<% end %>/> Enable - checked="checked"<% end %>/> Disable -
- - <% if cfgs["VOW_Airtime_Fairness_En"] then %> - - <% end %> - -
- - - - - -
- - - - -<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_dev_cfg.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_dev_cfg.htm deleted file mode 100755 index 23e3a05b7fd3..000000000000 --- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_dev_cfg.htm +++ /dev/null @@ -1,1380 +0,0 @@ - -<%+header%> - - -<% -local disp = require "luci.dispatcher" --- local request = disp.context.path -local request = disp.context.request -local mtkwifi = require("mtkwifi") -local devname = request[5] -local devs = mtkwifi.get_all_devs() -local dev = {} -for _,v in ipairs(devs) do - if v.devname == devname then - dev = v - end -end -local cfgs = mtkwifi.load_profile(dev.profile) -local bands = mtkwifi.detect_triband() -local map_cfgs -local first_card_cfgs -local appliedMapModeDiff -if pcall(require, "map_helper") then - map_cfgs = mtkwifi.load_profile(mtkwifi.__read_easymesh_profile_path()) - first_card_cfgs = mtkwifi.load_profile(mtkwifi.detect_first_card()) - local appliedMapDiffTable = mtkwifi.diff_profile(mtkwifi.detect_first_card()) - appliedMapModeDiff = appliedMapDiffTable["MapMode"] and appliedMapDiffTable["MapMode"][2] or nil -end -%> - - - - - -
" enctype="multipart/form-data" onreset="return cbi_validate_reset(this)" onsubmit="return cbi_validate_form(this, 'Some fields are invalid, cannot save values!') && ValidateAllSettings()" autocomplete="false"> -
- Device Configuration - <%=devname%> - <%local diff = mtkwifi.diff_profile(dev.profile)%> - <%if next(diff) ~= nil then%> - ( '">Click here to apply changes) - <%end%> - - - - - - - - - - - - - - - - - - - - - - - - - - <% if string.split(cfgs.WirelessMode,";")[1] == "16" or string.split(cfgs.WirelessMode,";")[1] == "17" or string.split(cfgs.WirelessMode,";")[1] == "18" then %> - - - - - - - - - - - - - - - - <% end %> - -
Channel - - <% if cfgs.ApCliEnable == "1" then %> APClient/Repeater Mode. <% end %>
BSS color - -
Co-located BSSID set max index - -
TWT Support - -
- - - - - <% if string.split(cfgs.WirelessMode,";")[1] == "16" or string.split(cfgs.WirelessMode,";")[1] == "17" or string.split(cfgs.WirelessMode,";")[1] == "18" then %> - - - - - - - - - - - - - - - - <% end %> - "> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <% if mtkwifi.band(string.split(cfgs.WirelessMode,";")[1]) == "5G" then %> - - - - - - - - - - - <% end %> - - - - - - - - - - - <% if cfgs.MUTxRxEnable then %> - - - - - - <% end %> - <% if string.split(cfgs.WirelessMode,";")[1] == "16" or string.split(cfgs.WirelessMode,";")[1] == "17" or string.split(cfgs.WirelessMode,";")[1] == "18" then %> - - - - - - <% end %> - - - <% if dev.isPowerBoostSupported then%> - - - - - - - style="display:none" <% end %>> - - - - - - - - - - - - - - <% end %> -
-
- - - - - -
-
- - -
" enctype="multipart/form-data" onreset="return cbi_validate_reset(this)" onsubmit="return cbi_validate_form(this, 'Some fields are invalid, cannot save values!')" autocomplete="off"> -
- Raw Configurations ( Edit WiFi profile directly ) -

WARNING : DO NOT MESS WITH IT IF YOU DON'T UNDERSTAND IT!

- -
-
- '" type="button"> - - '"> -
-
- - - - -<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_loading.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_loading.htm deleted file mode 100755 index 9a459f2e1020..000000000000 --- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_loading.htm +++ /dev/null @@ -1,90 +0,0 @@ -<%# - File name : mtk_wifi_loading.htm - This file is used in WebUI based on LuCI to handle the loading event. -%> -<%+header%> -<% -local disp = require "luci.dispatcher" -local request = disp.context.request -local url = "/"..table.concat(request,'/',5) -%> -

Applying Settings

-
- - Please wait while the settings are being applied. -
- - - -<%+footer%> \ No newline at end of file diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_ap_capabilities.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_ap_capabilities.htm deleted file mode 100755 index db8fc513eb6c..000000000000 --- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_ap_capabilities.htm +++ /dev/null @@ -1,257 +0,0 @@ -<%# - File name : mtk_wifi_map_ap_capabilities.htm - This file is used in WebUI based on LuCI to display EasyMesh AP capabilities. -%> -<%+header%> - - - -

EasyMesh AP Capabilities

-
- - -<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_bh_link_metrics.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_bh_link_metrics.htm deleted file mode 100755 index 6af669c69d57..000000000000 --- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_bh_link_metrics.htm +++ /dev/null @@ -1,220 +0,0 @@ -<%# - File name : mtk_wifi_map_bh_link_metrics.htm - This file is used in WebUI based on LuCI - to display the feature of EasyMesh Configurations - named as Back-haul Link Metrics at Controller. -%> -<%+header%> - - - -

EasyMesh Back-haul Link Metrics at Controller

- - - - - -<%+footer%> \ No newline at end of file diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_bss_cfg_renew.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_bss_cfg_renew.htm deleted file mode 100755 index def34932fe7a..000000000000 --- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_bss_cfg_renew.htm +++ /dev/null @@ -1,1156 +0,0 @@ -<%# - File name : mtk_wifi_map_bss_cfg_renew.htm - This file is used in WebUI based on LuCI - to configure BSS in EasyMesh Network. -%> -<%+header%> - - - -<% - local mtkwifi = require("mtkwifi") - local cfgs = mtkwifi.load_easymesh_bss_cfgs() - local cfg_1905d = mtkwifi.load_profile("/etc/map/1905d.cfg") -%> - -
- EasyMesh BSS Configuration - <%local diff = mtkwifi.diff_profile(mtkwifi.__easymesh_bss_cfgs_path())%> - <%if next(diff) ~= nil then%> - - ( '">Click here to apply EasyMesh BSS changes) - - <% end %> - -
- - - - - -
-

- -

-
" enctype="multipart/form-data" onsubmit="return validate_all()" autocomplete="off" style="display:none"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AL-MAC - -
Radio Band - -
-
- -
- -
- - - - - -
- - - -<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_bssinfo.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_bssinfo.htm deleted file mode 100755 index 9743e9894336..000000000000 --- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_bssinfo.htm +++ /dev/null @@ -1,363 +0,0 @@ -<%# - File name : mtk_wifi_map_bssinfo.htm - This file is used in WebUI based on LuCI - to display the feature of EasyMesh Configurations - named as Front-haul status per BSS. -%> -<%+header%> - - - -

EasyMesh Front-haul Status per BSS

-
- - Retrieving EasyMesh Front-haul Status per BSS Information! -
- -
- - -<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_channel_planning_score.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_channel_planning_score.htm deleted file mode 100755 index 98ca3391dee2..000000000000 --- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_channel_planning_score.htm +++ /dev/null @@ -1,407 +0,0 @@ -<%# - File name : mtk_wifi_map_channel_planning_score.htm - This file is used in WebUI based on LuCI to display Channel Scan Result. -%> -<%+header%> - - - - - -

MAP R2 Channel Planning Score

- -
- - Retrieving MAP R2 Channel Planning Score! -
- - - - - - -<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_channel_scan_result.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_channel_scan_result.htm deleted file mode 100755 index e1e9b934763c..000000000000 --- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_channel_scan_result.htm +++ /dev/null @@ -1,510 +0,0 @@ -<%# - File name : mtk_wifi_map_channel_scan_result.htm - This file is used in WebUI based on LuCI to display Channel Scan Result. -%> -<%+header%> - - - - - - - -

MAP R2 Channel Scan Result

- -
- - Retrieving MAP R2 Channel Scan Result! -
- - - - - - -<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_client_capabilities.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_client_capabilities.htm deleted file mode 100755 index c8ee08775855..000000000000 --- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_client_capabilities.htm +++ /dev/null @@ -1,146 +0,0 @@ -<%# - File name : mtk_wifi_map_client_capabilities.htm - This file is used in WebUI based on LuCI to display EasyMesh client capabilities. -%> -<%+header%> - - - -

EasyMesh Client Capabilities

-
- - -<%+footer%> \ No newline at end of file diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_data_element.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_data_element.htm deleted file mode 100755 index a7bf8caef199..000000000000 --- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_data_element.htm +++ /dev/null @@ -1,216 +0,0 @@ -<%# - File name : mtk_wifi_map_data_element.htm - This file is used in WebUI based on LuCI to display Data Element. -%> -<%+header%> - - - -<% - local disp = require "luci.dispatcher" - local path = disp.context.path - local request = disp.context.request - local getAlMac = request[#request] -%> - -

MAP R2 Data Element

- -
- - Retrieving MAP R2 Data Element -
- -
- - -<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_display_bootstrapping_uri.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_display_bootstrapping_uri.htm deleted file mode 100755 index 5f65b8403045..000000000000 --- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_display_bootstrapping_uri.htm +++ /dev/null @@ -1,36 +0,0 @@ -<%# - File name : local mtkwifi = require("mtkwifi").htm - This file is used in WebUI to show MAP Bootstrapping URIs. -%> -<%+header%> - - - - - -<% - local mtkwifi = require("mtkwifi") - local dpp_cfg = mtkwifi.load_profile("/etc/dpp_cfg.txt") -%> - -

MAP Bootstrapping URIs

- - - <% for k,v in pairs(dpp_cfg) do - if string.find(k, "agt_qr_code") then %> - - - - - <% end %> - <% end %> -
<%=k%><%=v%>
- - -<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_runtime_topology.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_runtime_topology.htm deleted file mode 100755 index 34bbec77ca1d..000000000000 --- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_map_runtime_topology.htm +++ /dev/null @@ -1,465 +0,0 @@ -<%# - File name : mtk_wifi_map_runtime_topology.htm - This file is used in WebUI based on LuCI to display EasyMesh Run-time Topology. -%> -<%+header%> - -<% - local mtkwifi = require("mtkwifi") - local mapcfgs = mtkwifi.load_profile("/etc/map/1905d.cfg") -%> - - - - -

- EasyMesh Run-time Topology Display - <% if mapcfgs.map_ver ~= "R1" then %> -

Click on the Easymesh device to display its Data Element Statistics

- <% end %> -

- -
- - Retrieving EasyMesh Run-time Topology Information! -
- - - - - - -<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_multi_ap.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_multi_ap.htm deleted file mode 100755 index 05bc762a6372..000000000000 --- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_multi_ap.htm +++ /dev/null @@ -1,2522 +0,0 @@ -<%+header%> - - - - - - - -<% local mtkwifi = require("mtkwifi") %> -<% if not mtkwifi then %> -
- mtkwifi lua module is not available! -
-<% else %> - <% - local devs = mtkwifi.get_all_devs() - local l1dat, l1 = mtkwifi.__get_l1dat() - local bands = mtkwifi.detect_triband() - local cfg_1905d = mtkwifi.load_profile("/etc/map/1905d.cfg") - local driver_cfgs - local allDevDiff = {} - local map_cfgs = mtkwifi.load_profile(mtkwifi.__read_easymesh_profile_path()) - local first_card_cfgs = mtkwifi.load_profile(mtkwifi.detect_first_card()) - local appliedMapDiffTable = mtkwifi.diff_profile(mtkwifi.detect_first_card()) - local appliedMapModeDiff = appliedMapDiffTable["MapMode"] and appliedMapDiffTable["MapMode"][2] or nil - local isMapSupported = true - local mapd_default_cfg = mtkwifi.load_profile("/etc/map/mapd_default.cfg") - local mapd_user_cfg = mtkwifi.load_profile("/etc/map/mapd_user.cfg") - local eth_mode = mapd_default_cfg.mode - if mapd_user_cfg.mode then - eth_mode = mapd_user_cfg.mode - end - local dpp_cfg = mtkwifi.load_profile("/etc/dpp_cfg.txt") - %> - - <% if not l1dat or not l1 then %> -
- l1profile.dat file is not available! -
- <% else %> - <% - for idx,dev in ipairs(devs) do - local zone = l1.l1_path_to_zone(dev.profile) - local diff = mtkwifi.diff_profile(dev.profile) - if next(diff) ~= nil then - allDevDiff[dev.devname] = diff - end - if not dev.isMultiAPSupported then - isMapSupported = false - end - if zone == "dev1" then - driver_cfgs = mtkwifi.load_profile(dev.profile) - end - end - %> - - <% if not isMapSupported then %> -
- EasyMesh feature is not supported for this platform! -
- <% else %> - - <% if not driver_cfgs or not map_cfgs then %> -
- Profile settings file is not available! -
- <% else %> - -
-
- EasyMesh Configurations - - -
-
    -
  • - Basic -
  • - <% if not appliedMapModeDiff and first_card_cfgs.MapMode == "1" then %> - - - <% if cfg_1905d.map_ver ~= "R1" and cfg_1905d.map_ver ~= "R2" then %> - - <% end %> - <% end %> -
- -
" enctype="multipart/form-data" onsubmit="return validate_all()" autocomplete="off"> - - - - - - - <% if bands == 3 and first_card_cfgs.MapMode == "1" then %> - - - - - <% end %> - <% if first_card_cfgs.MapMode == "1" then %> - <% if mapd_default_cfg.mode then %> - - - - - - - - - - - <% end %> - - - - - - - - - - <% if appliedMapModeDiff and appliedMapModeDiff ~= "1" and first_card_cfgs.MapMode == "1" then %> - - - - - - - - <% end %> - <% if appliedMapModeDiff and appliedMapModeDiff == "1" and first_card_cfgs.MapMode ~= "1" then %> - - - - - - - - <% end %> - <% if not appliedMapModeDiff and first_card_cfgs.MapMode ~= "1" then %> - - - - - - - - <% end %> - <% end %> - <% if not appliedMapModeDiff and first_card_cfgs.MapMode == "1" then %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <% if cfg_1905d.map_ver ~= "R1" and cfg_1905d.map_ver ~= "R2" then %> - - - - - - <% end %> - - - - - - - - - - - - - - - <% if bands == 3 then %> - - - - - - - - - - - - - - - - - <% else %> - - - - - - - - - - - - - <% end %> - - - <% end %> -
EasyMesh Mode - -
Reset EasyMesh Settings to default - -
- - EasyMesh has not been enabled yet!
- Please click on Save and Apply button to enable the EasyMesh. -
-
Reset EasyMesh Settings to default - -
- - EasyMesh has not been disabled yet!
- Please click on Save and Apply button to disable the EasyMesh. -
-
Reset EasyMesh Settings to default - -
- - Other EasyMesh related settings will be displayed only after enabling EasyMesh!
- Please check Enable radio button of EasyMesh and then click on Save and Apply button to enable EasyMesh. -
-
- - Other EasyMesh related settings will be displayed once Device Role is configured. - -
- <% if not appliedMapModeDiff and first_card_cfgs.MapMode == "1" then %> - - - - - - - - - - - - - - <% if cfg_1905d.map_ver ~= "R1" then %> - - - - - - - - - <% end %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <% if cfg_1905d.map_ver ~= "R1" then %> - - - - - - - - - <% end %> - <% if bands == 3 then %> - - - - - - - - - - - - - - - - - - - - - <% else %> - - - - - - - - - - - - - - - - - <% end %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <% end %> - <% if not appliedMapModeDiff and first_card_cfgs.MapMode == "1" then %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <% end %> - - <% if cfg_1905d.map_ver ~= "R1" and cfg_1905d.map_ver ~= "R2" and not appliedMapModeDiff and first_card_cfgs.MapMode == "1" then %> - - <% end %> - -
- - - -
-
-
- - - <% end %> - <% end %> - <% end %> -<% end %> -<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_overview.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_overview.htm deleted file mode 100755 index edbeb1c13b07..000000000000 --- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_overview.htm +++ /dev/null @@ -1,557 +0,0 @@ -<%+header%> - - - - -<% -local mtkwifi = require("mtkwifi") -local devs = mtkwifi.get_all_devs() -local l1dat, l1 = mtkwifi.__get_l1dat() -local dridx = l1.DEV_RINDEX -local main_ifname -local map_cfgs -local first_card_cfgs -local appliedMapModeDiff -local chipname -if pcall(require, "map_helper") then - map_cfgs = mtkwifi.load_profile(mtkwifi.__read_easymesh_profile_path()) - first_card_cfgs = mtkwifi.load_profile(mtkwifi.detect_first_card()) - local appliedMapDiffTable = mtkwifi.diff_profile(mtkwifi.detect_first_card()) - appliedMapModeDiff = appliedMapDiffTable["MapMode"] and appliedMapDiffTable["MapMode"][2] or nil -end -%> - - - -

Wireless Overview

- - <% if #devs == 0 then %> -
- No wireless device found! -
- <% end %> - - <% for _,dev in ipairs(devs) do %> - <% main_ifname = l1dat and l1dat[dridx][dev.devname].main_ifname or dbdc_prefix[mainidx][subidx].."0" %> - <% if mtkwifi.exists("/sys/class/net/"..main_ifname) then %> -
- - - - <% if chipname ~= string.split(dev.devname,".")[1].."."..(dev.mainidx) then %> - <% chipname = string.split(dev.devname,".")[1].."."..(dev.mainidx) %> - - - - - - <% end %> - - - - - - - <% if dev.vifs then%> - - <% for _,vif in ipairs(dev.vifs) do %> - - - - - - - <% end %> - - - <% if dev.apcli then %> - - - - - - - <% end %> - - <% end %> - -
- - - <%=string.split(dev.devname,".")[1]%> - <%if not dev.vifs then%> - * FATAL ERROR: Incorrect Profile Settings - <%end%> -
- Driver version: <%=dev.version%> -
-
"> - ','<%=luci.dispatcher.build_url("admin", "mtk", "wifi", "chip_cfg_view", dev.devname)%>')"> -
-
" style="display:none"> - Processing request. -
-
- <%=dev.devname%> - <%local diff = mtkwifi.diff_profile(dev.profile)%> - <%if next(diff) ~= nil then%> - * need reload to apply changes - <%end%> -
- Work mode: <% if dev.ApCliEnable == "1" then %> APCli <% else %> AP <% end %> -
-
- <%if not dev.vifs then%> - ')"> - <%else%> - ')"> - ')"> - - <%end%> -
- -
- <% if vif.state == "up" then %> - - <% else %> - - <% end %> - - Interface: <%=vif.vifname%> | - Type: AP | - SSID: - - <% if vif.__ssid == "" then %> - Error: value not present in dat file - <% else %> - <%=vif.__ssid and vif.__ssid:gsub(" "," ") or nil%> <% end %> - | - Channel: - <%=vif.__channel or dev.Channel%> -
- <% if vif.state == "up" then %> - BSSID: <%=vif.__bssid%> | Mode: <%=dev.WirelessModeList[tonumber(vif.__wirelessmode)]%> - <% else %> - Wireless is disabled or not associated - <% end %> -
-
- <% if not vif.state then %> - - <% elseif vif.state == "up" then %> - ')"> - <% else %> - ')"> - <% end %> - ')"> - ')"> -
- -
- <% if dev.apcli.state == "up" then %> - - <% else %> - - <% end %> - Interface: <%=dev.apcli.devname%> | Type: STA | Status: <% if dev.ApCliEnable ~= "1" then %> Disconnected <% end %> -
-
style="display:none" <% end %>> - <%:Loading%>  Loading connection information of <%=dev.apcli.devname%> -
- - style="display:none" <% end %>>Wireless is disabled or not associated -
-
- <% if dev.ApCliEnable ~= "1" then %> - <% if dev.apcli.state == "up" then %> - ')"> - <% else %> - ')"> - <% end %> - ')"> - ')"> - <% else %> - ')"> - ')"> - ')"> - ')"> - ')"> - <% end %> -
- -
-
- <% end %> - <% end %> - - - - <%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_vif_cfg.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_vif_cfg.htm deleted file mode 100755 index 026d5f729c20..000000000000 --- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/admin_mtk/mtk_wifi_vif_cfg.htm +++ /dev/null @@ -1,2790 +0,0 @@ - -<%+header%> - -<% -local disp = require "luci.dispatcher" -local path = disp.context.path -local request = disp.context.request -local mtkwifi = require("mtkwifi") -local devs = mtkwifi.get_all_devs() -local devname -local vifname, vifidx -local dev = {} -local vif = {} -if request[4] == "vif_add_view" then - devname, vifname = request[5], request[6] - dev = devs and devs[devname] - vifname = vifname..#dev.vifs - vifidx = #dev.vifs + 1 - -elseif request[4] == "vif_cfg_view" then - devname, vifname = request[5], request[6] - dev = devs and devs[devname] or nil - vif = dev and dev.vifs[vifname] or nil - vifidx = vif and vif.vifidx or nil -end - -local cfgs = mtkwifi.load_profile(dev.profile) -local diff = mtkwifi.diff_profile(dev.profile) -local WscValue = mtkwifi.token_get(cfgs["WscConfMode"], vifidx, "0") or "0" -local appliedWscValue = diff["WscConfMode"] and mtkwifi.token_get(diff["WscConfMode"][2], vifidx) or nil - -local map_cfgs -local first_card_cfgs = mtkwifi.load_profile(mtkwifi.detect_first_card()) -local appliedMapModeDiff -if pcall(require, "map_helper") then - map_cfgs = mtkwifi.load_profile(mtkwifi.__read_easymesh_profile_path()) - local appliedMapDiffTable = mtkwifi.diff_profile(mtkwifi.detect_first_card()) - appliedMapModeDiff = appliedMapDiffTable["MapMode"] and appliedMapDiffTable["MapEnable"][2] or nil -end - -local AuthModes = {} -local EncryptionTypeLists = {} -if string.split(cfgs.WirelessMode,";")[1] == "18" then - AuthModes = (WscValue == "0") and dev.AuthModeList_6G or dev.WpsEnableAuthModeList_6G - EncryptionTypeLists = dev.EncryptionTypeList_6G -else - AuthModes = (WscValue == "0") and dev.AuthModeList or dev.WpsEnableAuthModeList - EncryptionTypeLists = dev.EncryptionTypeList -end -%> - - - - - -
" enctype="multipart/form-data" onsubmit="return validate_all('<%=vifidx%>','<%=cfgs["HT_DisallowTKIP"]%>') && chk_WPS_ACL('<%=tostring(mtkwifi.__any_wsc_enabled(WscValue)) %>')" autocomplete="off"> -<% if not dev or not vif then%> -
- Interface Not Exist - <%=vifname and devname.."@"..vifname or devname%> - -
-<% else %> - - <% if mtkwifi.band(vif.__wirelessmode or string.split(cfgs.WirelessMode,";")[1]) == "5G" or mtkwifi.band(vif.__wirelessmode or string.split(cfgs.WirelessMode,";")[1]) == "6G" then %> - - <% else %> - - <% end %> - -
- Interface Configurations - <%=vifname and devname.."@"..vifname or devname%> - <%if next(diff) ~= nil then%> - ( '">Click here to apply changes) - <%end%> - - - -
    -
  • - );this.blur(); ">Basic -
  • - <% if string.split(cfgs.WirelessMode,";")[1] == "16" or string.split(cfgs.WirelessMode,";")[1] == "17" or string.split(cfgs.WirelessMode,";")[1] == "18" then %> -
  • - );this.blur(); ">HE_MU -
  • - <% end %> -
  • - );this.blur(); ">WPS -
  • - <% if map_cfgs then %> - <% if (not dev.wdsBand or dev.wdsBand == dev.dbdcBandName) and first_card_cfgs.MapMode ~= "1" then %> -
  • - WDS -
  • - <% end %> - <% else %> - <% if (not dev.wdsBand or dev.wdsBand == dev.dbdcBandName) then %> -
  • - WDS -
  • - <% end %> - <% end %> - <% if map_cfgs then %> - <% if request[4] == "vif_cfg_view" and cfgs.MapMode == "0" then%> -
  • - );this.blur(); ">Stations -
  • - <% end %> - <% else %> - <% if request[4] == "vif_cfg_view" then%> -
  • - );this.blur(); ">Stations -
  • - <% end %> - <% end %> -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <% if dev.DBDC_MODE == "0" then %> - - - - - - <% end %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
SSID - " name="<%="SSID"..vifidx%>"> -
Channel - -
Auth Mode - -
Hidden - - checked="checked" - <% end %> type="checkbox"> -
AP Isolation - - checked="checked" - <% end %> type="checkbox"> -
WMM Capable - - checked="checked" - <% end %> type="checkbox"> -
TX Rate - -
STBC - checked="checked"<% end %>/> Enable - checked="checked"<% end %>/> Disable -
HT LDPC - checked="checked"<% end %>/> Enable - checked="checked"<% end %>/> Disable -
VHT STBC - checked="checked"<% end %>/> Enable - checked="checked"<% end %>/> Disable -
VHT LDPC - checked="checked"<% end %>/> Enable - checked="checked"<% end %>/> Disable -
Mode - -
DLS Capable - checked="checked"<% end %>/> Enable - checked="checked"<% end %>/> Disable -
APSD Capable - checked="checked"<% end %>/> Enable - checked="checked"<% end %>/> Disable -
Fragment Threshold - (range 256-2346, default 2346) -
RTS Threshold - (range 1-2347, default 2347) -
VHT Short GI - -
VHT BW Signaling - checked="checked"<% end %>/> Enable - checked="checked"<% end %>/> Disable - checked="checked"<% end %>/> Dynamic -
HT Protection - checked="checked"<% end %>/> Enable - checked="checked"<% end %>/> Disable -
HT Guard Interval - -
Operating Mode - -
A-MSDU - checked="checked"<% end %>/> Enable - checked="checked"<% end %>/> Disable -
Auto Block ACK - checked="checked"<% end %>/> Enable - checked="checked"<% end %>/> Disable -
IGMP Snooping - checked="checked"<% end %>/> Enable - checked="checked"<% end %>/> Disable -
- - <% if string.split(cfgs.WirelessMode,";")[1] == "16" or string.split(cfgs.WirelessMode,";")[1] == "17" or string.split(cfgs.WirelessMode,";")[1] == "18" then %> - - - - - - - - - - - - - - - - - - - - - - - - - - - - <% end %> - - - - - - - - - - - - <%if tostring(mtkwifi.__any_wsc_enabled(WscValue)) == "1" then%> - <% if not appliedWscValue or (WscValue == appliedWscValue) then%> - - - - - - - - - - - - - - - - - - - - - - - - - - - - <% if map_cfgs then %> - <% if cfgs.MapMode == "0" then %> - - - - - - - - - - - - - - - <% end %> - <% else %> - - - - - - - - - - - - - - - <% end %> - - - - - - - - - - - - - - - - - - - - - - - - - - <% else %> - - - - <% end %> - <% end %> - - - - <% if not dev.wdsBand or dev.wdsBand == dev.dbdcBandName then %> - - - - - - - - style="display:none" <% end %>> - - - - - style="display:none" <% end %>> - - - - - style="display:none" <% end %>> - - - - - style="display:none" <% end %>> - - - - style="display:none" <% end %>> - - - - - style="display:none" <% end %>> - - - - style="display:none" <% end %>> - - - - - style="display:none" <% end %>> - - - - style="display:none" <% end %>> - - - - - - style="display:none" <% end %>> - - <% _wdsMac=cfgs.WdsList and cfgs.WdsList:match("^([%x:]+)") %> - - - - style="display:none" <% end %>> - - <% _wdsMac=cfgs.WdsList and cfgs.WdsList:match("^[%x:]+;([%x:]+)") %> - - - - style="display:none" <% end %>> - - <% _wdsMac=cfgs.WdsList and cfgs.WdsList:match("^[%x:]+;[%x:]+;([%x:]+)") %> - - - - style="display:none" <% end %>> - - <% _wdsMac=cfgs.WdsList and cfgs.WdsList:match("^[%x:]+;[%x:]+;[%x:]+;([%x:]+)") %> - - - - - - <% end %> - - <% if request[4] == "vif_cfg_view" then%> - - - - - - - - - - - - - - - - <% end %> - - <% if map_cfgs then %> -
- Access Control - <%=vifname and devname.."@"..vifname or devname%> - - - - - -
Access Policy - disabled="disabled" <% end %> <% if cfgs["AccessPolicy"..(vifidx-1)] == "0" then %> checked="checked"<% end %>/> Disable -
- disabled="disabled" <% end %> <% if cfgs["AccessPolicy"..(vifidx-1)] == "1" then %> checked="checked"<% end %>/> White List -
- disabled="disabled" <% end %> <% if cfgs["AccessPolicy"..(vifidx-1)] == "2" then %> checked="checked"<% end %>/> Black List -
- <% if first_card_cfgs.MapMode == "1" then %> - To set Black List see MAP application note when EasyMesh is enabled. - <% end %> -
-
-# 1. one MAC one line.
-# 2. empty lines will be ignored.
-# 3. lines start with "#" will be ignored.
-# 4. invalid MAC will be ignored.
-
-11:22:33:44:55:66
-AA:BB:CC:DD:EE:FF
-11:22:33:aa:bb:cc
-            
- -
- <% else %> -
- Access Control - <%=vifname and devname.."@"..vifname or devname%> - - - - - -
Access Policy - checked="checked"<% end %>/> Disable -
- checked="checked"<% end %>/> White List -
- checked="checked"<% end %>/> Black List -
-
-# 1. one MAC one line.
-# 2. empty lines will be ignored.
-# 3. lines start with "#" will be ignored.
-# 4. invalid MAC will be ignored.
-
-11:22:33:44:55:66
-AA:BB:CC:DD:EE:FF
-11:22:33:aa:bb:cc
-            
- -
- <% end %> - -
- - - -
-<% end %> - - - -<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/csrftoken.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/csrftoken.htm deleted file mode 100644 index 57ac03f3bfa5..000000000000 --- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/csrftoken.htm +++ /dev/null @@ -1,24 +0,0 @@ -<%# - Copyright 2015 Jo-Philipp Wich - Licensed to the public under the Apache License 2.0. --%> - -<%+header%> - -

<%:Form token mismatch%>

-
- -

<%:The submitted security token is invalid or already expired!%>

- -

<%: - In order to prevent unauthorized access to the system, your request has - been blocked. Click "Continue »" below to return to the previous page. -%>

- -
- -

- Continue » -

- -<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/empty_node_placeholder.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/empty_node_placeholder.htm deleted file mode 100644 index b7e276b9609d..000000000000 --- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/empty_node_placeholder.htm +++ /dev/null @@ -1,11 +0,0 @@ -<%# - Copyright 2010 Jo-Philipp Wich - Copyright 2018 Daniel F. Dickinson - Licensed to the public under the Apache License 2.0. --%> - -<%+header%> - -

Component not present.

- -<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/error404.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/error404.htm deleted file mode 100644 index ff151d1834c9..000000000000 --- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/error404.htm +++ /dev/null @@ -1,12 +0,0 @@ -<%# - Copyright 2008 Steven Barth - Copyright 2008 Jo-Philipp Wich - Licensed to the public under the Apache License 2.0. --%> - -<%+header%> -

404 <%:Not Found%>

-

<%:Sorry, the object you requested was not found.%>

-

<%=message%>

-<%:Unable to dispatch%>: <%=url(unpack(luci.dispatcher.context.request))%> -<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/error500.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/error500.htm deleted file mode 100644 index 34a52cda84f7..000000000000 --- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/error500.htm +++ /dev/null @@ -1,11 +0,0 @@ -<%# - Copyright 2008 Steven Barth - Copyright 2008 Jo-Philipp Wich - Licensed to the public under the Apache License 2.0. --%> - -<%+header%> -

500 <%:Internal Server Error%>

-

<%:Sorry, the server encountered an unexpected error.%>

-
<%=message%>
-<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/footer.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/footer.htm deleted file mode 100644 index ba14ec8678d7..000000000000 --- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/footer.htm +++ /dev/null @@ -1,27 +0,0 @@ -<%# - Copyright 2008 Steven Barth - Copyright 2008-2019 Jo-Philipp Wich - Licensed to the public under the Apache License 2.0. --%> - -<% - local is_rollback_pending, rollback_time_remaining, rollback_session, rollback_token = luci.model.uci:rollback_pending() - - if is_rollback_pending or trigger_apply or trigger_revert then -%> - -<% - end - - include("themes/" .. theme .. "/footer") -%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/header.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/header.htm deleted file mode 100644 index b9ac4958d45a..000000000000 --- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/header.htm +++ /dev/null @@ -1,38 +0,0 @@ -<%# - Copyright 2008 Steven Barth - Copyright 2008-2019 Jo-Philipp Wich - Licensed to the public under the Apache License 2.0. --%> - -<% - if not luci.dispatcher.context.template_header_sent then - include("themes/" .. theme .. "/header") - luci.dispatcher.context.template_header_sent = true - end - - local applyconf = luci.config and luci.config.apply -%> - - - - diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/indexer.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/indexer.htm deleted file mode 100644 index 28fc3debc3f9..000000000000 --- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/indexer.htm +++ /dev/null @@ -1,7 +0,0 @@ -<%# - Copyright 2008 Steven Barth - Copyright 2008 Jo-Philipp Wich - Licensed to the public under the Apache License 2.0. --%> - -<% include("themes/" .. theme .. "/indexer") %> \ No newline at end of file diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/sysauth.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/sysauth.htm deleted file mode 100644 index acd5ff7e38f9..000000000000 --- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/sysauth.htm +++ /dev/null @@ -1,75 +0,0 @@ -<%# - Copyright 2008 Steven Barth - Copyright 2008-2012 Jo-Philipp Wich - Licensed to the public under the Apache License 2.0. --%> - -<%+header%> - -
- <%- if fuser then %> -
-

<%:Invalid username and/or password! Please try again.%>

-
- <% end -%> - -
-

<%:Authorization Required%>

-
- <%:Please enter your username and password.%> -
-
-
- -
- -
-
-
- -
- -
-
-
-
- -
- - -
-
- - -<% -local uci = require "luci.model.uci".cursor() -local fs = require "nixio.fs" -local https_key = uci:get("uhttpd", "main", "key") -local https_port = uci:get("uhttpd", "main", "listen_https") -if type(https_port) == "table" then - https_port = https_port[1] -end - -if https_port and fs.access(https_key) then - https_port = https_port:match("(%d+)$") -%> - - - -<% end %> - -<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/view.htm b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/view.htm deleted file mode 100644 index b451e8cfbf92..000000000000 --- a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/luci/view/view.htm +++ /dev/null @@ -1,12 +0,0 @@ -<%+header%> - -
-
<%:Loading view…%>
- -
- -<%+footer%> diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/lucihttp.so b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/lucihttp.so deleted file mode 100644 index 8eb74e8121fbb335e69119fe55b167ec85321fc7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12291 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 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/map_helper.so b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/map_helper.so deleted file mode 100755 index 476133b93f16e285bb65773792cbb288d8c734f3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33161 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 G`Thq=o+A+e diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket-3.0-rc1.so b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket-3.0-rc1.so deleted file mode 100755 index 8adda1dbc0030aa71c93d2f98ae5627c0420d774..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63610 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 HIKT2Y!U1{l diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/unix.so b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/socket/unix.so deleted file mode 100755 index 1d846a590bbe936398962585b21a587d24d97b96..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45922 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 M&d)rZ-c|X30gHqo`Tzg` diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/ubus.so b/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua/ubus.so deleted file mode 100755 index 8ba85549618d7a9094371c45009f95148035df0e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20483 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 Date: Fri, 17 Jul 2026 02:04:35 -0400 Subject: [PATCH 088/105] test(lua2cpg): cover openwrt-derived fixture corpus --- ...penWrtDerivedFirmwareCorpusSmokeTest.scala | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/OpenWrtDerivedFirmwareCorpusSmokeTest.scala diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/OpenWrtDerivedFirmwareCorpusSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/OpenWrtDerivedFirmwareCorpusSmokeTest.scala new file mode 100644 index 000000000000..fbca3ca5691d --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/OpenWrtDerivedFirmwareCorpusSmokeTest.scala @@ -0,0 +1,63 @@ +package io.joern.lua2cpg + +import io.shiftleft.codepropertygraph.cpgloading.CpgLoader +import io.shiftleft.semanticcpg.language.* +import io.shiftleft.semanticcpg.language.types.structure.FileTraversal +import io.shiftleft.semanticcpg.utils.FileUtil +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec + +import java.nio.file.{Files, Paths} +import scala.jdk.CollectionConverters.* + +class OpenWrtDerivedFirmwareCorpusSmokeTest extends AnyWordSpec with Matchers { + + "Lua2Cpg" should { + "analyze the OpenWrt-derived Lua corpus and export decoder evidence" in { + val resourceRoot = + Paths.get(getClass.getClassLoader.getResource("openwrt-derived-firmware-lua/usr/lib/lua").toURI) + + FileUtil.usingTemporaryDirectory("lua2cpg-openwrt-derived-corpus-smoke") { tmpDir => + val outputPath = tmpDir.resolve("openwrt-derived-firmware-lua.cpg.bin").toString + val exportDir = tmpDir.resolve("openwrt-derived-firmware-lua-evidence") + val cpg = new Lua2Cpg() + .createCpg( + Config(realFirmwareOutputDir = Some(exportDir.toString)) + .withInputPath(resourceRoot.toString) + .withOutputPath(outputPath) + ) + .get + cpg.close() + + Files.isRegularFile(Paths.get(outputPath)) shouldBe true + + val reopened = CpgLoader.load(outputPath) + try { + reopened.metaData.language.l shouldBe List("LUA") + reopened.file.nameNot(FileTraversal.UNKNOWN).name.l should contain allOf ( + "luci/http.lua", + "luci/http.luac", + "luci/controller/mtkwifi.lua", + "luci/controller/mtkwifi.luac", + "mtkwifi.lua", + "mtkwifi.luac" + ) + } finally { + reopened.close() + } + + val decoderTotals = ujson.read(Files.readString(exportDir.resolve("decoder-summary.json"))).obj("totals").obj + decoderTotals("input_count").num.toInt shouldBe 42 + decoderTotals("decoded_count").num.toInt shouldBe 42 + decoderTotals("diagnostic_count").num.toInt shouldBe 0 + + val stagingStream = Files.list(exportDir.resolve("staging")) + try { + stagingStream.iterator.asScala.count(Files.isRegularFile(_)) shouldBe 42 + } finally { + stagingStream.close() + } + } + } + } +} From 17b0bd7784663d4fd11c18c4b63570a9ac64c2e9 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Fri, 17 Jul 2026 02:12:10 -0400 Subject: [PATCH 089/105] test(lua2cpg): replace vendor firmware tests with neutral corpus coverage --- .../lua2cpg/BytecodeModelSmokeTest.scala | 5 +- .../RealFirmwareEvidenceExportSmokeTest.scala | 2962 +---------------- 2 files changed, 62 insertions(+), 2905 deletions(-) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/BytecodeModelSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/BytecodeModelSmokeTest.scala index bdc69937475c..6aaa53996754 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/BytecodeModelSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/BytecodeModelSmokeTest.scala @@ -27,10 +27,7 @@ class BytecodeModelSmokeTest extends AnyWordSpec with Matchers { reopened.file.nameNot(FileTraversal.UNKNOWN).name.sorted.l should contain allOf ( "bytecode-model/bc-prototype-params/input.luac", "bytecode-model/bc-constants-call/input.luac", - "bytecode-model/bc-stripped-metadata/input.luac", - "OpenWrtDerived-luci/cgi.lua", - "OpenWrtDerived-luci/uci.lua", - "OpenWrtDerived-luci/version.lua" + "bytecode-model/bc-stripped-metadata/input.luac" ) // Expected rows are anchored in the committed bytecode fixtures for diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 2bd1a75f8f0a..6adc2709f08f 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -1,18 +1,16 @@ package io.joern.lua2cpg -import io.joern.lua2cpg.bytecode.* import io.shiftleft.semanticcpg.utils.FileUtil import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec -import java.nio.charset.StandardCharsets -import java.nio.file.{Files, Paths} +import java.nio.file.{Files, Path, Paths} import scala.jdk.CollectionConverters.* class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { "Lua2Cpg" should { - "export Lua real-firmware evidence with scoped callsite rows" in { + "export Lua evidence with scoped callsite rows" in { val resourceRoot = Paths.get(getClass.getClassLoader.getResource("rules-sanitizer-report").toURI) FileUtil.usingTemporaryDirectory("lua2cpg-real-firmware-export-smoke") { tmpDir => @@ -32,2881 +30,86 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { Files.isRegularFile(exportDir.resolve("run-errors.json")) shouldBe true Files.isRegularFile(exportDir.resolve("path-search-profile.json")) shouldBe true - val stagingDir = exportDir.resolve("staging") - val stagingStream = Files.list(stagingDir) - val stagingFiles = stagingStream.iterator.asScala.toVector - try { - stagingFiles.size should be > 0 + val staging = stagingRows(exportDir) + .find(_("relative_path").str.endsWith("d24-sanitizer-suppresses-report/input.luac")) + .getOrElse(fail("missing sanitizer fixture staging evidence")) - val staging = stagingFiles - .map(path => ujson.read(Files.readString(path)).obj) - .find(_("relative_path").str.endsWith("d24-sanitizer-suppresses-report/input.luac")) - .getOrElse(fail("missing sanitizer fixture staging evidence")) - - val callRows = staging("call_name_resolution").arr.map(_.obj) - callRows.exists(row => - row("module_path").str.endsWith("d24-sanitizer-suppresses-report/input.luac") && - hasScopedCallsite(row, "root@pc20") && - row("resolved_name").str == "tonumber" - ) shouldBe true - - val pathRows = staging("path_evidence").arr.map(_.obj) - pathRows.exists(row => - row("path_steps").arr.exists(_.str.endsWith("d24-sanitizer-suppresses-report/input.luac::root@pc20:r2")) - ) shouldBe true - } finally { - stagingStream.close() - } - } - } - - "export OpenWrtDerived real-firmware source-to-sink path evidence without fixture-id fallback" in { - withDLinkStagingRows { stagingRows => - val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) - val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - sourceRows.exists(row => - row("module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && - hasScopedCallsite(row, "root.110@pc3") && - row("trigger").str == "luci.http.formvalue" - ) shouldBe true - - sinkRows.exists(row => - row("module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && - hasScopedCallsite(row, "root.110@pc12") && - row("trigger").str == "os.execute" - ) shouldBe true - - pathRows.size should be > 0 - pathRows.exists(row => - row("source_module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && - row("sink_module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && - row("source_pc").num.toInt == 3 && - row("sink_pc").num.toInt == 12 && - row("path_steps").arr.exists(_.str.contains("::")) && - row("path_steps").arr.exists(_.str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac::root.110@pc3:r0")) && - row("path_steps").arr.exists(_.str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac::root.110@pc12:r1")) - ) shouldBe true - - pathRows.foreach { row => - row("source_module_path").str should not be empty - row("sink_module_path").str should not be empty - row("path_steps").arr.foreach { step => - step.str should include("::") - } - } - pathRows.exists(row => row.obj.contains("callsite_id")) shouldBe false - } - } - - "export OpenWrtDerived root.61 fan-out source-to-sink path evidence" in { - withDLinkStagingRows { stagingRows => - val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) - val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - sourceRows.exists(row => - row("module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && - hasScopedCallsite(row, "root.61@pc63") && - row("trigger").str == "luci.http.formvalue" - ) shouldBe true - - val sinkCallsites = Vector( - "root.61@pc180", - "root.61@pc188", - "root.61@pc196", - "root.61@pc204", - "root.61@pc212", - "root.61@pc220", - "root.61@pc228" - ) - sinkCallsites.foreach { callsiteId => - sourceRows.exists(row => - row("module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && - hasScopedCallsite(row, "root.61@pc63") && - row("trigger").str == "luci.http.formvalue" - ) shouldBe true - - sinkRows.exists(row => - row("module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && - hasScopedCallsite(row, callsiteId) && - row("trigger").str == "os.execute" - ) shouldBe true - } - - sinkCallsites.foreach { callsiteId => - val sinkPc = callsiteId.split("@pc", 2)(1).toInt - pathRows.exists(row => - row("source_module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && - row("sink_module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && - row("source_pc").num.toInt == 63 && - row("sink_pc").num.toInt == sinkPc && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_trigger").str == "os.execute" && - row("path_steps").arr.forall(_.str.contains("::")) - ) shouldBe true - } - pathRows.exists(row => row.obj.contains("callsite_id")) shouldBe false - } - } - - "export OpenWrtDerived cross-module webcmd to mtkwifi popen path evidence" in { - withDLinkStagingRows { stagingRows => - val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) - val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) - val linkRows = stagingRows.flatMap(_("module_linkage").arr.map(_.obj)) - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - sourceRows.exists(row => - row("module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && - hasScopedCallsite(row, "root.55@pc3") && - row("trigger").str == "luci.http.formvalue" - ) shouldBe true - - linkRows.exists(row => - row("module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && - hasScopedCallsite(row, "root.55@pc13") && - row("target_module_path").str.endsWith("usr/lib/lua/mtkwifi.luac") && - row("target_prototype_id").str == "root.12" && - row("field_name").str == "read_pipe" - ) shouldBe true - - sinkRows.exists(row => - row("module_path").str.endsWith("usr/lib/lua/mtkwifi.luac") && - hasScopedCallsite(row, "root.12@pc5") && - row("trigger").str == "io.popen" - ) shouldBe true - - pathRows.exists(row => - row("source_module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && - row("sink_module_path").str.endsWith("usr/lib/lua/mtkwifi.luac") && - row("source_pc").num.toInt == 3 && - row("sink_pc").num.toInt == 5 && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_trigger").str == "io.popen" && - row("path_steps").arr.forall(_.str.contains("::")) - ) shouldBe true - pathRows.exists(row => - row("source_module_path").str.endsWith("usr/lib/lua/luci/controller/mtkwifi.luac") && - row("sink_module_path").str.endsWith("usr/lib/lua/mtkwifi.luac") && - row("source_pc").num.toInt == 4 && - row("source_function_name").str == "root.2" && - row("sink_pc").num.toInt == 5 && - row("sink_function_name").str == "root.12" - ) shouldBe false - pathRows.exists(row => row.obj.contains("callsite_id")) shouldBe false - } - } - - "export CrossPlatform real-firmware source and sink endpoints before path repair" in { - withXiaomiStagingRows { stagingRows => - val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) - val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) - val callRows = stagingRows.flatMap(_("call_name_resolution").arr.map(_.obj)) - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - sourceRows.exists(row => - row("module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && - hasScopedCallsite(row, "root.39@pc15") && - row("trigger").str == "luci.http.formvalue" - ) shouldBe true - - sinkRows.exists(row => - row("module_path").str.endsWith("usr/lib/lua/xiaoqiang/util/XQQoSUtil.luac") && - hasScopedCallsite(row, "root.24@pc82") && - row("trigger").str == "os.execute" - ) shouldBe true - - sinkRows.exists(row => - row("module_path").str.endsWith("usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac") && - hasScopedCallsite(row, "root.0@pc25") && - row("trigger").str == "os.execute" - ) shouldBe true - - sinkRows.exists(row => - row("module_path").str.endsWith("usr/lib/lua/luci/util.luac") && - hasScopedCallsite(row, "root.36@pc3") && - row("trigger").str == "io.popen" - ) shouldBe true - - callRows.exists(row => - row("module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && - hasScopedCallsite(row, "root.145@pc48") && - row("resolved_name").str == "luci.util.exec" - ) shouldBe true - - sinkRows.exists(row => - row("module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && - hasScopedCallsite(row, "root.94@pc38") && - row("trigger").str == "luci.util.exec" - ) shouldBe true - - pathRows.exists(row => row.obj.contains("callsite_id")) shouldBe false - } - } - - "export CrossPlatform representative source-to-sink path evidence" in { - withXiaomiStagingRows { stagingRows => - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - val misystem = "usr/lib/lua/luci/controller/api/misystem.luac" - - pathRows.exists(row => - row("source_module_path").str.endsWith(misystem) && - row("sink_module_path").str.endsWith("usr/lib/lua/xiaoqiang/util/XQQoSUtil.luac") && - row("source_pc").num.toInt == 15 && - row("sink_pc").num.toInt == 82 && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_trigger").str == "os.execute" && - row("path_steps").arr.forall(_.str.contains("::")) && - row("path_steps").arr.exists(_.str == s"$misystem::root.63@pc23:r7") && - row("path_steps").arr.exists(_.str == s"$misystem::root.63@pc23:r6") - ) shouldBe true - - pathRows.exists(row => - row("source_module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && - row("sink_module_path").str.endsWith("usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac") && - row("source_pc").num.toInt == 15 && - row("sink_pc").num.toInt == 25 && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_trigger").str == "os.execute" && - row("path_steps").arr.forall(_.str.contains("::")) - ) shouldBe true - - pathRows.exists(row => - row("source_module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && - row("sink_module_path").str.endsWith("usr/lib/lua/luci/util.luac") && - Set(15, 19).contains(row("source_pc").num.toInt) && - row("sink_pc").num.toInt == 3 && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_trigger").str == "io.popen" && - row("path_steps").arr.forall(_.str.contains("::")) - ) shouldBe true - - pathRows.exists(row => - row("source_module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && - row("sink_module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && - row("source_pc").num.toInt == 15 && - row("sink_pc").num.toInt == 38 && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_trigger").str == "luci.util.exec" && - row("path_steps").arr.forall(_.str.contains("::")) - ) shouldBe true - } - } - - "distinguish source-value bridge proof from representative bridge proof" in { - withXiaomiStagingRows { stagingRows => - val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) - val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) - val bridgeRows = stagingRows.flatMap(_("interproc_arg_flow").arr.map(_.obj)) - val defuseRows = stagingRows.flatMap(_("defuse_paths").arr.map(_.obj)) - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - def hasSource(moduleSuffix: String, pc: Int, trigger: String): Boolean = - sourceRows.exists(row => - row("module_path").str.endsWith(moduleSuffix) && - row("callsite_id").str.endsWith(s"@pc$pc") && - row("callsite_id").str.contains("::") && - row("trigger").str == trigger - ) - - def hasSink(moduleSuffix: String, pc: Int, trigger: String): Boolean = - sinkRows.exists(row => - row("module_path").str.endsWith(moduleSuffix) && - row("callsite_id").str.endsWith(s"@pc$pc") && - row("callsite_id").str.contains("::") && - row("trigger").str == trigger - ) - - def hasBridge( - sourceModuleSuffix: String, - sourceCallsiteSuffix: String, - targetModuleSuffix: String, - targetPrototypeId: String, - argumentIndex: Int - ): Boolean = - bridgeRows.exists(row => - row("callsite_id").str.contains("::") && - row("callsite_id").str.endsWith(sourceCallsiteSuffix) && - row("from_argument_ref").str.contains(sourceModuleSuffix) && - row("argument_index").num.toInt == argumentIndex && - row("target_module_path").str.endsWith(targetModuleSuffix) && - row("target_prototype_id").str == targetPrototypeId - ) - - def pathByPc( - sourceModuleSuffix: String, - sourcePc: Int, - sourceTrigger: String, - sinkModuleSuffix: String, - sinkPc: Int, - sinkTrigger: String - ) = - pathRows.find(row => - row("source_module_path").str.endsWith(sourceModuleSuffix) && - row("source_pc").num.toInt == sourcePc && - row("source_trigger").str == sourceTrigger && - row("sink_module_path").str.endsWith(sinkModuleSuffix) && - row("sink_pc").num.toInt == sinkPc && - row("sink_trigger").str == sinkTrigger && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) - ) - - def pathByFunction( - sourceModuleSuffix: String, - sourceFunctionName: String, - sourcePc: Int, - sourceTrigger: String, - sinkModuleSuffix: String, - sinkFunctionName: String, - sinkPc: Int, - sinkTrigger: String - ) = - pathByPc(sourceModuleSuffix, sourcePc, sourceTrigger, sinkModuleSuffix, sinkPc, sinkTrigger) - .filter(row => - row("source_function_name").str == sourceFunctionName && - row("sink_function_name").str == sinkFunctionName - ) - - hasSource("usr/lib/lua/luci/controller/api/misystem.luac", 57, "luci.http.formvalue") shouldBe true - hasSink("usr/lib/lua/xiaoqiang/common/XQFunction.luac", 35, "os.execute") shouldBe true - hasBridge( - "usr/lib/lua/luci/controller/api/misystem.luac", - "root.37@pc114", - "usr/lib/lua/xiaoqiang/common/XQFunction.luac", - "root.33", - 1 - ) shouldBe true - - pathByPc( - "usr/lib/lua/luci/controller/api/misystem.luac", - 57, - "luci.http.formvalue", - "usr/lib/lua/xiaoqiang/common/XQFunction.luac", - 35, - "os.execute" - ).isDefined shouldBe true - - defuseRows.exists(row => - row("source_ref").str == "root.37@pc57:r15" && - row("sink_ref").str == "root.37@pc114:r28" && - row("first_missing_edge").str == "none" - ) shouldBe false - - val strictBridgePath = pathByFunction( - "usr/lib/lua/luci/controller/api/misystem.luac", - "memTestConfig", - 14, - "luci.http.formvalue", - "usr/lib/lua/xiaoqiang/common/XQFunction.luac", - "nvramSet", - 35, - "os.execute" - ) - strictBridgePath.isDefined shouldBe true - val strictBridgeSteps = strictBridgePath.get("path_steps").arr.map(_.str).toSet - strictBridgeSteps should contain("usr/lib/lua/luci/controller/api/misystem.luac::root.151@pc22:r6") - strictBridgeSteps should contain("usr/lib/lua/xiaoqiang/common/XQFunction.luac::root.33:r1") - strictBridgeSteps should contain("usr/lib/lua/xiaoqiang/common/XQFunction.luac::root.33@pc30:r3") - } - } - - "preserve CrossPlatform r5 representative bridge rows without unscoped fallback" in { - withXiaomiStagingRows { stagingRows => - val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) - val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val sourceModule = "usr/lib/lua/luci/controller/api/xqsystem.luac" - val sinkModule = "usr/lib/lua/xiaoqiang/common/XQFunction.luac" - - sourceRows.exists(row => - row("module_path").str.endsWith(sourceModule) && - row("callsite_id").str.contains("::") && - row("callsite_id").str.endsWith("::root.40@pc31") && - row("trigger").str == "luci.http.formvalue" - ) shouldBe true - - sinkRows.exists(row => - row("module_path").str.endsWith(sinkModule) && - row("callsite_id").str.endsWith("::root.33@pc35") && - row("trigger").str == "os.execute" - ) shouldBe true - - val representativePath = pathRows.find(row => - row("source_module_path").str.endsWith(sourceModule) && - row("source_function_name").str == "setRouter" && - row("source_pc").num.toInt == 31 && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str.endsWith(sinkModule) && - row("sink_function_name").str == "nvramSet" && - row("sink_pc").num.toInt == 35 && - row("sink_trigger").str == "os.execute" && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) - ) - - representativePath.isDefined shouldBe true - representativePath.get.obj.contains("callsite_id") shouldBe false - - val steps = representativePath.get("path_steps").arr.map(_.str) - steps.exists(_.startsWith(s"$sourceModule::")) shouldBe true - steps.exists(_.startsWith(s"$sinkModule::")) shouldBe true - } - } - - "export CrossPlatform r5 residual sink endpoints and source-to-sink paths" in { - withXiaomiStagingRows { stagingRows => - val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - def hasSink(moduleSuffix: String, pc: Int, trigger: String): Boolean = - sinkRows.exists(row => - row("module_path").str.endsWith(moduleSuffix) && - row("callsite_id").str.endsWith(s"@pc$pc") && - row("callsite_id").str.contains("::") && - row("trigger").str == trigger - ) - - def hasPath( - sourceModuleSuffix: String, - sourceFunctionName: String, - sourcePc: Int, - sinkModuleSuffix: String, - sinkFunctionName: String, - sinkPc: Int, - sinkTrigger: String - ): Boolean = - pathRows.exists(row => - row("source_module_path").str.endsWith(sourceModuleSuffix) && - row("source_function_name").str == sourceFunctionName && - row("source_pc").num.toInt == sourcePc && - row("sink_module_path").str.endsWith(sinkModuleSuffix) && - row("sink_function_name").str == sinkFunctionName && - row("sink_pc").num.toInt == sinkPc && - row("sink_trigger").str == sinkTrigger && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) - ) - - hasSink( - "usr/lib/lua/luci/controller/api/xqnetwork.luac", - 276, - "test.api.Process.forkExec" - ) shouldBe true - hasSink( - "usr/lib/lua/xiaoqiang/module/XQEcos.luac", - 15, - "test.api.Process.forkExec" - ) shouldBe true - hasSink( - "usr/lib/lua/xiaoqiang/util/XQSysUtil.luac", - 112, - "test.api.Process.forkExec" - ) shouldBe true - hasSink("usr/lib/lua/xiaoqiang/common/XQFunction.luac", 56, "forkExec") shouldBe true - - hasPath( - "usr/lib/lua/luci/controller/api/misystem.luac", - "networkAccessControlStatus", - 8, - "usr/lib/lua/xiaoqiang/module/XQParentControl.luac", - "get_macfilter_wan", - 10, - "luci.util.exec" - ) shouldBe true - hasPath( - "usr/lib/lua/luci/controller/api/misystem.luac", - "parentalctlSetUrl", - 8, - "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac", - "func_unknow_0_0", - 25, - "os.execute" - ) shouldBe true - hasPath( - "usr/lib/lua/luci/controller/api/misystem.luac", - "parentalctlSetUrl", - 8, - "usr/lib/lua/xiaoqiang/common/XQFunction.luac", - "thrift_tunnel_to_datacenter", - 22, - "luci.util.exec" - ) shouldBe true - hasPath( - "usr/lib/lua/luci/controller/api/misystem.luac", - "qosApp", - 16, - "usr/lib/lua/luci/controller/api/misystem.luac", - "qosApp", - 102, - "os.execute" - ) shouldBe true - hasPath( - "usr/lib/lua/luci/controller/api/xqsystem.luac", - "ExtendWifiConnectInitedRouter", - 32, - "usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac", - "apcli_get_connect", - 24, - "luci.util.exec" - ) shouldBe true - hasPath( - "usr/lib/lua/luci/controller/api/xqsystem.luac", - "ExtendWifiConnectInitedRouter", - 32, - "usr/lib/lua/xiaoqiang/module/XQAPModule.luac", - "extendwifi_set_connect", - 139, - "luci.util.exec" - ) shouldBe true - hasPath( - "usr/lib/lua/luci/controller/api/xqsystem.luac", - "setPassword", - 16, - "usr/lib/lua/xiaoqiang/util/XQSecureUtil.luac", - "decCiphertext", - 46, - "os.execute" - ) shouldBe true - hasPath( - "usr/lib/lua/luci/controller/service/datacenter.luac", - "setSyncRouterFile", - 6, - "usr/lib/lua/luci/controller/service/datacenter.luac", - "tunnelRequestDatacenter", - 24, - "luci.util.exec" - ) shouldBe true - } - } - - "export CrossPlatform r7 regressed source-to-sink paths without unscoped fallback" in { - withXiaomiStagingRows { stagingRows => - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - def hasPath( - sourceModuleSuffix: String, - sourceFunctionName: String, - sourcePc: Int, - sinkModuleSuffix: String, - sinkFunctionName: String, - sinkPc: Int, - sinkTrigger: String - ): Boolean = - pathRows.exists(row => - row("source_module_path").str.endsWith(sourceModuleSuffix) && - row("source_function_name").str == sourceFunctionName && - row("source_pc").num.toInt == sourcePc && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str.endsWith(sinkModuleSuffix) && - row("sink_function_name").str == sinkFunctionName && - row("sink_pc").num.toInt == sinkPc && - row("sink_trigger").str == sinkTrigger && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - !row.obj.contains("callsite_id") - ) - - val missingFamilies = Vector( - "xqsmarthome.requestMitv@pc3->luci.util.exec@pc3" -> hasPath( - "usr/lib/lua/luci/controller/api/xqsmarthome.luac", - "requestMitv", - 3, - "usr/lib/lua/luci/util.luac", - "exec", - 3, - "io.popen" - ), - "xqsystem.sysRecovery@pc12->XQFunction.nvramSet@pc35" -> hasPath( - "usr/lib/lua/luci/controller/api/xqsystem.luac", - "sysRecovery", - 12, - "usr/lib/lua/xiaoqiang/common/XQFunction.luac", - "nvramSet", - 35, - "os.execute" - ), - "misystem.setLanApMode_Init@pc22->XQSynchrodata.func_unknow_0_0@pc25" -> hasPath( - "usr/lib/lua/luci/controller/api/misystem.luac", - "setLanApMode_Init", - 22, - "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac", - "func_unknow_0_0", - 25, - "os.execute" - ), - "misystem.setWifiApMode@pc61->XQSynchrodata.func_unknow_0_0@pc25" -> hasPath( - "usr/lib/lua/luci/controller/api/misystem.luac", - "setWifiApMode", - 61, - "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac", - "func_unknow_0_0", - 25, - "os.execute" - ), - "xqnetwork.pppoeStatus@pc6->luci.util.exec@pc3" -> hasPath( - "usr/lib/lua/luci/controller/api/xqnetwork.luac", - "pppoeStatus", - 6, - "usr/lib/lua/luci/util.luac", - "exec", - 3, - "io.popen" - ), - "xqnetwork.setPeerWifiAutoAPMode@pc42->XQWifiUtil.apcli_set_inactive@pc75" -> hasPath( - "usr/lib/lua/luci/controller/api/xqnetwork.luac", - "setPeerWifiAutoAPMode", - 42, - "usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac", - "apcli_set_inactive", - 75, - "os.execute" - ), - "miats.getWifiMacfilterInfo@pc70->luci.util.exec@pc3" -> hasPath( - "usr/lib/lua/luci/controller/api/miats.luac", - "getWifiMacfilterInfo", - 70, - "usr/lib/lua/luci/util.luac", - "exec", - 3, - "io.popen" - ) - ).collect { case (label, false) => label } - - withClue(s"missing families: ${missingFamilies.mkString(", ")}") { - missingFamilies shouldBe empty - } - } - } - - "export CrossPlatform r7 miats pc148 sink endpoint with exact module scope" in { - withXiaomiStagingRows { stagingRows => - val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) - - sinkRows.count(row => - row("module_path").str.endsWith("usr/lib/lua/luci/controller/api/miats.luac") && - row("callsite_id").str.contains("::") && - row("callsite_id").str.endsWith("@pc148") && - row("trigger").str == "luci.util.exec" - ) shouldBe 1 - } - } - - "export CrossPlatform r7 setWifiApMode to nvramSet path with exact module scope" in { - withXiaomiStagingRows { stagingRows => - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - pathRows.exists(row => - row("source_module_path").str.endsWith("usr/lib/lua/luci/controller/api/xqnetwork.luac") && - row("source_function_name").str == "setWifiApMode" && - row("source_pc").num.toInt == 28 && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str.endsWith("usr/lib/lua/xiaoqiang/common/XQFunction.luac") && - row("sink_function_name").str == "nvramSet" && - row("sink_pc").num.toInt == 35 && - row("sink_trigger").str == "os.execute" && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - !row.obj.contains("callsite_id") - ) shouldBe true - } - } - - "export CrossPlatform r7 setWifiApMode to XQSynchrodata path with exact module scope" in { - withXiaomiStagingRows { stagingRows => - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - val targetPath = pathRows.find(row => - row("source_module_path").str.endsWith("usr/lib/lua/luci/controller/api/xqnetwork.luac") && - row("source_function_name").str == "setWifiApMode" && - row("source_pc").num.toInt == 28 && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str.endsWith("usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac") && - row("sink_function_name").str == "func_unknow_0_0" && - row("sink_pc").num.toInt == 25 && - row("sink_trigger").str == "os.execute" - ) - - targetPath.isDefined shouldBe true - val pathSteps = targetPath.get("path_steps").arr.map(_.str) - pathSteps should contain("usr/lib/lua/luci/controller/api/xqnetwork.luac::root.93@pc28:r8") - pathSteps.exists(_.startsWith("usr/lib/lua/xiaoqiang/module/XQAPModule.luac::")) shouldBe true - pathSteps should contain("usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac::root.0@pc25:r4") - pathSteps.foreach(_ should include("::")) - targetPath.get.obj.contains("callsite_id") shouldBe false - } - } - - "export CrossPlatform r7 XQSynchrodata synthetic report-facing function identity" in { - withXiaomiStagingRows { stagingRows => - val synchrodata = stagingRows - .find(_("relative_path").str.endsWith("usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac")) - .getOrElse(fail("missing XQSynchrodata staging evidence")) - - val identityRows = synchrodata("function_identity").arr.map(_.obj) - identityRows.exists(row => - row("module_path").str == "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac" && - row("prototype_id").str == "root.0" && - row("display_name").str == "func_unknow_0_0" && - row("identity_kind").str == "synthetic" && - row("provenance").str == "upstream-lua2cpg,bytecode-only,synthetic-name" - ) shouldBe true - } - } - - "export CrossPlatform r7 XQWifiUtil to XQSynchrodata strict producer evidence" in { - withXiaomiStagingRows { stagingRows => - val wifiUtil = stagingRows - .find(_("relative_path").str.endsWith("usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac")) - .getOrElse(fail("missing XQWifiUtil staging evidence")) - - val linkageRows = wifiUtil("module_linkage").arr.map(_.obj) - linkageRows.exists(row => - row("callsite_id").str == "usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac::root.41@pc225" && - row("target_module_path").str == "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac" && - row("target_prototype_id").str == "root.3" && - row("field_name").str == "syncWiFiSSID" && - row("resolution_status").str == "matched" - ) shouldBe true - - val argRows = wifiUtil("interproc_arg_flow").arr.map(_.obj) - argRows.exists(row => - row("callsite_id").str == "usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac::root.41@pc225" && - row("from_argument_ref").str == "usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac:root.41@pc225:r22" && - row("argument_index").num.toInt == 1 && - row("target_module_path").str == "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac" && - row("target_prototype_id").str == "root.3" && - row("to_parameter_ref").str == "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac:root.3:r1" - ) shouldBe true - } - } - - "export CrossPlatform r7 requestMitv paths through call result flow" in { - withXiaomiStagingRows { stagingRows => - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - def requestMitvPath(sinkModule: String, sinkFunction: String, sinkPc: Int, sinkTrigger: String) = - pathRows.find(row => - row("source_module_path").str == "usr/lib/lua/luci/controller/api/xqsmarthome.luac" && - row("source_function_name").str == "requestMitv" && - row("source_pc").num.toInt == 3 && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == sinkModule && - row("sink_function_name").str == sinkFunction && - row("sink_pc").num.toInt == sinkPc && - row("sink_trigger").str == sinkTrigger && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - !row.obj.contains("callsite_id") - ) - - val doExecPath = requestMitvPath( - "usr/lib/lua/xiaoqiang/util/XQMitvUtil.luac", - "DoExec", - 10, - "luci.util.exec" - ).getOrElse(fail("missing requestMitv to XQMitvUtil.DoExec strict path")) - val doExecSteps = doExecPath("path_steps").arr.map(_.str) - - doExecSteps should contain("usr/lib/lua/luci/controller/api/xqsmarthome.luac::root.5@pc3:r0") - doExecSteps should contain("usr/lib/lua/luci/controller/api/xqsmarthome.luac::root.5@pc11:r4") - doExecSteps should contain("usr/lib/lua/xiaoqiang/util/XQMitvUtil.luac::root.1:r0") - doExecSteps should contain("usr/lib/lua/xiaoqiang/util/XQMitvUtil.luac::root.1@pc7:r2") - doExecSteps should contain("usr/lib/lua/xiaoqiang/util/XQMitvUtil.luac::root.1@pc7:r1") - doExecSteps.exists(_.startsWith("usr/lib/lua/xiaoqiang/util/XQMitvUtil.luac::root.0")) shouldBe true - doExecSteps should contain("usr/lib/lua/xiaoqiang/util/XQMitvUtil.luac::root.0@pc10:r3") - assertRequestMitvStringMatchSanitizer(doExecPath) - - val popenPath = requestMitvPath( - "usr/lib/lua/luci/util.luac", - "exec", - 3, - "io.popen" - ).getOrElse(fail("missing requestMitv to luci.util.exec strict path")) - val popenSteps = popenPath("path_steps").arr.map(_.str) - - popenSteps should contain("usr/lib/lua/luci/controller/api/xqsmarthome.luac::root.5@pc3:r0") - popenSteps should contain("usr/lib/lua/xiaoqiang/util/XQMitvUtil.luac::root.1@pc7:r2") - popenSteps should contain("usr/lib/lua/xiaoqiang/util/XQMitvUtil.luac::root.1@pc7:r1") - popenSteps should contain("usr/lib/lua/xiaoqiang/util/XQMitvUtil.luac::root.0@pc10:r3") - popenSteps should contain("usr/lib/lua/luci/util.luac::root.36:r0") - popenSteps should contain("usr/lib/lua/luci/util.luac::root.36@pc3:r2") - assertRequestMitvStringMatchSanitizer(popenPath) - } - } - - "export CrossPlatform r7 pppoeStatus path to luci util exec" in { - withXiaomiStagingRows { stagingRows => - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val sanitizerCall = "usr/lib/lua/xiaoqiang/util/XQCryptoUtil.luac::root.3@pc8" - val sanitizerValue = s"$sanitizerCall:r4" - - val path = pathRows - .find(row => - row("source_module_path").str == "usr/lib/lua/luci/controller/api/xqnetwork.luac" && - row("source_function_name").str == "pppoeStatus" && - row("source_pc").num.toInt == 6 && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == "usr/lib/lua/luci/util.luac" && - row("sink_function_name").str == "exec" && - row("sink_pc").num.toInt == 3 && - row("sink_trigger").str == "io.popen" && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - !row.obj.contains("callsite_id") - ) - .getOrElse(fail("missing pppoeStatus to luci.util.exec strict path")) - - val steps = path("path_steps").arr.map(_.str) - steps should contain("usr/lib/lua/luci/controller/api/xqnetwork.luac::root.55@pc6:r1") - steps should contain(sanitizerValue) - steps should contain("usr/lib/lua/luci/util.luac::root.36:r0") - steps should contain("usr/lib/lua/luci/util.luac::root.36@pc3:r2") - - withClue( - s"path_steps=${steps.mkString("[", ",", "]")} sanitizer_hits=${path("sanitizer_hits")}" - ) { - path("classification").str shouldBe "sanitized" - path("sanitizer_hits").arr.exists { hit => - val row = hit.obj - row("callsite_id").str == sanitizerCall && - row("sanitizer_name").str == "test.api.Process._cmdformat" && - row("applies_to_sink").bool && - row("on_dataflow_chain").bool - } shouldBe true - } - } - } - - "export CrossPlatform r7 miats getWifiMacfilterInfo path to luci util exec" in { - withXiaomiStagingRows { stagingRows => - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val path = pathRows - .find(row => - row("source_module_path").str == "usr/lib/lua/luci/controller/api/miats.luac" && - row("source_function_name").str == "getWifiMacfilterInfo" && - row("source_pc").num.toInt == 70 && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == "usr/lib/lua/luci/util.luac" && - row("sink_function_name").str == "exec" && - row("sink_pc").num.toInt == 3 && - row("sink_trigger").str == "io.popen" && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - !row.obj.contains("callsite_id") - ) - .getOrElse(fail("missing miats.getWifiMacfilterInfo to luci.util.exec strict path")) - - val steps = path("path_steps").arr.map(_.str) - steps should contain("usr/lib/lua/luci/controller/api/miats.luac::root.3@pc70:r7") - steps should contain("usr/lib/lua/luci/util.luac::root.36:r0") - steps should contain("usr/lib/lua/luci/util.luac::root.36@pc3:r2") - } - } - - "export CrossPlatform r7 vpnSwitch path to XQCryptoUtil md5Str" in { - withXiaomiStagingRows { stagingRows => - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val path = pathRows - .find(row => - row("source_module_path").str == "usr/lib/lua/luci/controller/api/xqsystem.luac" && - row("source_function_name").str == "vpnSwitch" && - row("source_pc").num.toInt == 12 && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == "usr/lib/lua/xiaoqiang/util/XQCryptoUtil.luac" && - row("sink_function_name").str == "md5Str" && - row("sink_pc").num.toInt == 10 && - row("sink_trigger").str == "luci.util.exec" && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - !row.obj.contains("callsite_id") - ) - .getOrElse(fail("missing vpnSwitch to XQCryptoUtil.md5Str strict path")) - - val steps = path("path_steps").arr.map(_.str) - steps should contain("usr/lib/lua/luci/controller/api/xqsystem.luac::root.92@pc12:r2") - steps should contain("usr/lib/lua/xiaoqiang/util/XQCryptoUtil.luac::root.3@pc10:r4") - } - } - - "export CrossPlatform r7 editDevice path to XQWifiUtil wl_editWiFiMacfilterList" in { - withXiaomiExportDir { exportDir => - val profile = ujson.read(Files.readString(exportDir.resolve("path-search-profile.json"))).obj - profile("report_count").num.toInt should be <= 4000 - profile("local_path_search_count").num.toInt should be <= 30000 - - val stagingDir = exportDir.resolve("staging") - val stagingStream = Files.list(stagingDir) - val stagingRows = stagingStream.iterator.asScala.toVector.map(path => ujson.read(Files.readString(path)).obj) - try { - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val path = pathRows - .find(row => - row("source_module_path").str == "usr/lib/lua/luci/controller/api/xqnetwork.luac" && - row("source_function_name").str == "editDevice" && - row("source_pc").num.toInt == 20 && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == "usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac" && - row("sink_function_name").str == "wl_editWiFiMacfilterList" && - row("sink_pc").num.toInt == 348 && - row("sink_trigger").str == "os.execute" && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - !row.obj.contains("callsite_id") - ) - .getOrElse(fail("missing editDevice to XQWifiUtil.wl_editWiFiMacfilterList strict path")) - - val steps = path("path_steps").arr.map(_.str) - steps should contain("usr/lib/lua/luci/controller/api/xqnetwork.luac::root.48@pc20:r7") - steps should contain("usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac::root.82@pc348:r17") - } finally { - stagingStream.close() - } - } - } - - "export CrossPlatform r7 editDevice path with _cmdformat sanitizer hit" in { - withXiaomiStagingRows { stagingRows => - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val sourceModule = "usr/lib/lua/luci/controller/api/xqnetwork.luac" - val sinkModule = "usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac" - val sanitizerModule = "usr/lib/lua/xiaoqiang/common/XQFunction.luac" - val sanitizerCall = s"$sanitizerModule::root.0@pc12" - val sanitizerValue = s"$sanitizerCall:r1" - - val path = pathRows - .find(row => - row("source_module_path").str == sourceModule && - row("source_function_name").str == "editDevice" && - row("source_pc").num.toInt == 20 && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == sinkModule && - row("sink_function_name").str == "wl_editWiFiMacfilterList" && - row("sink_pc").num.toInt == 348 && - row("sink_trigger").str == "os.execute" && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - row("path_steps").arr.exists(_.str == sanitizerValue) && - !row.obj.contains("callsite_id") - ) - .getOrElse(fail("missing editDevice to wl_editWiFiMacfilterList strict path")) - - withClue( - s"path_steps=${path("path_steps").arr.map(_.str).mkString("[", ",", "]")} sanitizer_hits=${path("sanitizer_hits")}" - ) { - path("classification").str shouldBe "sanitized" - path("sanitizer_hits").arr.exists { hit => - val row = hit.obj - row("callsite_id").str == sanitizerCall && - row("callsite_id").str.contains("::") && - row("sanitizer_name").str == "_cmdformat" && - row("applies_to_sink").bool && - row("on_dataflow_chain").bool - } shouldBe true - } - } - } - - "export CrossPlatform r7 setConfigIotDevHidessid paths with _cmdformat sanitizer hits" in { - withXiaomiStagingRows { stagingRows => - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val module = "usr/lib/lua/luci/controller/api/misystem.luac" - val expectedPairs = Vector( - 11 -> 161, - 11 -> 186, - 19 -> 161, - 19 -> 186, - 23 -> 161, - 23 -> 186, - 27 -> 161, - 27 -> 186, - 31 -> 186, - 35 -> 186 - ) - - expectedPairs.foreach { case (sourcePc, sinkPc) => - val path = pathRows - .find(row => - row("source_module_path").str == module && - row("source_function_name").str == "setConfigIotDevHidessid" && - row("source_pc").num.toInt == sourcePc && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == module && - row("sink_function_name").str == "setConfigIotDevHidessid" && - row("sink_pc").num.toInt == sinkPc && - row("sink_trigger").str == "test.api.Process.forkExec" && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - !row.obj.contains("callsite_id") - ) - .getOrElse(fail(s"missing setConfigIotDevHidessid strict path sourcePc=$sourcePc sinkPc=$sinkPc")) - - withClue( - s"sourcePc=$sourcePc sinkPc=$sinkPc path_steps=${path("path_steps").arr.map(_.str).mkString("[", ",", "]")} sanitizer_hits=${path("sanitizer_hits")}" - ) { - path("classification").str shouldBe "sanitized" - path("sanitizer_hits").arr.exists { hit => - val row = hit.obj - row("callsite_id").str.startsWith(s"$module::root.172@pc") && - row("callsite_id").str.contains("::") && - row("sanitizer_name").str == "test.api.Process._cmdformat" && - row("applies_to_sink").bool && - row("on_dataflow_chain").bool - } shouldBe true - } - } - } - } - - "export CrossPlatform r7 addMeshNode paths with _strformat sanitizer hits" in { - withXiaomiStagingRows { stagingRows => - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val sourceModule = "usr/lib/lua/luci/controller/api/xqnetwork.luac" - val sinkModule = "usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac" - val expectedPairs = Vector( - (11, 16, 15), - (11, 32, 31), - (15, 32, 31) - ) - - expectedPairs.foreach { case (sourcePc, sinkPc, sanitizerPc) => - val path = pathRows - .find(row => - row("source_module_path").str == sourceModule && - row("source_function_name").str == "addMeshNode" && - row("source_pc").num.toInt == sourcePc && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == sinkModule && - row("sink_function_name").str == "mesh_add_node" && - row("sink_pc").num.toInt == sinkPc && - row("sink_trigger").str == "test.api.Process.forkExec" && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - !row.obj.contains("callsite_id") - ) - .getOrElse(fail(s"missing addMeshNode strict path sourcePc=$sourcePc sinkPc=$sinkPc")) - - withClue( - s"sourcePc=$sourcePc sinkPc=$sinkPc path_steps=${path("path_steps").arr.map(_.str).mkString("[", ",", "]")} sanitizer_hits=${path("sanitizer_hits")}" - ) { - path("classification").str shouldBe "sanitized" - path("sanitizer_hits").arr.exists { hit => - val row = hit.obj - row("callsite_id").str == s"$sinkModule::root.101@pc$sanitizerPc" && - row("sanitizer_name").str == "test.api.Process._strformat" && - row("applies_to_sink").bool && - row("on_dataflow_chain").bool - } shouldBe true - } - } - } - } - - "export CrossPlatform r7 debug paths with _cmdformat sanitizer hits" in { - withXiaomiStagingRows { stagingRows => - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val module = "usr/lib/lua/luci/controller/api/misystem.luac" - val utilModule = "usr/lib/lua/luci/util.luac" - val expectedPaths = Vector( - (19, utilModule, "exec", 3, "io.popen", 36, 10), - (15, module, "debug", 38, "luci.util.exec", 33, 9), - (19, module, "debug", 38, "luci.util.exec", 36, 10) - ) - - expectedPaths.foreach { case (sourcePc, sinkModule, sinkFunction, sinkPc, sinkTrigger, sanitizerPc, sanitizerSlot) => - val sanitizerCall = s"$module::root.94@pc$sanitizerPc" - val sanitizerValue = s"$sanitizerCall:r$sanitizerSlot" - val path = pathRows - .find(row => - row("source_module_path").str == module && - row("source_function_name").str == "debug" && - row("source_pc").num.toInt == sourcePc && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == sinkModule && - row("sink_function_name").str == sinkFunction && - row("sink_pc").num.toInt == sinkPc && - row("sink_trigger").str == sinkTrigger && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - !row.obj.contains("callsite_id") - ) - .getOrElse(fail(s"missing debug strict path sourcePc=$sourcePc sinkPc=$sinkPc sink=$sinkModule")) - - withClue( - s"sourcePc=$sourcePc sinkPc=$sinkPc path_steps=${path("path_steps").arr.map(_.str).mkString("[", ",", "]")} sanitizer_hits=${path("sanitizer_hits")}" - ) { - path("path_steps").arr.exists(_.str == sanitizerValue) shouldBe true - path("classification").str shouldBe "sanitized" - path("sanitizer_hits").arr.exists { hit => - val row = hit.obj - row("callsite_id").str == sanitizerCall && - row("sanitizer_name").str == "test.api.Process._cmdformat" && - row("applies_to_sink").bool && - row("on_dataflow_chain").bool - } shouldBe true - } - } - } - } - - "export CrossPlatform r7 appSetWifiApMode exec path with apcli sanitizer hit" in { - withXiaomiStagingRows { stagingRows => - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val sourceModule = "usr/lib/lua/luci/controller/api/xqnetwork.luac" - val sinkModule = "usr/lib/lua/luci/util.luac" - val sanitizerCall = "usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac::root.55@pc55" - val sanitizerValue = s"$sanitizerCall:r14" - val sanitizerPrefix = "xiaoqiang.util.XQWifiUtil.apcli_get" - - val path = pathRows - .find(row => - row("source_module_path").str == sourceModule && - row("source_function_name").str == "appSetWifiApMode" && - row("source_pc").num.toInt == 61 && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == sinkModule && - row("sink_function_name").str == "exec" && - row("sink_pc").num.toInt == 3 && - row("sink_trigger").str == "io.popen" && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - !row.obj.contains("callsite_id") - ) - .getOrElse(fail("missing appSetWifiApMode strict path sourcePc=61 sink=luci.util.exec")) - - withClue( - s"path_steps=${path("path_steps").arr.map(_.str).mkString("[", ",", "]")} sanitizer_hits=${path("sanitizer_hits")}" - ) { - path("path_steps").arr.exists(_.str == sanitizerValue) shouldBe true - path("classification").str shouldBe "sanitized" - path("sanitizer_hits").arr.exists { hit => - val row = hit.obj - row("callsite_id").str == sanitizerCall && - row("sanitizer_name").str.startsWith(sanitizerPrefix) && - row("applies_to_sink").bool && - row("on_dataflow_chain").bool - } shouldBe true - } - } - } - - "export CrossPlatform r7 misystem setWifiAPMode paths with scoped sanitizer hits" in { - withXiaomiStagingRows { stagingRows => - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val sourceModule = "usr/lib/lua/luci/controller/api/misystem.luac" - val apModule = "usr/lib/lua/xiaoqiang/module/XQAPModule.luac" - val utilModule = "usr/lib/lua/luci/util.luac" - val sanitizerName = "xiaoqiang.module.XQAPModule.setWifiAPMode" - val expectedPaths = Vector( - ("setWifiApMode", 101, apModule, "setWifiAPMode", 211, "os.execute", "root.37@pc168", "root.37@pc168:r32"), - ("setWifiApMode_Init", 104, apModule, "setWifiAPMode", 211, "os.execute", "root.38@pc200", "root.38@pc200:r34"), - ("setWifiApMode_Init", 104, utilModule, "exec", 3, "io.popen", "root.38@pc200", "root.38@pc200:r34") - ) - - expectedPaths.foreach { - case (sourceFunction, sourcePc, sinkModule, sinkFunction, sinkPc, sinkTrigger, sanitizerLocalCall, sanitizerLocalValue) => - val sanitizerCall = s"$sourceModule::$sanitizerLocalCall" - val sanitizerValue = s"$sourceModule::$sanitizerLocalValue" - val matchingPaths = pathRows.filter(row => - row("source_module_path").str == sourceModule && - row("source_function_name").str == sourceFunction && - row("source_pc").num.toInt == sourcePc && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == sinkModule && - row("sink_function_name").str == sinkFunction && - row("sink_pc").num.toInt == sinkPc && - row("sink_trigger").str == sinkTrigger && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - !row.obj.contains("callsite_id") - ) - - matchingPaths should not be empty - matchingPaths.foreach { path => - withClue( - s"$sourceFunction sourcePc=$sourcePc sink=$sinkFunction pc=$sinkPc path_steps=${path("path_steps").arr.map(_.str).mkString("[", ",", "]")} sanitizer_hits=${path("sanitizer_hits")}" - ) { - path("path_steps").arr.exists(_.str == sanitizerValue) shouldBe true - path("classification").str shouldBe "sanitized" - path("sanitizer_hits").arr.exists { hit => - val row = hit.obj - row("callsite_id").str == sanitizerCall && - row("sanitizer_name").str == sanitizerName && - row("applies_to_sink").bool && - row("on_dataflow_chain").bool - } shouldBe true - } - } - } - } - } - - "export CrossPlatform r7 setRouterToBaidu path with ipairs sanitizer hit" in { - withXiaomiStagingRows { stagingRows => - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val sourceModule = "usr/lib/lua/luci/controller/api/xqnetwork.luac" - val sinkModule = "usr/lib/lua/xiaoqiang/module/XQBaiduPanUtil.luac" - val sanitizerCall = "usr/lib/lua/xiaoqiang/module/XQBaiduPanUtil.luac::root.43@pc3" - val sanitizerValue = s"$sanitizerCall:r4" - - val path = pathRows - .find(row => - row("source_module_path").str == sourceModule && - row("source_function_name").str == "setRouterToBaidu" && - row("source_pc").num.toInt == 27 && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == sinkModule && - row("sink_function_name").str == "handleFileDirname" && - row("sink_pc").num.toInt == 57 && - row("sink_trigger").str == "luci.util.exec" && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - !row.obj.contains("callsite_id") - ) - .getOrElse(fail("missing setRouterToBaidu strict path sourcePc=27 sink=handleFileDirname")) - - withClue( - s"path_steps=${path("path_steps").arr.map(_.str).mkString("[", ",", "]")} sanitizer_hits=${path("sanitizer_hits")}" - ) { - path("path_steps").arr.exists(_.str == sanitizerValue) shouldBe true - path("classification").str shouldBe "sanitized" - path("sanitizer_hits").arr.exists { hit => - val row = hit.obj - row("callsite_id").str == sanitizerCall && - row("sanitizer_name").str == "ipairs.match" && - row("applies_to_sink").bool && - row("on_dataflow_chain").bool - } shouldBe true - } - } - } - - "export CrossPlatform r7 xqsystem payment paths with _cmdformat sanitizer hits" in { - withXiaomiStagingRows { stagingRows => - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val module = "usr/lib/lua/luci/controller/api/xqsystem.luac" - val utilModule = "usr/lib/lua/luci/util.luac" - val expectedPaths = Vector( - ("setPaymentInfo", 19, module, "setPaymentInfo", 37, "luci.util.exec", "root.123@pc33", "root.123@pc33:r6"), - ("setPaymentInfo", 19, utilModule, "exec", 3, "io.popen", "root.123@pc33", "root.123@pc33:r6"), - ("signOrder", 14, module, "signOrder", 58, "luci.util.exec", "root.124@pc54", "root.124@pc54:r7"), - ("signOrder", 14, utilModule, "exec", 3, "io.popen", "root.124@pc54", "root.124@pc54:r7"), - ("signOrder", 18, module, "signOrder", 58, "luci.util.exec", "root.124@pc54", "root.124@pc54:r7"), - ("signOrder", 18, utilModule, "exec", 3, "io.popen", "root.124@pc54", "root.124@pc54:r7") - ) - - expectedPaths.foreach { - case (sourceFunction, sourcePc, sinkModule, sinkFunction, sinkPc, sinkTrigger, sanitizerLocalCall, sanitizerLocalValue) => - val sanitizerCall = s"$module::$sanitizerLocalCall" - val sanitizerValue = s"$module::$sanitizerLocalValue" - val path = pathRows - .find(row => - row("source_module_path").str == module && - row("source_function_name").str == sourceFunction && - row("source_pc").num.toInt == sourcePc && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == sinkModule && - row("sink_function_name").str == sinkFunction && - row("sink_pc").num.toInt == sinkPc && - row("sink_trigger").str == sinkTrigger && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - !row.obj.contains("callsite_id") - ) - .getOrElse(fail(s"missing xqsystem payment strict path $sourceFunction sourcePc=$sourcePc sink=$sinkFunction pc=$sinkPc")) - - withClue( - s"$sourceFunction sourcePc=$sourcePc sink=$sinkFunction pc=$sinkPc path_steps=${path("path_steps").arr.map(_.str).mkString("[", ",", "]")} sanitizer_hits=${path("sanitizer_hits")}" - ) { - path("path_steps").arr.exists(_.str == sanitizerValue) shouldBe true - path("classification").str shouldBe "sanitized" - path("sanitizer_hits").arr.exists { hit => - val row = hit.obj - row("callsite_id").str == sanitizerCall && - row("sanitizer_name").str == "test.api.Process._cmdformat" && - row("applies_to_sink").bool && - row("on_dataflow_chain").bool - } shouldBe true - } - } - } - } - - "export CrossPlatform r7 XQAPModule extendwifi paths with apcli sanitizer hits" in { - withXiaomiStagingRows { stagingRows => - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val sinkModule = "usr/lib/lua/xiaoqiang/module/XQAPModule.luac" - val sanitizerCall = s"$sinkModule::root.13@pc85" - val sanitizerValue = s"$sinkModule::root.13@pc138:r16" - val expectedPaths = Vector( - ("usr/lib/lua/luci/controller/api/xqnetwork.luac", "setPeerWifiAutoAPMode", 42), - ("usr/lib/lua/luci/controller/api/misystem.luac", "set_extendwifi_connect", 29), - ("usr/lib/lua/luci/controller/api/xqsystem.luac", "ExtendWifiConnectInitedRouter", 32) - ) - - expectedPaths.foreach { case (sourceModule, sourceFunction, sourcePc) => - val path = pathRows - .find(row => - row("source_module_path").str == sourceModule && - row("source_function_name").str == sourceFunction && - row("source_pc").num.toInt == sourcePc && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == sinkModule && - row("sink_function_name").str == "extendwifi_set_connect" && - row("sink_pc").num.toInt == 139 && - row("sink_trigger").str == "luci.util.exec" && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - !row.obj.contains("callsite_id") - ) - .getOrElse(fail(s"missing XQAPModule extendwifi strict path $sourceFunction sourcePc=$sourcePc")) - - withClue( - s"$sourceFunction sourcePc=$sourcePc path_steps=${path("path_steps").arr.map(_.str).mkString("[", ",", "]")} sanitizer_hits=${path("sanitizer_hits")}" - ) { - path("path_steps").arr.exists(_.str == sanitizerValue) shouldBe true - path("classification").str shouldBe "sanitized" - path("sanitizer_hits").arr.exists { hit => - val row = hit.obj - row("callsite_id").str == sanitizerCall && - row("sanitizer_name").str == "xiaoqiang.util.XQWifiUtil.apcli_get_ifname_form_band" && - row("applies_to_sink").bool && - row("on_dataflow_chain").bool - } shouldBe true - } - } - } - } - - "preserve CrossPlatform r7 baseline sanitizer classifications for setWifiMacfilter and setWanSpeed" in { - withXiaomiStagingRows { stagingRows => - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val wifiPath = pathRows - .find(row => - row("source_module_path").str == "usr/lib/lua/luci/controller/api/xqnetwork.luac" && - row("source_function_name").str == "setWifiMacfilter" && - row("source_pc").num.toInt == 34 && - row("sink_module_path").str == "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac" && - row("sink_function_name").str == "func_unknow_0_0" && - row("sink_pc").num.toInt == 25 && - row("sink_trigger").str == "os.execute" - ) - .getOrElse(fail("missing setWifiMacfilter to XQSynchrodata.func_unknow_0_0 strict path")) - - wifiPath("classification").str shouldBe "sanitized" - wifiPath("sanitizer_hits").arr.exists { hit => - val row = hit.obj - row("callsite_id").str == "usr/lib/lua/luci/controller/api/xqnetwork.luac::root.47@pc35" && - row("sanitizer_name").str == "tonumber" && - row("applies_to_sink").bool && - row("on_dataflow_chain").bool - } shouldBe true - - val wanSpeedPath = pathRows - .find(row => - row("source_module_path").str == "usr/lib/lua/luci/controller/api/xqnetwork.luac" && - row("source_function_name").str == "setWanSpeed" && - row("source_pc").num.toInt == 7 && - row("sink_module_path").str == "usr/lib/lua/xiaoqiang/util/XQLanWanUtil.luac" && - row("sink_function_name").str == "setWanSpeed" && - row("sink_pc").num.toInt == 31 && - row("sink_trigger").str == "os.execute" - ) - .getOrElse(fail("missing setWanSpeed to XQLanWanUtil.setWanSpeed strict path")) - - wanSpeedPath("classification").str shouldBe "sanitized" - wanSpeedPath("sanitizer_hits").arr.exists { hit => - val row = hit.obj - row("callsite_id").str == "usr/lib/lua/luci/controller/api/xqnetwork.luac::root.82@pc8" && - row("sanitizer_name").str == "tonumber" && - row("applies_to_sink").bool && - row("on_dataflow_chain").bool - } shouldBe true - wanSpeedPath("sanitizer_hits").arr.exists { hit => - val row = hit.obj - row("callsite_id").str == "usr/lib/lua/xiaoqiang/util/XQLanWanUtil.luac::root.68@pc5" && - row("sanitizer_name").str == "tonumber" && - row("applies_to_sink").bool && - row("on_dataflow_chain").bool - } shouldBe true - } - } - - "export CrossPlatform r7 setAllWifi path through conditional call result flow" in { - withXiaomiStagingRows { stagingRows => - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val path = pathRows - .find(row => - row("source_module_path").str == "usr/lib/lua/luci/controller/api/xqnetwork.luac" && - row("source_function_name").str == "setAllWifi" && - row("source_pc").num.toInt == 40 && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == "usr/lib/lua/xiaoqiang/common/XQFunction.luac" && - row("sink_function_name").str == "nvramSet" && - row("sink_pc").num.toInt == 35 && - row("sink_trigger").str == "os.execute" && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - !row.obj.contains("callsite_id") - ) - .getOrElse(fail("missing setAllWifi to XQFunction.nvramSet strict path")) - - val steps = path("path_steps").arr.map(_.str) - steps should contain("usr/lib/lua/luci/controller/api/xqnetwork.luac::root.15@pc40:r13") - steps should contain("usr/lib/lua/luci/controller/api/xqnetwork.luac::root.15@pc287:r13") - steps should contain("usr/lib/lua/luci/controller/api/xqnetwork.luac::root.15@pc287:r48") - steps should contain("usr/lib/lua/luci/controller/api/xqnetwork.luac::root.15@pc300:r48") - steps should contain("usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac::root.41:r2") - steps should contain("usr/lib/lua/xiaoqiang/common/XQFunction.luac::root.33@pc35:r4") - } - } - - "export CrossPlatform r8 setAllWifi formvalue paths to XQFunction nvramSet" in { - withXiaomiStagingRows { stagingRows => - val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) - val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val sourceModule = "usr/lib/lua/luci/controller/api/xqnetwork.luac" - val sinkModule = "usr/lib/lua/xiaoqiang/common/XQFunction.luac" - val expectedSources = Vector( - 74 -> "root.15@pc74:r20", - 78 -> "root.15@pc78:r21", - 85 -> "root.15@pc85:r22", - 112 -> "root.15@pc112:r28", - 116 -> "root.15@pc116:r29", - 120 -> "root.15@pc120:r30" - ) - - expectedSources.foreach { case (pc, localRef) => - sourceRows.exists(row => - row("module_path").str == sourceModule && - row("value_ref").str == localRef && - hasScopedCallsite(row, s"root.15@pc$pc") && - row("trigger").str == "luci.http.formvalue" - ) shouldBe true - } - - sinkRows.exists(row => - row("module_path").str == sinkModule && - row("value_ref").str == "root.33@pc35:r4" && - hasScopedCallsite(row, "root.33@pc35") && - row("trigger").str == "os.execute" - ) shouldBe true - - val missingPaths = expectedSources.collect { case (pc, localRef) - if !pathRows.exists(row => - row("source_module_path").str == sourceModule && - row("source_function_name").str == "setAllWifi" && - row("source_pc").num.toInt == pc && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == sinkModule && - row("sink_function_name").str == "nvramSet" && - row("sink_pc").num.toInt == 35 && - row("sink_trigger").str == "os.execute" && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - row("path_steps").arr.exists(_.str == s"$sourceModule::$localRef") && - row("path_steps").arr.exists(_.str == s"$sinkModule::root.33@pc35:r4") && - !row.obj.contains("callsite_id") - ) => - s"setAllWifi@pc$pc" - } - - withClue(s"missing r8 paths: ${missingPaths.mkString(", ")}") { - missingPaths shouldBe empty - } - } - } - - "export CrossPlatform r8 setRouterInfo formvalue paths to XQFunction nvramSet" in { - withXiaomiStagingRows { stagingRows => - val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) - val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val sourceModule = "usr/lib/lua/luci/controller/api/misystem.luac" - val sinkModule = "usr/lib/lua/xiaoqiang/common/XQFunction.luac" - val expectedSources = Vector( - 74 -> "root.27@pc74:r17", - 86 -> "root.27@pc86:r20", - 90 -> "root.27@pc90:r21" - ) - - expectedSources.foreach { case (pc, localRef) => - sourceRows.exists(row => - row("module_path").str == sourceModule && - row("value_ref").str == localRef && - hasScopedCallsite(row, s"root.27@pc$pc") && - row("trigger").str == "luci.http.formvalue" - ) shouldBe true - } - - sinkRows.exists(row => - row("module_path").str == sinkModule && - row("value_ref").str == "root.33@pc35:r4" && - hasScopedCallsite(row, "root.33@pc35") && - row("trigger").str == "os.execute" - ) shouldBe true - - val missingPaths = expectedSources.collect { case (pc, localRef) - if !pathRows.exists(row => - row("source_module_path").str == sourceModule && - row("source_function_name").str == "setRouterInfo" && - row("source_pc").num.toInt == pc && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == sinkModule && - row("sink_function_name").str == "nvramSet" && - row("sink_pc").num.toInt == 35 && - row("sink_trigger").str == "os.execute" && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - row("path_steps").arr.exists(_.str == s"$sourceModule::$localRef") && - row("path_steps").arr.exists(_.str == s"$sinkModule::root.33@pc35:r4") && - !row.obj.contains("callsite_id") - ) => - s"setRouterInfo@pc$pc" - } - - withClue(s"missing r8 paths: ${missingPaths.mkString(", ")}") { - missingPaths shouldBe empty - } - } - } - - "export CrossPlatform r8 misystem paths to XQSynchrodata" in { - withXiaomiStagingRows { stagingRows => - val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) - val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val sourceModule = "usr/lib/lua/luci/controller/api/misystem.luac" - val sinkModule = "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac" - val expectedSources = Vector( - ("setRouterInfo", 78, "root.27@pc78", "root.27@pc78:r18"), - ("setLanApMode_Init", 92, "root.40@pc92", "root.40@pc92:r19") - ) - - expectedSources.foreach { case (_, _, callsiteId, localRef) => - sourceRows.exists(row => - row("module_path").str == sourceModule && - row("value_ref").str == localRef && - hasScopedCallsite(row, callsiteId) && - row("trigger").str == "luci.http.formvalue" - ) shouldBe true - } - - sinkRows.exists(row => - row("module_path").str == sinkModule && - row("value_ref").str == "root.0@pc25:r4" && - hasScopedCallsite(row, "root.0@pc25") && - row("trigger").str == "os.execute" - ) shouldBe true - - val missingPaths = expectedSources.collect { case (functionName, pc, _, localRef) - if !pathRows.exists(row => - row("source_module_path").str == sourceModule && - row("source_function_name").str == functionName && - row("source_pc").num.toInt == pc && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == sinkModule && - row("sink_function_name").str == "func_unknow_0_0" && - row("sink_pc").num.toInt == 25 && - row("sink_trigger").str == "os.execute" && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - row("path_steps").arr.exists(_.str == s"$sourceModule::$localRef") && - row("path_steps").arr.exists(_.str == s"$sinkModule::root.0@pc25:r4") && - !row.obj.contains("callsite_id") - ) => - s"$functionName@pc$pc" - } - - withClue(s"missing r8 XQSynchrodata paths: ${missingPaths.mkString(", ")}") { - missingPaths shouldBe empty - } - } - } - - "export CrossPlatform r8 setRouterToBaidu formvalue paths to local exec sink" in { - withXiaomiStagingRows { stagingRows => - val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) - val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val module = "usr/lib/lua/luci/controller/api/xqnetwork.luac" - val expectedSources = Vector( - 27 -> "root.131@pc27:r8", - 35 -> "root.131@pc35:r10" - ) - - expectedSources.foreach { case (pc, localRef) => - sourceRows.exists(row => - row("module_path").str == module && - row("value_ref").str == localRef && - hasScopedCallsite(row, s"root.131@pc$pc") && - row("trigger").str == "luci.http.formvalue" - ) shouldBe true - } - - sinkRows.exists(row => - row("module_path").str == module && - row("value_ref").str == "root.131@pc161:r19" && - hasScopedCallsite(row, "root.131@pc161") && - row("trigger").str == "luci.util.exec" - ) shouldBe true - - val missingPaths = expectedSources.collect { case (pc, localRef) - if !pathRows.exists(row => - row("source_module_path").str == module && - row("source_function_name").str == "setRouterToBaidu" && - row("source_pc").num.toInt == pc && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == module && - row("sink_function_name").str == "setRouterToBaidu" && - row("sink_pc").num.toInt == 161 && - row("sink_trigger").str == "luci.util.exec" && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - row("path_steps").arr.exists(_.str == s"$module::$localRef") && - row("path_steps").arr.exists(_.str == s"$module::root.131@pc161:r19") && - !row.obj.contains("callsite_id") - ) => - s"setRouterToBaidu@pc$pc" - } - - withClue(s"missing r8 setRouterToBaidu local exec paths: ${missingPaths.mkString(", ")}") { - missingPaths shouldBe empty - } - } - } - - "export CrossPlatform r8 setBaiduToRouter formvalue path to local exec sink" in { - withXiaomiStagingRows { stagingRows => - val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) - val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val module = "usr/lib/lua/luci/controller/api/xqnetwork.luac" - val sourceRef = "root.132@pc27:r8" - val sinkRef = "root.132@pc116:r16" - - sourceRows.exists(row => - row("module_path").str == module && - row("value_ref").str == sourceRef && - hasScopedCallsite(row, "root.132@pc27") && - row("trigger").str == "luci.http.formvalue" - ) shouldBe true - - sinkRows.exists(row => - row("module_path").str == module && - row("value_ref").str == sinkRef && - hasScopedCallsite(row, "root.132@pc116") && - row("trigger").str == "luci.util.exec" - ) shouldBe true - - val pathExists = pathRows.exists(row => - row("source_module_path").str == module && - row("source_function_name").str == "setBaiduToRouter" && - row("source_pc").num.toInt == 27 && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == module && - row("sink_function_name").str == "setBaiduToRouter" && - row("sink_pc").num.toInt == 116 && - row("sink_trigger").str == "luci.util.exec" && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - row("path_steps").arr.exists(_.str == s"$module::$sourceRef") && - row("path_steps").arr.exists(_.str == s"$module::$sinkRef") && - !row.obj.contains("callsite_id") - ) - - withClue("missing r8 setBaiduToRouter local exec path") { - pathExists shouldBe true - } - } - } - - "export CrossPlatform r8 getTransListFileStat sanitized path to local exec sink" in { - withXiaomiStagingRows { stagingRows => - val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) - val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val module = "usr/lib/lua/luci/controller/api/xqnetwork.luac" - val sourceRef = "root.137@pc26:r7" - val sinkRef = "root.137@pc68:r14" - - sourceRows.exists(row => - row("module_path").str == module && - row("value_ref").str == sourceRef && - hasScopedCallsite(row, "root.137@pc26") && - row("trigger").str == "luci.http.formvalue" - ) shouldBe true - - sinkRows.exists(row => - row("module_path").str == module && - row("value_ref").str == sinkRef && - hasScopedCallsite(row, "root.137@pc68") && - row("trigger").str == "luci.util.exec" - ) shouldBe true - - val path = pathRows - .find(row => - row("source_module_path").str == module && - row("source_function_name").str == "getTransListFileStat" && - row("source_pc").num.toInt == 26 && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == module && - row("sink_function_name").str == "getTransListFileStat" && - row("sink_pc").num.toInt == 68 && - row("sink_trigger").str == "luci.util.exec" && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - row("path_steps").arr.exists(_.str == s"$module::$sourceRef") && - row("path_steps").arr.exists(_.str == s"$module::$sinkRef") && - !row.obj.contains("callsite_id") - ) - .getOrElse(fail("missing r8 getTransListFileStat local exec path")) - - withClue( - s"path_steps=${path("path_steps").arr.map(_.str).mkString("[", ",", "]")} sanitizer_hits=${path("sanitizer_hits")}" - ) { - path("classification").str shouldBe "sanitized" - path("sanitizer_hits").arr.exists { hit => - val row = hit.obj - row("callsite_id").str == s"$module::root.137@pc60" && - row("sanitizer_name").str == "json.encode" && - row("applies_to_sink").bool && - row("on_dataflow_chain").bool - } shouldBe true - } - } - } - - "export CrossPlatform r8 tunnelSmartHomeRequest formvalue path to local exec sink" in { - withXiaomiStagingRows { stagingRows => - val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) - val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val module = "usr/lib/lua/luci/controller/api/xqsmarthome.luac" - val sourceRef = "root.1@pc7:r2" - val sinkRef = "root.1@pc19:r6" - - sourceRows.exists(row => - row("module_path").str == module && - row("value_ref").str == sourceRef && - hasScopedCallsite(row, "root.1@pc7") && - row("trigger").str == "luci.http.formvalue" - ) shouldBe true - - sinkRows.exists(row => - row("module_path").str == module && - row("value_ref").str == sinkRef && - hasScopedCallsite(row, "root.1@pc19") && - row("trigger").str == "luci.util.exec" - ) shouldBe true - - val pathExists = pathRows.exists(row => - row("source_module_path").str == module && - row("source_function_name").str == "tunnelSmartHomeRequest" && - row("source_pc").num.toInt == 7 && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == module && - row("sink_function_name").str == "tunnelSmartHomeRequest" && - row("sink_pc").num.toInt == 19 && - row("sink_trigger").str == "luci.util.exec" && - row("classification").str == "sanitized" && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - row("path_steps").arr.exists(_.str == s"$module::$sourceRef") && - row("path_steps").arr.exists(_.str == s"$module::$sinkRef") && - !row.obj.contains("callsite_id") - ) - - withClue("missing r8 tunnelSmartHomeRequest local exec path") { - pathExists shouldBe true - } - } - } - - "export CrossPlatform r8 tunnelSmartControllerRequest formvalue path to local exec sink" in { - withXiaomiStagingRows { stagingRows => - val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) - val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val module = "usr/lib/lua/luci/controller/api/xqsmarthome.luac" - val sourceRef = "root.2@pc19:r5" - val sinkRef = "root.2@pc79:r10" - - sourceRows.exists(row => - row("module_path").str == module && - row("value_ref").str == sourceRef && - hasScopedCallsite(row, "root.2@pc19") && - row("trigger").str == "luci.http.formvalue" - ) shouldBe true - - sinkRows.exists(row => - row("module_path").str == module && - row("value_ref").str == sinkRef && - hasScopedCallsite(row, "root.2@pc79") && - row("trigger").str == "luci.util.exec" - ) shouldBe true - - val pathExists = pathRows.exists(row => - row("source_module_path").str == module && - row("source_function_name").str == "tunnelSmartControllerRequest" && - row("source_pc").num.toInt == 19 && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == module && - row("sink_function_name").str == "tunnelSmartControllerRequest" && - row("sink_pc").num.toInt == 79 && - row("sink_trigger").str == "luci.util.exec" && - row("classification").str == "sanitized" && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - row("path_steps").arr.exists(_.str == s"$module::$sourceRef") && - row("path_steps").arr.exists(_.str == s"$module::$sinkRef") && - !row.obj.contains("callsite_id") - ) - - withClue("missing r8 tunnelSmartControllerRequest local exec path") { - pathExists shouldBe true - } - } - } - - "export CrossPlatform r8 setMeshInfo formvalue path to forkExec sink" in { - withXiaomiStagingRows { stagingRows => - val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) - val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val module = "usr/lib/lua/luci/controller/api/misystem.luac" - val sourceRef = "root.28@pc47:r12" - val sinkRef = "root.28@pc345:r23" - - sourceRows.exists(row => - row("module_path").str == module && - row("value_ref").str == sourceRef && - hasScopedCallsite(row, "root.28@pc47") && - row("trigger").str == "luci.http.formvalue" - ) shouldBe true - - sinkRows.exists(row => - row("module_path").str == module && - row("value_ref").str == sinkRef && - hasScopedCallsite(row, "root.28@pc345") && - row("trigger").str == "test.api.Process.forkExec" - ) shouldBe true - - val pathExists = pathRows.exists(row => - row("source_module_path").str == module && - row("source_function_name").str == "setMeshInfo" && - row("source_pc").num.toInt == 47 && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == module && - row("sink_function_name").str == "setMeshInfo" && - row("sink_pc").num.toInt == 345 && - row("sink_trigger").str == "test.api.Process.forkExec" && - row("classification").str == "true-positive" && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - row("path_steps").arr.exists(_.str == s"$module::$sourceRef") && - row("path_steps").arr.exists(_.str == s"$module::$sinkRef") && - !row.obj.contains("callsite_id") - ) - - withClue("missing r8 setMeshInfo forkExec path") { - pathExists shouldBe true - } - } - } - - "export CrossPlatform r8 datacenter setSyncRouterFile path to tunnelRequestDatacenter exec sink" in { - withXiaomiStagingRows { stagingRows => - val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) - val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val module = "usr/lib/lua/luci/controller/service/datacenter.luac" - val sourceRef = "root.1@pc6:r1" - val sinkRef = "root.14@pc24:r7" - - sourceRows.exists(row => - row("module_path").str == module && - row("value_ref").str == sourceRef && - hasScopedCallsite(row, "root.1@pc6") && - row("trigger").str == "luci.http.formvalue" - ) shouldBe true - - sinkRows.exists(row => - row("module_path").str == module && - row("value_ref").str == sinkRef && - hasScopedCallsite(row, "root.14@pc24") && - row("trigger").str == "luci.util.exec" - ) shouldBe true - - val pathExists = pathRows.exists(row => - row("source_module_path").str == module && - row("source_function_name").str == "setSyncRouterFile" && - row("source_pc").num.toInt == 6 && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == module && - row("sink_function_name").str == "tunnelRequestDatacenter" && - row("sink_pc").num.toInt == 24 && - row("sink_trigger").str == "luci.util.exec" && - row("classification").str == "sanitized" && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - row("path_steps").arr.exists(_.str == s"$module::$sourceRef") && - row("path_steps").arr.exists(_.str == s"$module::$sinkRef") && - !row.obj.contains("callsite_id") - ) - - withClue("missing r8 datacenter setSyncRouterFile local exec path") { - pathExists shouldBe true - } - } - } - - "export CrossPlatform r8 setSysTime path to XQSysUtil setSysTime forkExec sink" in { - withXiaomiStagingRows { stagingRows => - val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) - val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val sourceModule = "usr/lib/lua/luci/controller/api/misystem.luac" - val sinkModule = "usr/lib/lua/xiaoqiang/util/XQSysUtil.luac" - val sourceRef = "root.144@pc5:r1" - val sinkRef = "root.108@pc112:r4" - - sourceRows.exists(row => - row("module_path").str == sourceModule && - row("value_ref").str == sourceRef && - hasScopedCallsite(row, "root.144@pc5") && - row("trigger").str == "luci.http.formvalue" - ) shouldBe true - - sinkRows.exists(row => - row("module_path").str == sinkModule && - row("value_ref").str == sinkRef && - hasScopedCallsite(row, "root.108@pc112") && - row("trigger").str == "test.api.Process.forkExec" - ) shouldBe true - - val path = pathRows.find(row => - row("source_module_path").str == sourceModule && - row("source_function_name").str == "setSysTime" && - row("source_pc").num.toInt == 5 && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == sinkModule && - row("sink_function_name").str == "setSysTime" && - row("sink_pc").num.toInt == 112 && - row("sink_trigger").str == "test.api.Process.forkExec" && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - row("path_steps").arr.exists(_.str == s"$sourceModule::$sourceRef") && - row("path_steps").arr.exists(_.str == s"$sinkModule::$sinkRef") && - !row.obj.contains("callsite_id") - ) - - withClue("missing r8 setSysTime XQSysUtil forkExec path") { - path.isDefined shouldBe true - } - val steps = path.get("path_steps").arr.map(_.str) - steps should contain(s"$sinkModule::root.108@pc98:r3") - steps should contain(s"$sinkModule::root.108@pc103:r3") - steps should contain(s"$sinkModule::root.108@pc109:r0") - steps should contain(s"$sinkModule::root.108@pc111:r4") - steps should contain(s"$sinkModule::$sinkRef") - path.get("classification").str shouldBe "sanitized" - path.get("sanitizer_hits").arr.exists { hit => - val row = hit.obj - row("callsite_id").str == s"$sinkModule::root.108@pc103" && - row("sanitizer_name").str == "Param_0.match" && - row("applies_to_sink").bool && - row("on_dataflow_chain").bool - } shouldBe true - } - } - - "export CrossPlatform r8 setChannel path with scoped iwprivSetChannel _cmdformat sanitizer hit" in { - withXiaomiStagingRows { stagingRows => - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val sourceModule = "usr/lib/lua/luci/controller/api/misystem.luac" - val sinkModule = "usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac" - val sanitizerCall = s"$sinkModule::root.93@pc43" - val sanitizerValue = s"$sinkModule::root.93@pc45:r2" - - val path = pathRows - .find(row => - row("source_module_path").str == sourceModule && - row("source_function_name").str == "setChannel" && - row("source_pc").num.toInt == 6 && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == sinkModule && - row("sink_function_name").str == "iwprivSetChannel" && - row("sink_pc").num.toInt == 80 && - row("sink_trigger").str == "test.api.Process.forkExec" && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - row("path_steps").arr.exists(_.str == sanitizerValue) && - !row.obj.contains("callsite_id") - ) - .getOrElse(fail("missing r8 setChannel XQWifiUtil iwprivSetChannel strict path")) - - withClue( - s"path_steps=${path("path_steps").arr.map(_.str).mkString("[", ",", "]")} sanitizer_hits=${path("sanitizer_hits")}" - ) { - path("classification").str shouldBe "sanitized" - path("sanitizer_hits").arr.exists { hit => - val row = hit.obj - row("callsite_id").str == sanitizerCall && - row("sanitizer_name").str == "test.api.Process._cmdformat" && - row("applies_to_sink").bool && - row("on_dataflow_chain").bool - } shouldBe true - } - } - } - - "export CrossPlatform r8 webAccess path to XQSysUtil webAccessControl exec sink" in { - withXiaomiStagingRows { stagingRows => - val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) - val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val sourceModule = "usr/lib/lua/luci/controller/api/misystem.luac" - val sinkModule = "usr/lib/lua/xiaoqiang/util/XQSysUtil.luac" - val sourceRef = "root.136@pc17:r2" - val bridgeRef = "root.136@pc56:r7" - val paramRef = "root.105:r1" - val sinkRef = "root.105@pc32:r8" - - sourceRows.exists(row => - row("module_path").str == sourceModule && - row("value_ref").str == sourceRef && - hasScopedCallsite(row, "root.136@pc17") && - row("trigger").str == "luci.http.formvalue" + val callRows = staging("call_name_resolution").arr.map(_.obj) + callRows.exists(row => + row("module_path").str.endsWith("d24-sanitizer-suppresses-report/input.luac") && + hasScopedCallsite(row, "root@pc20") && + row("resolved_name").str == "tonumber" ) shouldBe true - sinkRows.exists(row => - row("module_path").str == sinkModule && - row("value_ref").str == sinkRef && - hasScopedCallsite(row, "root.105@pc32") && - row("trigger").str == "os.execute" + val pathRows = staging("path_evidence").arr.map(_.obj) + pathRows.exists(row => + row("path_steps").arr.exists(_.str.endsWith("d24-sanitizer-suppresses-report/input.luac::root@pc20:r2")) ) shouldBe true - - val path = pathRows.find(row => - row("source_module_path").str == sourceModule && - row("source_function_name").str == "webAccess" && - row("source_pc").num.toInt == 17 && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == sinkModule && - row("sink_function_name").str == "webAccessControl" && - row("sink_pc").num.toInt == 32 && - row("sink_trigger").str == "os.execute" && - row("classification").str == "true-positive" && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - row("path_steps").arr.exists(_.str == s"$sourceModule::$sourceRef") && - row("path_steps").arr.exists(_.str == s"$sourceModule::$bridgeRef") && - row("path_steps").arr.exists(_.str == s"$sinkModule::$paramRef") && - row("path_steps").arr.exists(_.str == s"$sinkModule::$sinkRef") && - !row.obj.contains("callsite_id") - ) - - withClue("missing r8 webAccess XQSysUtil webAccessControl os.execute path") { - path.isDefined shouldBe true - } - val steps = path.get("path_steps").arr.map(_.str) - steps should contain(s"$sinkModule::root.105@pc11:r6") - steps should contain(s"$sinkModule::root.105@pc28:r6") - steps should contain(s"$sinkModule::root.105@pc31:r8") - steps should contain(s"$sinkModule::$sinkRef") } } - "export CrossPlatform r8 pingTest path to luci sys exec sink" in { - withXiaomiStagingRows { stagingRows => - val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) - val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) - val exportRows = stagingRows.flatMap(_("module_return_table").arr.map(_.obj)) - val linkRows = stagingRows.flatMap(_("module_linkage").arr.map(_.obj)) - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val sourceModule = "usr/lib/lua/luci/controller/api/xqnetdetect.luac" - val sinkModule = "usr/lib/lua/luci/sys.luac" - val sourceRef = "root.3@pc6:r1" - val bridgeRef = "root.3@pc10:r3" - val sinkRef = "root.25@pc9:r2" + "export OpenWrt-derived source and sink endpoints" in { + withOpenWrtDerivedExportDir { exportDir => + val rows = stagingRows(exportDir) + val sourceRows = rows.flatMap(_("source_endpoints").arr.map(_.obj)) + val sinkRows = rows.flatMap(_("sink_endpoints").arr.map(_.obj)) + val pathRows = rows.flatMap(_("path_evidence").arr.map(_.obj)) sourceRows.exists(row => - row("module_path").str == sourceModule && - row("value_ref").str == sourceRef && - hasScopedCallsite(row, "root.3@pc6") && + row("module_path").str.endsWith("luci/controller/mtkwifi.luac") && + hasScopedCallsite(row, "root.110@pc3") && row("trigger").str == "luci.http.formvalue" ) shouldBe true sinkRows.exists(row => - row("module_path").str == sinkModule && - row("value_ref").str == sinkRef && - hasScopedCallsite(row, "root.25@pc9") && + row("module_path").str.endsWith("luci/controller/mtkwifi.luac") && + hasScopedCallsite(row, "root.110@pc12") && row("trigger").str == "os.execute" ) shouldBe true - exportRows.exists(row => - row("module_path").str == sinkModule && - row("table_ref").str == s"$sinkModule:module-global" && - row("field_name").str == "net.pingtest" && - row("target_prototype_id").str == "root.25" - ) shouldBe true - - linkRows.exists(row => - row("module_path").str == sourceModule && - hasScopedCallsite(row, "root.3@pc10") && - row("target_module_path").str == sinkModule && - row("target_prototype_id").str == "root.25" && - row("field_name").str == "net.pingtest" - ) shouldBe true - - val path = pathRows.find(row => - row("source_module_path").str == sourceModule && - row("source_function_name").str == "pingTest" && - row("source_pc").num.toInt == 6 && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == sinkModule && - row("sink_function_name").str == "net.pingtest" && - row("sink_pc").num.toInt == 9 && - row("sink_trigger").str == "os.execute" && - row("classification").str == "true-positive" && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - row("path_steps").arr.exists(_.str == s"$sourceModule::$sourceRef") && - row("path_steps").arr.exists(_.str == s"$sourceModule::$bridgeRef") && - row("path_steps").arr.exists(_.str == s"$sinkModule::$sinkRef") && - !row.obj.contains("callsite_id") - ) - - withClue("missing r8 pingTest luci.sys os.execute path") { - path.isDefined shouldBe true - } - val steps = path.get("path_steps").arr.map(_.str) - steps should contain(s"$sourceModule::root.3@pc9:r1") - steps should contain(s"$sourceModule::$bridgeRef") - steps should contain(s"$sinkModule::root.25:r0") - steps should contain(s"$sinkModule::root.25@pc3:r4") - steps should contain(s"$sinkModule::root.25@pc6:r4") - steps should contain(s"$sinkModule::root.25@pc6:r3") - steps should contain(s"$sinkModule::root.25@pc8:r2") - steps should contain(s"$sinkModule::$sinkRef") - } - } - - "export CrossPlatform r8 miats remote_call paths to datacenter requestDatacenter" in { - withXiaomiStagingRows { stagingRows => - val sourceRows = stagingRows.flatMap(_("source_endpoints").arr.map(_.obj)) - val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val sourceModule = "usr/lib/lua/luci/controller/api/miats.luac" - val sinkModule = "usr/lib/lua/luci/controller/service/datacenter.luac" - val expectedSources = Vector( - 9 -> "root.9@pc9:r2", - 17 -> "root.9@pc17:r4" - ) - - expectedSources.foreach { case (pc, localRef) => - sourceRows.exists(row => - row("module_path").str == sourceModule && - row("value_ref").str == localRef && - hasScopedCallsite(row, s"root.9@pc$pc") && - row("trigger").str == "luci.http.formvalue" - ) shouldBe true - } - sinkRows.exists(row => - row("module_path").str == sinkModule && - row("value_ref").str == "root.15@pc22:r6" && - hasScopedCallsite(row, "root.15@pc22") && - row("trigger").str == "luci.util.exec" - ) shouldBe true - - val missingPaths = expectedSources.collect { case (pc, localRef) - if !pathRows.exists(row => - row("source_module_path").str == sourceModule && - row("source_function_name").str == "remote_call" && - row("source_pc").num.toInt == pc && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == sinkModule && - row("sink_function_name").str == "requestDatacenter" && - row("sink_pc").num.toInt == 22 && - row("sink_trigger").str == "luci.util.exec" && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - row("path_steps").arr.exists(_.str == s"$sourceModule::$localRef") && - row("path_steps").arr.exists(_.str == s"$sinkModule::root.15@pc22:r6") && - !row.obj.contains("callsite_id") - ) => - s"miats.remote_call@pc$pc" - } - - withClue(s"missing r8 miats requestDatacenter paths: ${missingPaths.mkString(", ")}") { - missingPaths shouldBe empty - } - } - } - - "export CrossPlatform r7 deleteTransportList path through XQBaiduPanUtil" in { - withXiaomiStagingRows { stagingRows => - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val path = pathRows - .find(row => - row("source_module_path").str == "usr/lib/lua/luci/controller/api/xqnetwork.luac" && - row("source_function_name").str == "deleteTransportList" && - row("source_pc").num.toInt == 28 && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str == "usr/lib/lua/xiaoqiang/module/XQBaiduPanUtil.luac" && - row("sink_function_name").str == "kill_baidupan_process" && - row("sink_pc").num.toInt == 22 && - row("sink_trigger").str == "luci.util.exec" && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - !row.obj.contains("callsite_id") - ) - .getOrElse(fail("missing deleteTransportList to XQBaiduPanUtil.kill_baidupan_process strict path")) - - val steps = path("path_steps").arr.map(_.str) - steps should contain("usr/lib/lua/luci/controller/api/xqnetwork.luac::root.133@pc28:r8") - steps should contain("usr/lib/lua/luci/controller/api/xqnetwork.luac::root.133@pc87:r15") - steps should contain("usr/lib/lua/xiaoqiang/module/XQBaiduPanUtil.luac::root.39:r2") - steps should contain("usr/lib/lua/xiaoqiang/module/XQBaiduPanUtil.luac::root.39@pc65:r9") - steps should contain("usr/lib/lua/xiaoqiang/module/XQBaiduPanUtil.luac::root.39@pc75:r12") - steps should contain("usr/lib/lua/xiaoqiang/module/XQBaiduPanUtil.luac::root.39@pc76:r14") - steps should contain("usr/lib/lua/xiaoqiang/module/XQBaiduPanUtil.luac::root.37:r0") - steps should contain("usr/lib/lua/xiaoqiang/module/XQBaiduPanUtil.luac::root.37@pc22:r3") - } - } - - "prune CrossPlatform captured-require bridge flows before local path search" in { - withXiaomiExportDir { exportDir => - val profile = ujson.read(Files.readString(exportDir.resolve("path-search-profile.json"))).obj - val pairProfiles = profile("performance_attribution")("pair_profiles").arr.map(_.obj) - val targetPair = pairProfiles - .find(row => - row("source_ref").str == - "usr/lib/lua/luci/controller/api/xqnetwork.luac:root.93@pc28:r8" && - row("source_callsite_id").str == - "usr/lib/lua/luci/controller/api/xqnetwork.luac::root.93@pc28" && - row("sink_ref").str == - "usr/lib/lua/xiaoqiang/common/XQFunction.luac:root.33@pc35:r4" && - row("sink_callsite_id").str == - "usr/lib/lua/xiaoqiang/common/XQFunction.luac::root.33@pc35" - ) - .getOrElse(fail("missing attributed captured-require pair")) - - targetPair("taint_path_count").num.toLong shouldBe 1L - targetPair("report_count").num.toLong shouldBe 1L - targetPair("bridge_local_path_success_count").num.toLong shouldBe 6L - targetPair("bridge_local_path_attempt_count").num.toLong should be <= 12L - } - } - - "export CrossPlatform r7 residual source-to-sink paths and miats sink endpoint" in { - withXiaomiStagingRows { stagingRows => - val sinkRows = stagingRows.flatMap(_("sink_endpoints").arr.map(_.obj)) - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - def hasSink(moduleSuffix: String, pc: Int, trigger: String): Boolean = - sinkRows.exists(row => - row("module_path").str.endsWith(moduleSuffix) && - row("callsite_id").str.endsWith(s"@pc$pc") && - row("callsite_id").str.contains("::") && - row("trigger").str == trigger - ) - - def hasPath( - sourceModuleSuffix: String, - sourceFunctionName: String, - sourcePc: Int, - sinkModuleSuffix: String, - sinkFunctionName: String, - sinkPc: Int, - sinkTrigger: String - ): Boolean = - pathRows.exists(row => - row("source_module_path").str.endsWith(sourceModuleSuffix) && - row("source_function_name").str == sourceFunctionName && - row("source_pc").num.toInt == sourcePc && - row("source_trigger").str == "luci.http.formvalue" && - row("sink_module_path").str.endsWith(sinkModuleSuffix) && - row("sink_function_name").str == sinkFunctionName && - row("sink_pc").num.toInt == sinkPc && - row("sink_trigger").str == sinkTrigger && - row("path_steps").arr.nonEmpty && - row("path_steps").arr.forall(_.str.contains("::")) && - !row.obj.contains("callsite_id") - ) - - val missingFamilies = Vector( - "miats.sink@pc148:luci.util.exec" -> hasSink( - "usr/lib/lua/luci/controller/api/miats.luac", - 148, - "luci.util.exec" - ), - "xqnetwork.setWifiApMode@pc28->XQFunction.nvramSet@pc35" -> hasPath( - "usr/lib/lua/luci/controller/api/xqnetwork.luac", - "setWifiApMode", - 28, - "usr/lib/lua/xiaoqiang/common/XQFunction.luac", - "nvramSet", - 35, - "os.execute" - ), - "xqnetwork.setAllWifi@pc40->XQFunction.nvramSet@pc35" -> hasPath( - "usr/lib/lua/luci/controller/api/xqnetwork.luac", - "setAllWifi", - 40, - "usr/lib/lua/xiaoqiang/common/XQFunction.luac", - "nvramSet", - 35, - "os.execute" - ), - "xqnetwork.setWifiApMode@pc28->XQSynchrodata.func_unknow_0_0@pc25" -> hasPath( - "usr/lib/lua/luci/controller/api/xqnetwork.luac", - "setWifiApMode", - 28, - "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac", - "func_unknow_0_0", - 25, - "os.execute" - ), - "misystem.setWifiApMode_Init@pc60->XQSynchrodata.func_unknow_0_0@pc25" -> hasPath( - "usr/lib/lua/luci/controller/api/misystem.luac", - "setWifiApMode_Init", - 60, - "usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac", - "func_unknow_0_0", - 25, - "os.execute" - ), - "xqsmarthome.requestMitv@pc3->XQMitvUtil.DoExec@pc10" -> hasPath( - "usr/lib/lua/luci/controller/api/xqsmarthome.luac", - "requestMitv", - 3, - "usr/lib/lua/xiaoqiang/util/XQMitvUtil.luac", - "DoExec", - 10, - "luci.util.exec" - ), - "xqnetwork.setWan6@pc40->xqnetwork.setWan6@pc276" -> hasPath( - "usr/lib/lua/luci/controller/api/xqnetwork.luac", - "setWan6", - 40, - "usr/lib/lua/luci/controller/api/xqnetwork.luac", - "setWan6", - 276, - "test.api.Process.forkExec" - ), - "xqsystem.vpnSwitch@pc12->XQCryptoUtil.md5Str@pc10" -> hasPath( - "usr/lib/lua/luci/controller/api/xqsystem.luac", - "vpnSwitch", - 12, - "usr/lib/lua/xiaoqiang/util/XQCryptoUtil.luac", - "md5Str", - 10, - "luci.util.exec" - ), - "xqnetwork.editDevice@pc20->XQWifiUtil.wl_editWiFiMacfilterList@pc348" -> hasPath( - "usr/lib/lua/luci/controller/api/xqnetwork.luac", - "editDevice", - 20, - "usr/lib/lua/xiaoqiang/util/XQWifiUtil.luac", - "wl_editWiFiMacfilterList", - 348, - "os.execute" - ), - "xqsystem.ExtendWifiConnectInitedRouter@pc36->XQExtendWifi.write_t_v@pc31" -> hasPath( - "usr/lib/lua/luci/controller/api/xqsystem.luac", - "ExtendWifiConnectInitedRouter", - 36, - "usr/lib/lua/xiaoqiang/module/XQExtendWifi.luac", - "write_t_v", - 31, - "os.execute" - ), - "xqnetwork.deleteTransportList@pc28->XQBaiduPanUtil.kill_baidupan_process@pc22" -> hasPath( - "usr/lib/lua/luci/controller/api/xqnetwork.luac", - "deleteTransportList", - 28, - "usr/lib/lua/xiaoqiang/module/XQBaiduPanUtil.luac", - "kill_baidupan_process", - 22, - "luci.util.exec" - ) - ).collect { case (label, false) => label } - - withClue(s"missing families: ${missingFamilies.mkString(", ")}") { - missingFamilies shouldBe empty - } - } - } - - "export CrossPlatform real-firmware sanitizer classifications from call-name rows" in { - withXiaomiExportDir { exportDir => - val profile = ujson.read(Files.readString(exportDir.resolve("path-search-profile.json"))).obj - val stagingDir = exportDir.resolve("staging") - val stagingList = Files.list(stagingDir) - try { - val stagingRows = stagingList.iterator.asScala.toVector.map(path => ujson.read(Files.readString(path)).obj) - val callRows = stagingRows.flatMap(_("call_name_resolution").arr.map(_.obj)) - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - val sanitizerSuffixes = Set("tonumber", "tostring") - callRows.exists(row => - row("module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && - sanitizerSuffixes.contains(row("resolved_name").str) && - row.obj.contains("target_value_ref") && - row("target_value_ref").str.nonEmpty - ) shouldBe true - - profile("sanitizer_classification_count").num.toInt should be > 0 - pathRows.exists(row => row("sanitizer_hits").arr.nonEmpty) shouldBe true - pathRows.exists(row => row("classification").str == "sanitized") shouldBe true - pathRows.exists(row => row("classification").str == "true-positive") shouldBe true - } finally { - stagingList.close() - } - } - } - - "export CrossPlatform sanitizer call rows with original call argument refs" in { - withXiaomiStagingRows { stagingRows => - val callRows = stagingRows.flatMap(_("call_name_resolution").arr.map(_.obj)) - - val sanitizerRow = callRows - .find(row => - row("module_path").str == "usr/lib/lua/luci/controller/api/xqnetwork.luac" && - row("callsite_id").str == "usr/lib/lua/luci/controller/api/xqnetwork.luac::root.93@pc101" && - row("resolved_name").str == "xiaoqiang.module.XQAPModule.setWifiAPMode" && - row("resolution_kind").str == "sanitizer-call" - ) - .getOrElse(fail("missing setWifiAPMode sanitizer call row")) - - val argumentRefs = sanitizerRow("argument_value_refs").arr.map(_.str).toVector - argumentRefs should contain("root.93@pc101:r29") - argumentRefs should contain("root.93@pc101:r32") - argumentRefs should not contain "root.93@pc101:r21" - } - } - - "export CrossPlatform representative cross-module path with recovered source callsite bridge" in { - withXiaomiStagingRows { stagingRows => - val pathRows = stagingRows.flatMap(_("path_evidence").arr.map(_.obj)) - - pathRows.exists(row => - row("source_module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && - row("source_pc").num.toInt == 27 && - row("sink_module_path").str.endsWith("usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac") && - row("sink_pc").num.toInt == 25 + row("module_path").str.endsWith("luci/controller/hwnat.luac") && + row("trigger").str == "io.popen" ) shouldBe true pathRows.exists(row => - row("source_module_path").str.endsWith("usr/lib/lua/luci/controller/api/misystem.luac") && - row("source_pc").num.toInt == 15 && - row("sink_module_path").str.endsWith("usr/lib/lua/xiaoqiang/util/XQQoSUtil.luac") && - row("sink_pc").num.toInt == 82 && - row("path_steps").arr.forall(_.str.contains("::")) + row("source_module_path").str.endsWith("luci/controller/mtkwifi.luac") && + row("sink_module_path").str.endsWith("luci/controller/mtkwifi.luac") && + row("source_pc").num.toInt == 3 && + row("sink_pc").num.toInt == 12 && + row("path_steps").arr.exists(_.str.endsWith("luci/controller/mtkwifi.luac::root.110@pc3:r0")) && + row("path_steps").arr.exists(_.str.endsWith("luci/controller/mtkwifi.luac::root.110@pc12:r1")) ) shouldBe true } } - "export CrossPlatform path search profile without repeated local graph builds" in { - withXiaomiExportDir { exportDir => - val profile = ujson.read(Files.readString(exportDir.resolve("path-search-profile.json"))).obj - - profile.contains("local_path_graph_module_count") shouldBe true - profile.contains("local_path_graph_build_count") shouldBe true - profile.contains("local_path_search_count") shouldBe true - - val moduleCount = profile("local_path_graph_module_count").num.toInt - val buildCount = profile("local_path_graph_build_count").num.toInt - val searchCount = profile("local_path_search_count").num.toInt - - moduleCount should be > 0 - searchCount should be > buildCount - buildCount should be <= (moduleCount * 3) - } - } - - "export CrossPlatform path search profile with source-specific sink pruning" in { - withXiaomiExportDir { exportDir => - val profile = ujson.read(Files.readString(exportDir.resolve("path-search-profile.json"))).obj - - profile.contains("source_sink_pair_count") shouldBe true - profile.contains("qualified_source_sink_pair_count") shouldBe true - profile.contains("prototype_pruned_source_sink_pair_count") shouldBe true - profile.contains("distinct_local_path_query_count") shouldBe true - - val totalPairCount = profile("source_sink_pair_count").num.toInt - val qualifiedPairCount = profile("qualified_source_sink_pair_count").num.toInt - val prunedPairCount = profile("prototype_pruned_source_sink_pair_count").num.toInt - val distinctQueryCount = profile("distinct_local_path_query_count").num.toInt - val localPathSearchCount = profile("local_path_search_count").num.toInt - - totalPairCount shouldBe profile("source_endpoint_count").num.toInt * profile("sink_endpoint_count").num.toInt - totalPairCount should be > qualifiedPairCount - prunedPairCount shouldBe (totalPairCount - qualifiedPairCount) - distinctQueryCount should be <= localPathSearchCount - qualifiedPairCount should be > distinctQueryCount - } - } - - "export r7 performance attribution without changing legacy producer profile fields" in { - withXiaomiExportDir { exportDir => + "export OpenWrt-derived path report totals with scoped path steps" in { + withOpenWrtDerivedExportDir { exportDir => val profile = ujson.read(Files.readString(exportDir.resolve("path-search-profile.json"))).obj - val legacyProfileKeys = Set( - "status", - "source_endpoint_count", - "sink_endpoint_count", - "taint_path_count", - "sanitizer_classification_count", - "report_count", - "local_path_graph_module_count", - "local_path_graph_build_count", - "local_path_search_count", - "distinct_local_path_query_count", - "source_sink_pair_count", - "qualified_source_sink_pair_count", - "prototype_pruned_source_sink_pair_count" - ) - - profile.value.keySet shouldBe (legacyProfileKeys + "performance_attribution") - profile("status").str shouldBe "completed" - profile("source_sink_pair_count").num.toInt shouldBe - profile("source_endpoint_count").num.toInt * profile("sink_endpoint_count").num.toInt - profile("prototype_pruned_source_sink_pair_count").num.toInt shouldBe - profile("source_sink_pair_count").num.toInt - profile("qualified_source_sink_pair_count").num.toInt - - val attribution = profile("performance_attribution").obj - attribution("schema").str shouldBe "lua-r7-performance-attribution-v1" - attribution("unattributed_changed_family_work").num.toLong shouldBe 0L - - val rows = attribution("rows").obj - rows.value.keySet shouldBe Set("P1", "P2", "P3", "P4", "P5", "P6", "P7", "early_short_circuit") - - val p1 = rows("P1").obj - p1("candidate_count").num.toLong should be > 0L - p1("rejected_count").num.toLong should be > 0L - p1("candidate_count").num.toLong shouldBe - p1("rejected_count").num.toLong + p1("accepted_count").num.toLong - - val p7 = rows("P7").obj - p7("status").str shouldBe "not-invoked-no-reuse" - p7("invocation_count").num.toLong shouldBe 0L - p7("reuse_count").num.toLong shouldBe 0L - - val p2 = rows("P2").obj - p2("candidate_count").num.toLong shouldBe - p2("accepted_count").num.toLong + p2("rejected_count").num.toLong - val p3 = rows("P3").obj - p3("candidate_count").num.toLong shouldBe - p3("accepted_count").num.toLong + p3("prototype_rejected_count").num.toLong + - p3("provenance_rejected_count").num.toLong - (p2("rejected_count").num.toLong + p3("prototype_rejected_count").num.toLong + - p3("provenance_rejected_count").num.toLong) should be > 0L - val p4 = rows("P4").obj - p4("candidate_count").num.toLong shouldBe - p4("pc_rejected_count").num.toLong + p4("reachability_rejected_count").num.toLong + - p4("continued_count").num.toLong - p4("pc_rejected_count").num.toLong should be > 0L - p4("reachability_rejected_count").num.toLong should be > 0L - p4("path_constructor_candidate_count").num.toLong shouldBe - p4("path_constructor_accepted_count").num.toLong + p4("path_constructor_rejected_count").num.toLong - val p5 = rows("P5").obj - (p5("node_visit_count").num.toLong + p5("edge_visit_count").num.toLong) should be > 0L - val p6 = rows("P6").obj - (p6("local_path_cache_hit_count").num.toLong + p6("local_path_cache_miss_count").num.toLong) should be > 0L - val early = rows("early_short_circuit").obj - early("count").num.toLong shouldBe - early("pc_rejected_count").num.toLong + early("reachability_rejected_count").num.toLong - early("count").num.toLong should be > 0L - - val pairProfiles = attribution("pair_profiles").arr.map(_.obj).toVector - val pairCounterKeys = Vector( - "source_reachability_check_count", - "source_reachability_accepted_count", - "prototype_unreachable_pair_count", - "source_specific_provenance_pruned_pair_count", - "parameter_position_check_count", - "parameter_position_accepted_count", - "parameter_position_pruned_count", - "path_constructor_check_count", - "path_constructor_accepted_count", - "path_constructor_pruned_count", - "bridge_argument_provenance_candidate_count", - "bridge_candidate_pc_pruned_count", - "bridge_candidate_reachability_pruned_count", - "bridge_local_path_attempt_count", - "bridge_local_path_success_count", - "local_path_search_count", - "distinct_local_path_query_count", - "local_path_cache_hit_count", - "local_path_cache_miss_count", - "local_path_graph_build_count", - "local_path_graph_cache_hit_count", - "local_path_graph_cache_miss_count", - "bridge_path_cache_hit_count", - "bridge_path_cache_miss_count", - "targeted_search_node_visit_count", - "targeted_search_edge_visit_count", - "early_candidate_short_circuit_count", - "taint_path_count", - "report_count" - ) - pairProfiles.foreach { row => - row("source_ref").str should not be empty - row("sink_ref").str should not be empty - row("source_callsite_id").str should include("::") - row("sink_callsite_id").str should include("::") - row("source_trigger").str should not be empty - row("sink_trigger").str should not be empty - row("pair_id").str shouldBe - s"${row("source_ref").str}|${row("source_callsite_id").str}|${row("source_trigger").str}->" + - s"${row("sink_ref").str}|${row("sink_callsite_id").str}|${row("sink_trigger").str}" - pairCounterKeys.foreach(key => row(key).num.toLong should be >= 0L) - row("source_reachability_check_count").num.toLong shouldBe - row("source_reachability_accepted_count").num.toLong + - row("prototype_unreachable_pair_count").num.toLong + - row("source_specific_provenance_pruned_pair_count").num.toLong - row("parameter_position_check_count").num.toLong shouldBe - row("parameter_position_accepted_count").num.toLong + row("parameter_position_pruned_count").num.toLong - row("path_constructor_check_count").num.toLong shouldBe - row("path_constructor_accepted_count").num.toLong + row("path_constructor_pruned_count").num.toLong - row("early_candidate_short_circuit_count").num.toLong shouldBe - row("bridge_candidate_pc_pruned_count").num.toLong + - row("bridge_candidate_reachability_pruned_count").num.toLong - row("local_path_search_count").num.toLong shouldBe - row("local_path_cache_hit_count").num.toLong + row("local_path_cache_miss_count").num.toLong - row("distinct_local_path_query_count").num.toLong shouldBe row("local_path_cache_miss_count").num.toLong - row("local_path_graph_build_count").num.toLong shouldBe row("local_path_graph_cache_miss_count").num.toLong - } - pairProfiles.map(_("pair_id").str).distinct.size shouldBe pairProfiles.size - attribution("retained_pair_profile_count").num.toInt shouldBe pairProfiles.size - attribution("retained_pair_profile_count").num.toLong should be <= - profile("local_path_search_count").num.toLong + profile("taint_path_count").num.toLong - attribution("retained_pair_profile_bytes").num.toLong shouldBe - ujson.write(ujson.Arr.from(pairProfiles)).getBytes(StandardCharsets.UTF_8).length.toLong - info( - s"r7 retained_pair_profile_count=${pairProfiles.size} retained_pair_profile_bytes=${attribution("retained_pair_profile_bytes").num.toLong}" - ) - pairProfiles.foreach { row => - (row("local_path_search_count").num.toLong > 0L || row("taint_path_count").num.toLong > 0L) shouldBe true - } - - def selectPair( - sourceRef: String, - sourceCallsiteId: String, - sourceTrigger: String, - sinkRef: String, - sinkCallsiteId: String, - sinkTrigger: String - ): ujson.Obj = - pairProfiles - .find(row => - row("source_ref").str == sourceRef && - row("source_callsite_id").str == sourceCallsiteId && - row("source_trigger").str == sourceTrigger && - row("sink_ref").str == sinkRef && - row("sink_callsite_id").str == sinkCallsiteId && - row("sink_trigger").str == sinkTrigger - ) - .getOrElse(fail(s"missing attributed pair $sourceRef -> $sinkRef")) - - val commonPairs = Vector( - selectPair("usr/lib/lua/luci/controller/api/misystem.luac:root.151@pc14:r3", "usr/lib/lua/luci/controller/api/misystem.luac::root.151@pc14", "luci.http.formvalue", "usr/lib/lua/xiaoqiang/common/XQFunction.luac:root.33@pc35:r4", "usr/lib/lua/xiaoqiang/common/XQFunction.luac::root.33@pc35", "os.execute"), - selectPair("usr/lib/lua/luci/controller/api/xqsystem.luac:root.40@pc31:r11", "usr/lib/lua/luci/controller/api/xqsystem.luac::root.40@pc31", "luci.http.formvalue", "usr/lib/lua/xiaoqiang/common/XQFunction.luac:root.33@pc35:r4", "usr/lib/lua/xiaoqiang/common/XQFunction.luac::root.33@pc35", "os.execute"), - selectPair("usr/lib/lua/luci/controller/api/misystem.luac:root.147@pc16:r4", "usr/lib/lua/luci/controller/api/misystem.luac::root.147@pc16", "luci.http.formvalue", "usr/lib/lua/luci/controller/api/misystem.luac:root.147@pc102:r9", "usr/lib/lua/luci/controller/api/misystem.luac::root.147@pc102", "os.execute") - ) - commonPairs.foreach { row => - row("path_constructor_check_count").num.toLong should be > 0L - row("local_path_search_count").num.toLong should be > 0L - row("taint_path_count").num.toLong should be > 0L - row("report_count").num.toLong should be > 0L - } - - val recoveredSmarthomeTargetPair = - selectPair("usr/lib/lua/luci/controller/api/xqsmarthome.luac:root.5@pc3:r0", "usr/lib/lua/luci/controller/api/xqsmarthome.luac::root.5@pc3", "luci.http.formvalue", "usr/lib/lua/luci/util.luac:root.36@pc3:r2", "usr/lib/lua/luci/util.luac::root.36@pc3", "io.popen") - recoveredSmarthomeTargetPair("path_constructor_check_count").num.toLong should be > 0L - recoveredSmarthomeTargetPair("bridge_argument_provenance_candidate_count").num.toLong should be > 0L - recoveredSmarthomeTargetPair("taint_path_count").num.toLong shouldBe 1L - recoveredSmarthomeTargetPair("report_count").num.toLong shouldBe 1L - - val recoveredTargetPair = - selectPair("usr/lib/lua/luci/controller/api/xqnetwork.luac:root.93@pc28:r8", "usr/lib/lua/luci/controller/api/xqnetwork.luac::root.93@pc28", "luci.http.formvalue", "usr/lib/lua/xiaoqiang/common/XQFunction.luac:root.33@pc35:r4", "usr/lib/lua/xiaoqiang/common/XQFunction.luac::root.33@pc35", "os.execute") - recoveredTargetPair("path_constructor_check_count").num.toLong should be > 0L - recoveredTargetPair("bridge_argument_provenance_candidate_count").num.toLong should be > 0L - recoveredTargetPair("taint_path_count").num.toLong shouldBe 1L - recoveredTargetPair("report_count").num.toLong shouldBe 1L - Vector(recoveredSmarthomeTargetPair, recoveredTargetPair).foreach { row => - row("path_constructor_check_count").num.toLong should be > 0L - row("bridge_argument_provenance_candidate_count").num.toLong should be > 0L - } - } - - } - - "reject malformed r7 performance attribution before output creation" in { - val requiredCounters = Vector( - "source_reachability_check_count", - "source_reachability_accepted_count", - "prototype_unreachable_pair_count", - "source_specific_provenance_pruned_pair_count", - "parameter_position_check_count", - "parameter_position_accepted_count", - "parameter_position_pruned_count", - "path_constructor_check_count", - "path_constructor_accepted_count", - "path_constructor_pruned_count", - "bridge_argument_provenance_candidate_count", - "bridge_candidate_pc_pruned_count", - "bridge_candidate_reachability_pruned_count", - "bridge_local_path_attempt_count", - "bridge_local_path_success_count", - "local_path_search_count", - "distinct_local_path_query_count", - "local_path_cache_hit_count", - "local_path_cache_miss_count", - "local_path_graph_build_count", - "local_path_graph_cache_hit_count", - "local_path_graph_cache_miss_count", - "bridge_path_cache_hit_count", - "bridge_path_cache_miss_count", - "targeted_search_node_visit_count", - "targeted_search_edge_visit_count", - "early_candidate_short_circuit_count", - "taint_path_count", - "report_count" - ) - val counters = requiredCounters.map(_ -> 0L).toMap ++ Map( - "source_reachability_check_count" -> 1L, - "source_reachability_accepted_count" -> 1L, - "parameter_position_check_count" -> 1L, - "parameter_position_accepted_count" -> 1L, - "path_constructor_check_count" -> 1L, - "path_constructor_accepted_count" -> 1L, - "local_path_search_count" -> 1L, - "distinct_local_path_query_count" -> 1L, - "local_path_cache_miss_count" -> 1L - ) - val validRow = LuaPairPerformanceProfile( - "a.luac:root@pc1:r0", - "b.luac:root@pc2:r1", - "a.luac::root@pc1", - "b.luac::root@pc2", - "luci.http.formvalue", - "os.execute", - counters - ) - val baselineSemantics = LuaProgramSemantics( - Vector.empty, Vector.empty, Vector.empty, Vector.empty, Vector.empty, Vector.empty, Vector.empty, Vector.empty, - Vector.empty, Vector.empty, Vector.empty, Vector.empty, Vector.empty, Vector.empty, Vector.empty, Vector.empty, - LuaPathSearchStats(0, 0, 1, 1, 1, 1, 0), - LuaPerformanceAttribution(1, 0, 1, 0, Vector(validRow), counters) - ) - val validSemantics = baselineSemantics.copy( - sourceEndpoints = Vector(LuaSourceEndpoint(validRow.sourceRef, "a.luac:root@pc1:r0", validRow.sourceTrigger, "bytecode-only")), - sinkEndpoints = Vector(LuaSinkEndpoint(validRow.sinkRef, "b.luac:root@pc2:r1", validRow.sinkTrigger, 0, "bytecode-only")) - ) - def withAggregate(updated: Map[String, Long]): LuaProgramSemantics = - validSemantics.copy( - performanceAttribution = validSemantics.performanceAttribution.copy(aggregateCounters = updated) - ) - val secondRow = validRow.copy( - sourceRef = "c.luac:root@pc3:r0", - sinkRef = "d.luac:root@pc4:r1", - sourceCallsiteId = "c.luac::root@pc3", - sinkCallsiteId = "d.luac::root@pc4" - ) - val malformed = Vector( - validSemantics.copy(performanceAttribution = validSemantics.performanceAttribution.copy(pairProfiles = Vector.empty)) -> "pair profiles are empty", - withAggregate(counters - "report_count") -> "aggregate counter keys do not exactly match", - withAggregate(counters + ("unknown_count" -> 1L)) -> "aggregate counter keys do not exactly match", - validSemantics.copy(performanceAttribution = validSemantics.performanceAttribution.copy(p1CandidateCount = 2L)) -> "P1 candidate count does not partition", - withAggregate(counters.updated("parameter_position_check_count", 2L)) -> "parameter position partition mismatch for aggregate", - withAggregate(counters.updated("source_reachability_check_count", 2L)) -> "source reachability partition mismatch for aggregate", - withAggregate(counters.updated("bridge_candidate_pc_pruned_count", 1L)) -> "bridge candidate partition mismatch for aggregate", - validSemantics.copy(pathSearchStats = validSemantics.pathSearchStats.copy(localPathSearchCount = 2)) -> "aggregate local path search count does not match legacy count", - validSemantics.copy(performanceAttribution = validSemantics.performanceAttribution.copy(pairProfiles = Vector(validRow.copy(counters = counters.updated("taint_path_count", 1L))))) -> "path reconciliation mismatch", - validSemantics.copy(performanceAttribution = validSemantics.performanceAttribution.copy(pairProfiles = Vector(validRow.copy(counters = counters.updated("report_count", 1L))))) -> "report reconciliation mismatch", - validSemantics.copy(performanceAttribution = validSemantics.performanceAttribution.copy(pairProfiles = Vector(validRow, secondRow))) -> "retained pair profile count exceeds", - validSemantics.copy(performanceAttribution = validSemantics.performanceAttribution.copy(pairProfiles = Vector(validRow.copy(sourceTrigger = "x" * 5000)))) -> "retained pair profile payload exceeds", - validSemantics.copy(performanceAttribution = validSemantics.performanceAttribution.copy(pairProfiles = Vector(validRow, validRow))) -> "pair identities are not unique", - validSemantics.copy(performanceAttribution = validSemantics.performanceAttribution.copy(pairProfiles = Vector(validRow.copy(counters = counters - "report_count")))) -> "counter keys do not exactly match", - validSemantics.copy(performanceAttribution = validSemantics.performanceAttribution.copy(pairProfiles = Vector(validRow.copy(counters = counters + ("unknown_count" -> 1L))))) -> "counter keys do not exactly match", - validSemantics.copy(performanceAttribution = validSemantics.performanceAttribution.copy(pairProfiles = Vector(validRow.copy(sourceCallsiteId = "b.luac::root@pc1")))) -> "mismatched source identity", - validSemantics.copy(performanceAttribution = validSemantics.performanceAttribution.copy(pairProfiles = Vector(validRow.copy(sourceTrigger = "")))) -> "empty trigger", - validSemantics.copy(performanceAttribution = validSemantics.performanceAttribution.copy(pairProfiles = Vector(validRow.copy(counters = counters.updated("path_constructor_accepted_count", 0L))))) -> "path constructor partition mismatch" - ) + profile("taint_path_count").num.toInt shouldBe 18 + profile("report_count").num.toInt shouldBe 18 - malformed.foreach { case (semantics, expectedMessage) => - FileUtil.usingTemporaryDirectory("lua2cpg-invalid-r7-attribution") { tmpDir => - val exportDir = tmpDir.resolve("must-not-exist") - val error = intercept[IllegalStateException](LuaRealFirmwareEvidenceExporter.write( - Config(realFirmwareOutputDir = Some(exportDir.toString)), - Vector.empty, - semantics - )) - error.getMessage should include(expectedMessage) - Files.exists(exportDir) shouldBe false + val pathRows = stagingRows(exportDir).flatMap(_("path_evidence").arr.map(_.obj)) + pathRows.size shouldBe 18 + pathRows.foreach { row => + row("source_module_path").str should not be empty + row("sink_module_path").str should not be empty + row("path_steps").arr should not be empty + row("path_steps").arr.foreach { step => + step.str should include("::") + } } - } - } - - "export CrossPlatform upvalue closure call targets for source-specific pruning" in { - withXiaomiStagingRows { stagingRows => - val synchrodata = stagingRows - .find(_("relative_path").str.endsWith("usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac")) - .getOrElse(fail("missing XQSynchrodata staging evidence")) - - val targetRows = synchrodata("call_target_candidate").arr.map(_.obj) - targetRows.exists(row => - hasScopedCallsite(row, "root.3@pc6") && - row("target_ref").str.endsWith("usr/lib/lua/xiaoqiang/util/XQSynchrodata.luac::root.0") && - row("resolution_status").str == "matched" - ) shouldBe true + pathRows.exists(row => row.obj.contains("callsite_id")) shouldBe false } } } - private def withDLinkStagingRows(test: Vector[ujson.Obj] => Unit): Unit = { - val resourceRoot = Paths.get(getClass.getClassLoader.getResource("OpenWrtDerived-real-firmware-path-report").toURI) + private def withOpenWrtDerivedExportDir(test: Path => Unit): Unit = { + val resourceRoot = Paths.get(getClass.getClassLoader.getResource("openwrt-derived-firmware-lua/usr/lib/lua").toURI) - FileUtil.usingTemporaryDirectory("lua2cpg-OpenWrtDerived-real-firmware-path-report") { tmpDir => - val outputPath = tmpDir.resolve("OpenWrtDerived-real-firmware-path-report.cpg.bin").toString - val exportDir = tmpDir.resolve("real-firmware-export") + FileUtil.usingTemporaryDirectory("lua2cpg-openwrt-derived-real-firmware-export") { tmpDir => + val outputPath = tmpDir.resolve("openwrt-derived-firmware-lua.cpg.bin").toString + val exportDir = tmpDir.resolve("openwrt-derived-firmware-lua-evidence") val cpg = new Lua2Cpg() .createCpg( Config(realFirmwareOutputDir = Some(exportDir.toString)) @@ -2916,64 +119,21 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { .get cpg.close() - val stagingDir = exportDir.resolve("staging") - val stagingStream = Files.list(stagingDir) - val stagingFiles = stagingStream.iterator.asScala.toVector - try { - stagingFiles.size should be > 0 - test(stagingFiles.map(path => ujson.read(Files.readString(path)).obj)) - } finally { - stagingStream.close() - } + test(exportDir) } } - private def hasScopedCallsite(row: ujson.Obj, localCallsiteId: String): Boolean = - row("callsite_id").str.contains("::") && row("callsite_id").str.endsWith(s"::$localCallsiteId") - - private def assertRequestMitvStringMatchSanitizer(path: ujson.Obj): Unit = { - val sanitizerCall = "usr/lib/lua/xiaoqiang/util/XQMitvUtil.luac::root.1@pc36" - path("classification").str shouldBe "sanitized" - path("path_steps").arr.exists(_.str == s"$sanitizerCall:r3") shouldBe true - path("sanitizer_hits").arr.exists { hit => - val row = hit.obj - row("callsite_id").str == sanitizerCall && - row("sanitizer_name").str == "string.match" && - row("applies_to_sink").bool && - row("on_dataflow_chain").bool - } shouldBe true - } - - private def withXiaomiStagingRows(test: Vector[ujson.Obj] => Unit): Unit = { - withXiaomiExportDir { exportDir => - val stagingDir = exportDir.resolve("staging") - val stagingStream = Files.list(stagingDir) - val stagingFiles = stagingStream.iterator.asScala.toVector - try { - stagingFiles.size should be > 0 - test(stagingFiles.map(path => ujson.read(Files.readString(path)).obj)) - } finally { - stagingStream.close() - } + private def stagingRows(exportDir: Path) = { + val stagingStream = Files.list(exportDir.resolve("staging")) + try { + val rows = stagingStream.iterator.asScala.toVector.map(path => ujson.read(Files.readString(path)).obj) + rows should not be empty + rows + } finally { + stagingStream.close() } } - private def withXiaomiExportDir(test: java.nio.file.Path => Unit): Unit = { - val resourceRoot = Paths.get(getClass.getClassLoader.getResource("CrossPlatform-real-firmware-path-report").toURI) - - FileUtil.usingTemporaryDirectory("lua2cpg-CrossPlatform-real-firmware-path-report") { tmpDir => - val outputPath = tmpDir.resolve("CrossPlatform-real-firmware-path-report.cpg.bin").toString - val exportDir = tmpDir.resolve("real-firmware-export") - val cpg = new Lua2Cpg() - .createCpg( - Config(realFirmwareOutputDir = Some(exportDir.toString)) - .withInputPath(resourceRoot.toString) - .withOutputPath(outputPath) - ) - .get - cpg.close() - - test(exportDir) - } - } + private def hasScopedCallsite(row: ujson.Obj, localCallsiteId: String): Boolean = + row("callsite_id").str.contains("::") && row("callsite_id").str.endsWith(s"::$localCallsiteId") } From 4568003c4160f8c81a6214542acacc73b68e594e Mon Sep 17 00:00:00 2001 From: prankster009 Date: Fri, 17 Jul 2026 02:18:21 -0400 Subject: [PATCH 090/105] docs(lua2cpg): document neutral firmware corpus --- joern-cli/frontends/lua2cpg/README.md | 33 +++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/README.md b/joern-cli/frontends/lua2cpg/README.md index a5fb4d42be21..befabac8eabf 100644 --- a/joern-cli/frontends/lua2cpg/README.md +++ b/joern-cli/frontends/lua2cpg/README.md @@ -127,6 +127,38 @@ The evidence directory contains: This export is optional. The primary `lua2cpg` output is the CPG written by `--output`. +## Self-Contained Reviewer Corpus + +The test resources include a self-contained OpenWrt-derived Lua corpus for +review and regression checks: + +```text +joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua +``` + +The corpus preserves the original `usr/lib/lua` layout and contains: + +- 42 `.lua` source files recorded in the CPG file inventory. +- 42 Lua 5.1 `.luac` bytecode files analyzed by the bytecode pipeline. + +A generated native JSON analysis report is committed for quick inspection: + +```text +joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua-report +``` + +To regenerate that report from the Joern repository root: + +```bash +joern-cli/frontends/lua2cpg/target/universal/stage/bin/lua2cpg \ + joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua/usr/lib/lua \ + --output /tmp/openwrt-derived-firmware-lua.cpg.bin \ + --lua-real-firmware-output-dir /tmp/openwrt-derived-firmware-lua-report +``` + +The committed report contains native JSON evidence only. The generated CPG +binary is not committed. + ## Supported Analysis - Lua version: Lua 5.1. @@ -176,6 +208,7 @@ sbt 'lua2cpg/testOnly io.joern.lua2cpg.BytecodeModelSmokeTest' sbt 'lua2cpg/testOnly io.joern.lua2cpg.IntraproceduralSemanticsSmokeTest' sbt 'lua2cpg/testOnly io.joern.lua2cpg.InterproceduralModuleTaintSmokeTest' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest' +sbt 'lua2cpg/testOnly io.joern.lua2cpg.OpenWrtDerivedFirmwareCorpusSmokeTest' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RealFirmwareEvidenceExportSmokeTest' ``` From 18266e4763afae7fb03f3247b6063ee83ada538e Mon Sep 17 00:00:00 2001 From: prankster009 Date: Fri, 17 Jul 2026 11:11:54 -0400 Subject: [PATCH 091/105] fix(lua2cpg): enforce luac bytecode input contract --- .../bc-malformed-diagnostic/not-lua-bytecode.luac | 1 + .../test/scala/io/joern/lua2cpg/BytecodeModelSmokeTest.scala | 3 ++- .../io/joern/lua2cpg/bytecode/LuaBytecodeDecoderTest.scala | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/bytecode-model/bc-malformed-diagnostic/not-lua-bytecode.luac diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/bytecode-model/bc-malformed-diagnostic/not-lua-bytecode.luac b/joern-cli/frontends/lua2cpg/src/test/resources/bytecode-model/bc-malformed-diagnostic/not-lua-bytecode.luac new file mode 100644 index 000000000000..af3f833d8afd --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/bytecode-model/bc-malformed-diagnostic/not-lua-bytecode.luac @@ -0,0 +1 @@ +not lua bytecode diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/BytecodeModelSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/BytecodeModelSmokeTest.scala index 6aaa53996754..5247cc185296 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/BytecodeModelSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/BytecodeModelSmokeTest.scala @@ -58,13 +58,14 @@ class BytecodeModelSmokeTest extends AnyWordSpec with Matchers { .fullName .l diagnostics should contain allOf ( - "lua:bytecode-model/bc-malformed-diagnostic/not-lua-bytecode.bin:diagnostic:not-lua-bytecode", + "lua:bytecode-model/bc-malformed-diagnostic/not-lua-bytecode.luac:diagnostic:not-lua-bytecode", "lua:bytecode-model/bc-malformed-diagnostic/truncated.luac:diagnostic:truncated-bytecode", "lua:bytecode-model/bc-malformed-diagnostic/unsupported-version.luac:diagnostic:unsupported-bytecode-version", "lua:bytecode-model/bc-malformed-diagnostic/unsupported-profile.luac:diagnostic:unsupported-bytecode-profile", "lua:bytecode-model/bc-malformed-diagnostic/malformed-constant.luac:diagnostic:malformed-constant", "lua:bytecode-model/bc-stripped-metadata/input.luac:diagnostic:metadata-unavailable" ) + diagnostics.exists(_.contains("not-lua-bytecode.bin")) shouldBe false methodFullNames.filter(_.startsWith("lua:bytecode-model/bc-malformed-diagnostic/")) shouldBe Nil } finally { diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/bytecode/LuaBytecodeDecoderTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/bytecode/LuaBytecodeDecoderTest.scala index 893d239e1cb9..b1587c8df5c5 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/bytecode/LuaBytecodeDecoderTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/bytecode/LuaBytecodeDecoderTest.scala @@ -44,7 +44,7 @@ class LuaBytecodeDecoderTest extends AnyWordSpec with Matchers { "return diagnostics without accepted prototype models for malformed inputs" in { val cases = Seq( - "not-lua-bytecode.bin" -> "not-lua-bytecode", + "not-lua-bytecode.luac" -> "not-lua-bytecode", "truncated.luac" -> "truncated-bytecode", "unsupported-version.luac" -> "unsupported-bytecode-version", "unsupported-profile.luac" -> "unsupported-bytecode-profile", From a9d87fef152ac8d1fbc4943521dccf6e3b1d8888 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Fri, 17 Jul 2026 11:20:47 -0400 Subject: [PATCH 092/105] fix(lua2cpg): generalize Lua module resolution --- .../left.lua | 5 ++++ .../left.luac | Bin 341 -> 431 bytes .../right.lua | 5 ++++ .../right.luac | Bin 342 -> 432 bytes .../controller.lua | 7 ++++++ .../controller.luac | Bin 390 -> 452 bytes .../library.luac | Bin 328 -> 0 bytes .../missinglib.lua | 7 ++++++ .../missinglib.luac | Bin 0 -> 392 bytes .../controller.lua | 15 ++++++++++++ .../controller.luac | Bin 844 -> 905 bytes .../returnlib.lua | 7 ++++++ .../{library.luac => returnlib.luac} | Bin 331 -> 394 bytes .../module-resolution-generic/a/foo.lua | 1 + .../module-resolution-generic/a/foo.luac | Bin 0 -> 230 bytes .../module-resolution-generic/b/foo.lua | 1 + .../module-resolution-generic/b/foo.luac | Bin 0 -> 230 bytes .../module-resolution-generic/controller.lua | 7 ++++++ .../module-resolution-generic/controller.luac | Bin 0 -> 383 bytes .../vendor/luci/util.lua | 1 + .../vendor/luci/util.luac | Bin 0 -> 244 bytes .../z/deep/foo/init.lua | 1 + .../z/deep/foo/init.luac | Bin 0 -> 243 bytes .../InterproceduralModuleTaintSmokeTest.scala | 22 +++++++++++------- 24 files changed, 70 insertions(+), 9 deletions(-) create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-ambiguous-unresolved-dynamic-negative/left.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-ambiguous-unresolved-dynamic-negative/right.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-missing-field-negative/controller.lua delete mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-missing-field-negative/library.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-missing-field-negative/missinglib.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-missing-field-negative/missinglib.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-return-table-field-call/controller.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-return-table-field-call/returnlib.lua rename joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-return-table-field-call/{library.luac => returnlib.luac} (58%) create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/a/foo.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/a/foo.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/b/foo.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/b/foo.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/controller.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/controller.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/vendor/luci/util.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/vendor/luci/util.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/z/deep/foo/init.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/z/deep/foo/init.luac diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-ambiguous-unresolved-dynamic-negative/left.lua b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-ambiguous-unresolved-dynamic-negative/left.lua new file mode 100644 index 000000000000..90c21f9b21d3 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-ambiguous-unresolved-dynamic-negative/left.lua @@ -0,0 +1,5 @@ +module("shared.module") + +function exec(cmd) + return io.popen(cmd) +end diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-ambiguous-unresolved-dynamic-negative/left.luac b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-ambiguous-unresolved-dynamic-negative/left.luac index 263e8d23b5c8dbbe5460019d6a461aa5c41293d5..9fb9a7f4b5d3711ff7538ef0a924feebe62f9d61 100644 GIT binary patch literal 431 zcmZurOHKnZ4E0O{?TQP4Sg}nbRXxCT0V_5f0Hlz(Lr^9e4 delta 161 zcmZ3_e3eOA+NU%zkb#kfgN1{^iva>0N>YnUiuKbnD@sa>Qi~@hxJ$DFWw?Mi1&BEv z7#idl7#QUk8WYnUiuKbnD@sa>Qi~@hcu2DXWw?Mi1&BEv z7#idl7#QUk8Wp+X>v8HiaY?g|iP0W#1r>tuOGbzWAG5+G&=;mN^_#sDuMD%}78 delta 126 zcmX@Y+{Uad?Ngc<$iT?L!NS4d$N&KjC8@vQpJh61v#lX pnMn+cP!SNt1jH;8e*}m!0~zR;WpXT|Ixh=I2@tb^@Z^b%#sJl96I=iQ diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-missing-field-negative/library.luac b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-missing-field-negative/library.luac deleted file mode 100644 index 8d4b8d6e136e3ab7c50fafa60d3b414598553328..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 328 zcmZ8bOAdlC5FMZz^a>{207;B%!$w!S@Bo^iEu;y*P-=9?(L9>-in#D4(|K>^&2&~r zo1MuMp$KV5Bhp@(-Xytx_O??dk(+H?HPV(UuCy__F5+CPQpU9^vR*$_QtDfmb+2`4 zv+oWh-=FcjL7hZ7Xj3v$& sumE&J0%o9XT2(uS3LO7|_3j4eBDz=7G29CvPknp?J=LIr)6W6*0XtPBx&QzG diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-missing-field-negative/missinglib.lua b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-missing-field-negative/missinglib.lua new file mode 100644 index 000000000000..5ba42fc9395c --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-missing-field-negative/missinglib.lua @@ -0,0 +1,7 @@ +local M = {} + +function M.run(cmd) + return io.popen(cmd) +end + +return M diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-missing-field-negative/missinglib.luac b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-missing-field-negative/missinglib.luac new file mode 100644 index 0000000000000000000000000000000000000000..24c16ee25868d1efefd3c8fd50e75ebf74fc59dc GIT binary patch literal 392 zcmZ8bOHKnZ4E0P?C|m)lHwcj`4lo;7!GZ&T6cTroDwB-zVTpRQ9<6pdU6d!iJm*uj`>es d-~Q0L0L)}t)nci-zO!<|{X(AMSjNZAhBNA8J-h$_ literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-return-table-field-call/controller.lua b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-return-table-field-call/controller.lua new file mode 100644 index 000000000000..f9a33a6bf9e1 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-return-table-field-call/controller.lua @@ -0,0 +1,15 @@ +local function via_local() + local lib = require("returnlib") + local cmd = luci.http.formvalue("cmd") + return lib.run(cmd) +end + +local function via_direct() + local cmd = luci.http.formvalue("cmd") + return require("returnlib").run(cmd) +end + +return { + via_local = via_local, + via_direct = via_direct, +} diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-return-table-field-call/controller.luac b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/d24-module-return-table-field-call/controller.luac index 448a3e104287134d8d5155657b549cf4e5593348..456de8fd9bd1f34dbcaccbdc0dccc41ea46875c0 100644 GIT binary patch delta 134 zcmX@Z*2$hA?Ngc<$iT?L!NS2%&j0}qS^23&dAi9tnfhr(`FSO&c`3#EIi-n4$pz{9 z#YM^bC8@BA2azfZSnMsoq7{w;fXIcvY{K75I delta 66 zcmeBVKf|Uh?Ngc<$iT?L!NS4d!~g*fC8@o?Ngc<$iT?L!NS2{&j0}qC8@MelZq0HDkt{- F1prU?53B$H diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/a/foo.lua b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/a/foo.lua new file mode 100644 index 000000000000..14de4f551593 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/a/foo.lua @@ -0,0 +1 @@ +return { selected = "a" } diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/a/foo.luac b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/a/foo.luac new file mode 100644 index 0000000000000000000000000000000000000000..b3456856f4ce18bf06bca1d89a593164efd79f17 GIT binary patch literal 230 zcmZ8a!41P840IwMNmrELXli04@zjk5g>=dUx_OC E0YcO>KL7v# literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/b/foo.lua b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/b/foo.lua new file mode 100644 index 000000000000..cffd4514930b --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/b/foo.lua @@ -0,0 +1 @@ +return { selected = "b" } diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/b/foo.luac b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/b/foo.luac new file mode 100644 index 0000000000000000000000000000000000000000..f8809c2f89e9992c20160a8fb5be25b4eb19e576 GIT binary patch literal 230 zcmZ8a!41P840IwMNmr5(jC#!-ZqW6l_9Y^xbSLCN`KUWn@aRu%U#mMtI|B$QIUKzN0OkWY*Qdb7*N_ zvWr2k6o{R{8J8Gu_3rt$wZ0dXFA88(YM%hQ33lkKCdNt)i9DH17DSU9=7ZZ}_g9Be F=?g<)GeH0V literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/controller.lua b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/controller.lua new file mode 100644 index 000000000000..ef00083024aa --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/controller.lua @@ -0,0 +1,7 @@ +local first = require("foo") +local util = require("luci.util") + +return { + first = first, + util = util, +} diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/controller.luac b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/controller.luac new file mode 100644 index 0000000000000000000000000000000000000000..6c4d61fb5933e26d3ff8dfce404a34d962569e68 GIT binary patch literal 383 zcmY*UK~4iP4D_}ED*6NvKiEd%1pLowHgVBK+7g7t|qWOoJ a)DzTOAE?Jeg>tu^bGgyMH`Xedw)q200!V@Y literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/vendor/luci/util.lua b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/vendor/luci/util.lua new file mode 100644 index 000000000000..9bb1c47b66f9 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/vendor/luci/util.lua @@ -0,0 +1 @@ +return { selected = "util" } diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/vendor/luci/util.luac b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/vendor/luci/util.luac new file mode 100644 index 0000000000000000000000000000000000000000..4054203490f8c445021b1d1b60ae59b3a39deb75 GIT binary patch literal 244 zcmZ8b%MHRX40Wp@wXA^D4T2>m=m1<8AxcaY6cXeloH%rJ9NH6>{C&^QK3%K1L8k^a zzy~&)ABi|>8$8}Q#X`|#4AorhYr)JG3*~}LIaRil(F=uNoGiJ@Cg^8!HIObw0Nk6# z4z>6c^+J)@TYQUdViC3;1qo+4v47A~-z$m78DN~}bpq%nSfQ7h8YMK#6v!5Gpbpyr On$E9myWOAf9QXq9H90K+ literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/z/deep/foo/init.lua b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/z/deep/foo/init.lua new file mode 100644 index 000000000000..38ef5d22f1fc --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/z/deep/foo/init.lua @@ -0,0 +1 @@ +return { selected = "init" } diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/z/deep/foo/init.luac b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/module-resolution-generic/z/deep/foo/init.luac new file mode 100644 index 0000000000000000000000000000000000000000..aab948e2da378732a3b6bfad1c27dd089199f71f GIT binary patch literal 243 zcmZ8b%MHUI40R$ZC0(IXHlUU|0R!~X5fC^L6@|#arM+bILX%UUwKWC}K+FZyQI1rr<0lrl173UbMg3H%>;dr2hz8BS>6~L&}HUV@KY|z)97^`bY matched:d16-rf-webcmd-cross-module-popen/mtkwifi.luac" ) + markerCodes(reopened, "lua.module.resolution") should contain allOf ( + "module-resolution-generic/controller.luac require foo -> matched:module-resolution-generic/a/foo.luac", + "module-resolution-generic/controller.luac require luci.util -> matched:module-resolution-generic/vendor/luci/util.luac" + ) markerCodes(reopened, "lua.module.return_table") should contain( - "d24-module-return-table-field-call/library.luac::run -> root.0" + "d24-module-return-table-field-call/returnlib.luac::run -> root.0" ) markerCodes(reopened, "lua.module.field_call_target") should contain allOf ( - "d24-module-return-table-field-call/controller.luac:root.0@pc10 -> d24-module-return-table-field-call/library.luac::root.0", - "d24-module-return-table-field-call/controller.luac:root.1@pc10 -> d24-module-return-table-field-call/library.luac::root.0" + "d24-module-return-table-field-call/controller.luac:root.0@pc10 -> d24-module-return-table-field-call/returnlib.luac::root.0", + "d24-module-return-table-field-call/controller.luac:root.1@pc10 -> d24-module-return-table-field-call/returnlib.luac::root.0" ) markerCodes(reopened, "lua.calltarget.cross_boundary") should contain( "d16-rf-webcmd-cross-module-popen/controller.luac:root.1@pc8 -> d16-rf-webcmd-cross-module-popen/mtkwifi.luac::root.1" @@ -60,10 +64,9 @@ class InterproceduralModuleTaintSmokeTest extends AnyWordSpec with Matchers { withClue(s"unresolved return flows: ${unresolvedReturnFlows.mkString(", ")}") { unresolvedReturnFlows.exists(_.contains("root.2@pc2")) shouldBe false } - markerCodes(reopened, "lua.module.resolution") - .exists(code => - code.contains("d24-module-ambiguous-unresolved-dynamic-negative") && code.contains("-> matched:") - ) shouldBe false + markerCodes(reopened, "lua.module.resolution") should contain( + "d24-module-ambiguous-unresolved-dynamic-negative/ambiguous.luac require shared.module -> matched:d24-module-ambiguous-unresolved-dynamic-negative/left.luac" + ) markerCodes(reopened, "lua.calltarget.cross_boundary") .exists(_.contains("d24-module-missing-field-negative")) shouldBe false markerCodes(reopened, "lua.taint.path") @@ -73,7 +76,6 @@ class InterproceduralModuleTaintSmokeTest extends AnyWordSpec with Matchers { boundaryCodes should contain allOf ( "d24-interproc-unresolved-callee-negative/input.luac:root.2@pc2 reason=unresolved-callee", "d24-module-ambiguous-unresolved-dynamic-negative/missing.luac:require:missing.module reason=unresolved-module", - "d24-module-ambiguous-unresolved-dynamic-negative/ambiguous.luac:require:shared.module reason=ambiguous-module", "d24-module-ambiguous-unresolved-dynamic-negative/controller.luac:require:dynamic reason=dynamic-require", "d24-module-missing-field-negative/controller.luac:root.0@pc3 reason=missing-export-field", "bc-kill-overwrite/input.luac:root@pc3:r2->root@pc7:r4 reason=killed-taint-path", @@ -81,7 +83,9 @@ class InterproceduralModuleTaintSmokeTest extends AnyWordSpec with Matchers { ) val e4NodeCount = reopened.call - .name("lua\\.(module\\.resolution|module\\.return_table|module\\.field_call_target|interproc\\.arg_flow|interproc\\.return_flow|calltarget\\.cross_boundary|taint\\.path|e4\\.boundary)") + .name( + "lua\\.(module\\.resolution|module\\.return_table|module\\.field_call_target|interproc\\.arg_flow|interproc\\.return_flow|calltarget\\.cross_boundary|taint\\.path|e4\\.boundary)" + ) .size val e4ReachingDefEdgeCount = reopened.identifier.outE(EdgeTypes.REACHING_DEF).size val e4TaintPathCount = reopened.call.nameExact("lua.taint.path").size From de6c10d6002dee7f6e124b1013987d2e50e54934 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Fri, 17 Jul 2026 11:25:30 -0400 Subject: [PATCH 093/105] fix(lua2cpg): derive local value flow generically --- .../bytecode/LuaInstructionSemantics.scala | 39 ++++++++----- .../lua2cpg/passes/LuaBytecodeModelPass.scala | 18 +++--- .../local-value-flow-generic/input.lua | 26 +++++++++ .../local-value-flow-generic/input.luac | Bin 0 -> 959 bytes .../IntraproceduralSemanticsSmokeTest.scala | 52 +++++++++++++----- 5 files changed, 99 insertions(+), 36 deletions(-) create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/intraprocedural-semantics/local-value-flow-generic/input.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/intraprocedural-semantics/local-value-flow-generic/input.luac diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala index 9ca2b53384ac..064c8b47585b 100644 --- a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala @@ -301,7 +301,8 @@ object LuaInstructionSemantics { private var reaching = (0 until prototype.numParams).map(slot => slot -> Set(staticSlotRef(slot))).toMap private var closuresBySlot = Map.empty[Int, LuaClosureValue] - private var tableWrites = Map.empty[(Int, String), Set[String]] + private var tableObjectsBySlot = (0 until prototype.numParams).map(slot => slot -> staticSlotRef(slot)).toMap + private var tableWrites = Map.empty[(String, String), Set[String]] private var closureTableWrites = Map.empty[(Int, String), LuaClosureValue] private var globalWrites = Map.empty[String, Set[String]] private var mutatedUpvalues = Set.empty[Int] @@ -311,11 +312,13 @@ object LuaInstructionSemantics { conditionalWriteUntilPc = conditionalWriteUntilPc.filter(instruction.pc < _) instruction.opcode match { case LuaOpcode.Move => - val source = readSlot(instruction, instruction.b) + val source = readSlot(instruction, instruction.b) + val movedTableObject = tableObjectsBySlot.get(instruction.b) val movedTableClosures = closureTableWrites.collect { case ((tableSlot, key), closure) if tableSlot == instruction.b => key -> closure } writeSlot(instruction, instruction.a, Set(source), "move") + movedTableObject.foreach(tableObject => tableObjectsBySlot += instruction.a -> tableObject) closuresBySlot.get(instruction.b).foreach { closure => val moved = closure.copy(slot = instruction.a, valueRef = slotRef(instruction.pc, instruction.a)) closuresBySlot += instruction.a -> moved @@ -329,7 +332,10 @@ object LuaInstructionSemantics { } case LuaOpcode.LoadK => writeSlot(instruction, instruction.a, Set(constantRef(instruction.b)), "loadk") - case LuaOpcode.LoadBool | LuaOpcode.LoadNil | LuaOpcode.NewTable | LuaOpcode.Vararg => + case LuaOpcode.NewTable => + writeSlot(instruction, instruction.a, Set(slotRef(instruction.pc, instruction.a)), "newtable") + tableObjectsBySlot += instruction.a -> slotRef(instruction.pc, instruction.a) + case LuaOpcode.LoadBool | LuaOpcode.LoadNil | LuaOpcode.Vararg => writeSlot( instruction, instruction.a, @@ -378,6 +384,7 @@ object LuaInstructionSemantics { val write = slotRef(instruction.pc, instruction.a) writeSlot(instruction, instruction.a, Set(write), "getglobal") stringConstant(instruction.b).foreach { name => + tableObjectsBySlot += instruction.a -> s"global:$name" globalWrites.get(name).foreach { sources => sources.foreach { source => globalFlows += LuaGlobalFlow(name, source, write, source, BytecodeProvenance) @@ -472,16 +479,17 @@ object LuaInstructionSemantics { } private def handleGetTable(instruction: LuaInstruction): Unit = { - val tableSlot = instruction.b - val tableRead = readSlot(instruction, tableSlot) - val keyReads = instruction.c.flatMap(rkRegister).map(readSlot(instruction, _)).toSet - val write = slotRef(instruction.pc, instruction.a) + val tableSlot = instruction.b + val tableObject = tableObjectsBySlot.get(tableSlot) + val tableRead = readSlot(instruction, tableSlot) + val keyReads = instruction.c.flatMap(rkRegister).map(readSlot(instruction, _)).toSet + val write = slotRef(instruction.pc, instruction.a) val loadedClosure = instruction.c .flatMap(rkConstantName) .flatMap(key => closureTableWrites.get((tableSlot, key))) writeSlot(instruction, instruction.a, keyReads + tableRead, "gettable") instruction.c.flatMap(rkConstantRef).foreach { key => - tableWrites.get((tableSlot, key)).foreach { sources => + tableObject.flatMap(identity => tableWrites.get((identity, key))).foreach { sources => sources.foreach { source => tableFieldFlows += LuaTableFieldFlow( slotRef(instruction.pc, tableSlot), @@ -515,8 +523,11 @@ object LuaInstructionSemantics { rkRegister(instruction.b).foreach(readSlot(instruction, _)) val valueSlot = instruction.c.flatMap(rkRegister) val valueRefs = valueSlot.map(readSlot(instruction, _)).toSet - instruction.bOptionConstantString.foreach { key => - tableWrites += (tableSlot, key) -> valueRefs + for { + tableObject <- tableObjectsBySlot.get(tableSlot) + key <- instruction.bOptionConstantString + } { + tableWrites += (tableObject, key) -> valueRefs } instruction.bOptionConstantName.foreach { key => valueSlot.flatMap(closuresBySlot.get).foreach { closure => @@ -535,11 +546,12 @@ object LuaInstructionSemantics { private def handleSetList(instruction: LuaInstruction): Unit = { val tableSlot = instruction.a readSlot(instruction, tableSlot) - if (instruction.b > 0) { - val valueRefs = (1 to instruction.b).map(offset => readSlot(instruction, tableSlot + offset)) + if (instruction.b > 0 && tableObjectsBySlot.contains(tableSlot)) { + val tableObject = tableObjectsBySlot(tableSlot) + val valueRefs = (1 to instruction.b).map(offset => readSlot(instruction, tableSlot + offset)) instruction.c.foreach { block => valueRefs.zipWithIndex.foreach { case (valueRef, index) => - tableWrites += (tableSlot, setListElementKey(block, index)) -> Set(valueRef) + tableWrites += (tableObject, setListElementKey(block, index)) -> Set(valueRef) } } } @@ -610,6 +622,7 @@ object LuaInstructionSemantics { } reaching += slot -> Set(write) closuresBySlot -= slot + tableObjectsBySlot -= slot closureTableWrites = closureTableWrites.filterNot { case ((tableSlot, _), _) => tableSlot == slot } } } diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/passes/LuaBytecodeModelPass.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/passes/LuaBytecodeModelPass.scala index 683dc84b90c8..072b83806c99 100644 --- a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/passes/LuaBytecodeModelPass.scala +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/passes/LuaBytecodeModelPass.scala @@ -19,7 +19,7 @@ class LuaBytecodeModelPass( private given ValidationMode = ValidationMode.Disabled - private final case class PrototypeAst(ast: Ast, reachingDefEdges: Vector[(NewIdentifier, NewIdentifier, String)]) + private final case class PrototypeAst(ast: Ast, reachingDefEdges: Vector[(NewNode, NewNode, String)]) override def run(diffGraph: DiffGraphBuilder): Unit = { val decoded = decodedInputs.getOrElse(LuaBytecodeModelPass.decodeInputs(config)) val programSemantics = LuaProgramSemantics.normalize(decoded.map(item => item.relativeName -> item.result)) @@ -430,12 +430,12 @@ class LuaBytecodeModelPass( .lineNumber(order) .columnNumber(0) - private def reachingDefEdges( - ast: Ast, - semantics: LuaPrototypeSemantics - ): Vector[(NewIdentifier, NewIdentifier, String)] = { - val nodesByCode = ast.nodes.collect { case node: NewIdentifier => node.code -> node }.toMap - val edges = mutable.LinkedHashSet.empty[(NewIdentifier, NewIdentifier, String)] + private def reachingDefEdges(ast: Ast, semantics: LuaPrototypeSemantics): Vector[(NewNode, NewNode, String)] = { + val nodesByCode = ast.nodes.collect { + case node: NewIdentifier => node.code -> node + case node: NewMethodParameterIn => node.code -> node + }.toMap + val edges = mutable.LinkedHashSet.empty[(NewNode, NewNode, String)] semantics.localFlows.foreach { flow => addEdge(nodesByCode, edges, flow.sourceRef, flow.sinkRef, flow.sourceRef) } @@ -452,8 +452,8 @@ class LuaBytecodeModelPass( } private def addEdge( - nodesByCode: Map[String, NewIdentifier], - edges: mutable.LinkedHashSet[(NewIdentifier, NewIdentifier, String)], + nodesByCode: Map[String, NewNode], + edges: mutable.LinkedHashSet[(NewNode, NewNode, String)], sourceRef: String, sinkRef: String, variable: String diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/intraprocedural-semantics/local-value-flow-generic/input.lua b/joern-cli/frontends/lua2cpg/src/test/resources/intraprocedural-semantics/local-value-flow-generic/input.lua new file mode 100644 index 000000000000..25b420ddeeb5 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/intraprocedural-semantics/local-value-flow-generic/input.lua @@ -0,0 +1,26 @@ +local function alias_flow(input) + local box = {} + box.value = input + local alias = box + return os.execute(alias.value) +end + +local function overwrite_flow(input) + local box = {} + box.value = input + box.value = "safe" + return os.execute(box.value) +end + +local function conditional_parameter(input, use_default) + if use_default then + input = "safe" + end + return os.execute(input) +end + +return { + alias_flow = alias_flow, + overwrite_flow = overwrite_flow, + conditional_parameter = conditional_parameter, +} diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/intraprocedural-semantics/local-value-flow-generic/input.luac b/joern-cli/frontends/lua2cpg/src/test/resources/intraprocedural-semantics/local-value-flow-generic/input.luac new file mode 100644 index 0000000000000000000000000000000000000000..ff7ddae6889962ee78a387703eb499ed94d58f85 GIT binary patch literal 959 zcmb7D%T5A85Ujz4;45l0J`+!#{f4<>ym<9OMi`P!R+hjna2yZ#7kctT_M!>?gMN!$ zvpd8C1ns2Q>7JgRs&3}uHaB-rQ8Q{{8>Q*EKghG|BsW!npIWpjzgL2;2HqU-Spaju z&9!rLho^5qUBY1lKon}3BsQaVFG(ke_GE*Wj_q(Vj5AxskIOJRX}=q1aoRUYdtip< z(Pnn&%T)Gwp4_By$(&|exgbo&T{2>X9O_Ezgq)1Cg5Z$HO zsqN&M^$u&-1YU}K>r23r+z+LF)5Hf@G4dl|J&NjZE*4YN>cjz`5-#0a>vl;iC0wC3#_-{1Lrc zsq0>rGIhs!&a~)qS{1VXt;_fR|3dbq3~?YhTmsTqEril(`N+1rwrBDr^L`i<+r$>I i9jwH^?up=3a3(mfK=MnT0QIueikCoM++P160-qoDpK+1^ literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/IntraproceduralSemanticsSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/IntraproceduralSemanticsSmokeTest.scala index a4273c260fc9..50b9273528af 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/IntraproceduralSemanticsSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/IntraproceduralSemanticsSmokeTest.scala @@ -2,7 +2,7 @@ package io.joern.lua2cpg import io.shiftleft.codepropertygraph.cpgloading.CpgLoader import io.shiftleft.codepropertygraph.generated.EdgeTypes -import io.shiftleft.codepropertygraph.generated.nodes.{Identifier, StoredNode} +import io.shiftleft.codepropertygraph.generated.nodes.{CfgNode, StoredNode} import io.shiftleft.semanticcpg.language.* import io.shiftleft.semanticcpg.utils.FileUtil import org.scalatest.matchers.should.Matchers @@ -27,9 +27,17 @@ class IntraproceduralSemanticsSmokeTest extends AnyWordSpec with Matchers { try { hasReachingDef(reopened, "bc-kill-overwrite", "root@pc4:r2", "root@pc7:r4") shouldBe true hasReachingDef(reopened, "d24-defuse-transitive-chain", "root@pc3:r2", "root@pc8:r6") shouldBe true + hasReachingDef(reopened, "local-value-flow-generic", "root.0:r0", "root.0@pc6:r4") shouldBe true + hasReachingDef(reopened, "local-value-flow-generic", "root.2:r0", "root.2@pc6:r3") shouldBe true hasReachingDef(reopened, "bc-kill-overwrite", "root@pc3:r2", "root@pc7:r4") shouldBe false - hasReachingDef(reopened, "d24-defuse-unrelated-register-negative", "root@pc4:r2", "root@pc9:r6") shouldBe false + hasReachingDef(reopened, "local-value-flow-generic", "root.1:r0", "root.1@pc6:r3") shouldBe false + hasReachingDef( + reopened, + "d24-defuse-unrelated-register-negative", + "root@pc4:r2", + "root@pc9:r6" + ) shouldBe false hasReachingDef(reopened, "d24-table-dynamic-key-negative", "root@pc3:r2", "root@pc4:r3") shouldBe false hasReachingDef(reopened, "d24-table-dynamic-key-negative", "root@pc3:r2", "root@pc5:r4") shouldBe false hasReachingDef(reopened, "d24-global-dynamic-env-negative", "root@pc3:r1", "root@pc4:r2") shouldBe false @@ -44,7 +52,13 @@ class IntraproceduralSemanticsSmokeTest extends AnyWordSpec with Matchers { "root.0@pc3:r3" ) shouldBe true hasReachingDef(reopened, "bc-table-global-upvalue", "root.0@pc6:r3", "root.0@pc8:r2") shouldBe true - hasSemanticEdge(reopened, "bc-table-global-upvalue", "upvalue:root.0:u0", "root.0@pc4:r4", "root.0@pc4:r4") shouldBe true + hasSemanticEdge( + reopened, + "bc-table-global-upvalue", + "upvalue:root.0:u0", + "root.0@pc4:r4", + "root.0@pc4:r4" + ) shouldBe true reopened.call .nameExact("lua.calltarget.candidate") @@ -91,7 +105,12 @@ class IntraproceduralSemanticsSmokeTest extends AnyWordSpec with Matchers { sourceCode: String, sinkCode: String ): Boolean = - hasSemanticEdge(cpg, fixtureId, sourceCode, sourceCode, sinkCode) || transitiveReachingDef(cpg, fixtureId, sourceCode, sinkCode) + hasSemanticEdge(cpg, fixtureId, sourceCode, sourceCode, sinkCode) || transitiveReachingDef( + cpg, + fixtureId, + sourceCode, + sinkCode + ) private def hasSemanticEdge( cpg: io.shiftleft.codepropertygraph.generated.Cpg, @@ -100,13 +119,13 @@ class IntraproceduralSemanticsSmokeTest extends AnyWordSpec with Matchers { sourceCode: String, sinkCode: String ): Boolean = { - val fixtureIdentifiers = identifiersInFixture(cpg, fixtureId) - val sinkIds = fixtureIdentifiers + val fixtureNodes = semanticNodesInFixture(cpg, fixtureId) + val sinkIds = fixtureNodes .filter(_.code == sinkCode) .map(_.id) .toSet - fixtureIdentifiers + fixtureNodes .filter(_.code == sourceCode) .outE(EdgeTypes.REACHING_DEF) .filter(edge => Option(edge.property).contains(variable)) @@ -122,18 +141,18 @@ class IntraproceduralSemanticsSmokeTest extends AnyWordSpec with Matchers { sourceCode: String, sinkCode: String ): Boolean = { - val fixtureIdentifiers = identifiersInFixture(cpg, fixtureId) - val fixtureIdentifierCodes = fixtureIdentifiers + val fixtureNodes = semanticNodesInFixture(cpg, fixtureId) + val fixtureNodeCodes = fixtureNodes .map(identifier => identifier.id -> identifier.code) .toMap - val graph = fixtureIdentifiers + val graph = fixtureNodes .outE(EdgeTypes.REACHING_DEF) .flatMap { edge => val sourceNode = edge.src.asInstanceOf[StoredNode] val sinkNode = edge.dst.asInstanceOf[StoredNode] - val source = fixtureIdentifierCodes.get(sourceNode.id) - val sink = fixtureIdentifierCodes.get(sinkNode.id) + val source = fixtureNodeCodes.get(sourceNode.id) + val sink = fixtureNodeCodes.get(sinkNode.id) source.zip(sink).headOption } .foldLeft(Map.empty[String, Set[String]]) { case (acc, (source, sink)) => @@ -154,6 +173,11 @@ class IntraproceduralSemanticsSmokeTest extends AnyWordSpec with Matchers { false } - private def identifiersInFixture(cpg: io.shiftleft.codepropertygraph.generated.Cpg, fixtureId: String): List[Identifier] = - cpg.method.filename(s".*$fixtureId/input\\.luac").ast.isIdentifier.l + private def semanticNodesInFixture( + cpg: io.shiftleft.codepropertygraph.generated.Cpg, + fixtureId: String + ): List[CfgNode] = + cpg.method.filename(s".*$fixtureId/input\\.luac").parameter.l ++ + cpg.method.filename(s".*$fixtureId/input\\.luac").ast.isIdentifier.l + } From af470d8d65eee42cf6aca06b4963b2a4adc748b5 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Fri, 17 Jul 2026 21:22:09 -0400 Subject: [PATCH 094/105] fix(lua2cpg): derive interprocedural bridges --- .../bridge-flow-generic/bridge.lua | 7 +++++++ .../bridge-flow-generic/bridge.luac | Bin 0 -> 225 bytes .../bridge-flow-generic/controller.lua | 5 +++++ .../bridge-flow-generic/controller.luac | Bin 0 -> 422 bytes .../InterproceduralModuleTaintSmokeTest.scala | 12 ++++++++++++ 5 files changed, 24 insertions(+) create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/bridge-flow-generic/bridge.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/bridge-flow-generic/bridge.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/bridge-flow-generic/controller.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/bridge-flow-generic/controller.luac diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/bridge-flow-generic/bridge.lua b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/bridge-flow-generic/bridge.lua new file mode 100644 index 000000000000..9ac9c421a500 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/bridge-flow-generic/bridge.lua @@ -0,0 +1,7 @@ +local M = {} + +function M.forward(value) + return value +end + +return M diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/bridge-flow-generic/bridge.luac b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/bridge-flow-generic/bridge.luac new file mode 100644 index 0000000000000000000000000000000000000000..bcbfe34a0185e593ec7bf91a1405ff6be2e6c688 GIT binary patch literal 225 zcmXwzT?&IR5QHabYHjfb1&`nvJb-=Z5mI7{AP9lzt4HsQi35|}{q~1EhveTdu68vV zWc8moh2PlC$rqwv?UnC?4)ndp-U6n@4#S%3S>GjYK86DpgbNcb6I#n~E(%Fvj@FEh cRkXDFIgLjqDxDLGx_T>CUst48{!lLP2ayI2JOBUy literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/bridge-flow-generic/controller.lua b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/bridge-flow-generic/controller.lua new file mode 100644 index 000000000000..c33b912646c4 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/bridge-flow-generic/controller.lua @@ -0,0 +1,5 @@ +local bridge = require("bridge") +local command = luci.http.formvalue("cmd") +local transformed = bridge.forward(command) + +return os.execute(transformed) diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/bridge-flow-generic/controller.luac b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/bridge-flow-generic/controller.luac new file mode 100644 index 0000000000000000000000000000000000000000..1764e5110782fe7675a3e31cc85fa6c471f84409 GIT binary patch literal 422 zcmZuu!BWC75Pg9#f_Tug44=R^l!KhOaQ70^2%SkA42f_o7r(-fsm|zM=(o5_+o{8_ znU~GJm+WSf`(;!s2sxBP+@iKbyCQgJt?@G(Rl~CD4en?j5atOmDIp$8U{O_A-~-1* z;E}kua*hfm-f8_+34G&IfW{9Xe(CI#v=MnKS(`U6G4lrUsv(|ymaZx0N^Cajtcx}w zY$2~}3D3^wugXT+E*nbcX_uVzz4Ao#8`bSP(HxA9!JHIsP5eWT*5}|HE% d16-rf-interprocedural-formvalue-execute/input.luac:root@pc15:r2" ) + markerCodes(reopened, "lua.interproc.arg_flow") + .exists(code => + code.contains("bridge-flow-generic/controller.luac") && code.contains("bridge.luac:root.0:r0") + ) shouldBe true + markerCodes(reopened, "lua.interproc.return_flow") + .exists(code => + code.contains("bridge-flow-generic/bridge.luac::root.0") && code.contains("controller.luac") + ) shouldBe true markerCodes(reopened, "lua.module.resolution") should contain( "d16-rf-webcmd-cross-module-popen/controller.luac require mtkwifi -> matched:d16-rf-webcmd-cross-module-popen/mtkwifi.luac" ) @@ -53,6 +61,10 @@ class InterproceduralModuleTaintSmokeTest extends AnyWordSpec with Matchers { markerCodes(reopened, "lua.taint.path") should contain( "bc-taint-minimal-path/input.luac:root@pc3:r2 -> bc-taint-minimal-path/input.luac:root@pc6:r4 via bc-taint-minimal-path/input.luac:root@pc3:r2;bc-taint-minimal-path/input.luac:root@pc5:r4;bc-taint-minimal-path/input.luac:root@pc6:r4" ) + val genericBridgePaths = markerCodes(reopened, "lua.taint.path").filter(_.contains("bridge-flow-generic")) + withClue(s"generic bridge paths: ${genericBridgePaths.mkString(", ")}") { + genericBridgePaths.exists(_.contains("bridge-flow-generic/bridge.luac")) shouldBe true + } val unresolvedArgFlows = markerCodes(reopened, "lua.interproc.arg_flow") .filter(_.contains("d24-interproc-unresolved-callee-negative")) From 82001b3954b04a2901aeea4478d74d0cbad33479 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Fri, 17 Jul 2026 21:25:05 -0400 Subject: [PATCH 095/105] fix(lua2cpg): classify sanitizers from resolved calls --- .../nested/controller.lua | 5 +++++ .../nested/controller.luac | Bin 0 -> 432 bytes .../vendor/formatter.lua | 7 +++++++ .../vendor/formatter.luac | Bin 0 -> 231 bytes .../sanitizer-rule-lookalike/controller.lua | 5 +++++ .../sanitizer-rule-lookalike/controller.luac | Bin 0 -> 411 bytes .../RulesSanitizerReportSmokeTest.scala | 19 +++++++++++++++++- 7 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/sanitizer-rule-generic/nested/controller.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/sanitizer-rule-generic/nested/controller.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/sanitizer-rule-generic/vendor/formatter.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/sanitizer-rule-generic/vendor/formatter.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/sanitizer-rule-lookalike/controller.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/sanitizer-rule-lookalike/controller.luac diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/sanitizer-rule-generic/nested/controller.lua b/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/sanitizer-rule-generic/nested/controller.lua new file mode 100644 index 000000000000..1782c7d88f13 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/sanitizer-rule-generic/nested/controller.lua @@ -0,0 +1,5 @@ +local formatter = require("formatter") +local command = luci.http.formvalue("cmd") +local safe_command = formatter._cmdformat(command) + +return os.execute(safe_command) diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/sanitizer-rule-generic/nested/controller.luac b/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/sanitizer-rule-generic/nested/controller.luac new file mode 100644 index 0000000000000000000000000000000000000000..15a00943e5a2c11a6e9f58dba135df39bb14c664 GIT binary patch literal 432 zcmZuuL2d#u44goapa`iwgLp#UupHPE7w(l%6oNp?5}HjA$L*!B=wr5($}iftV6(dd zN*zsXj~zQshO?;V5V9+~ctjcdKyif@*?++`8u0!81IO>j eVV0k{nOwUpQ`_a)XMR9n$<@j%njnwS{LKmfMLPum literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/sanitizer-rule-generic/vendor/formatter.lua b/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/sanitizer-rule-generic/vendor/formatter.lua new file mode 100644 index 000000000000..6352b21f0819 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/sanitizer-rule-generic/vendor/formatter.lua @@ -0,0 +1,7 @@ +local M = {} + +function M._cmdformat(value) + return value +end + +return M diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/sanitizer-rule-generic/vendor/formatter.luac b/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/sanitizer-rule-generic/vendor/formatter.luac new file mode 100644 index 0000000000000000000000000000000000000000..5fffef7aad4cec64e80d93879bbab669ed94843b GIT binary patch literal 231 zcmXv|+YN&-5OdPBygoWWB}V8B3_w3RK@|c00#!nUZ$_`pfhC{soI5LzHpMqw$U}}h z49)j9PjOj>`8lR|m=f==tR>l#MT4ydT#XHmbEsH^PdD`zvHNBM7dE=5={@DVl_kgv fq;`~dBZW1NY1lit!nq+;YIacXN)cZ6CmZkwe{&Gf literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/sanitizer-rule-lookalike/controller.lua b/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/sanitizer-rule-lookalike/controller.lua new file mode 100644 index 000000000000..c6053fa6fa8c --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/sanitizer-rule-lookalike/controller.lua @@ -0,0 +1,5 @@ +local formatter = unknown_provider() +local command = luci.http.formvalue("cmd") +local unchanged = formatter._cmdformat(command) + +return os.execute(unchanged) diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/sanitizer-rule-lookalike/controller.luac b/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/sanitizer-rule-lookalike/controller.luac new file mode 100644 index 0000000000000000000000000000000000000000..e9261e2439418b0c50f45254becfb1dffa98bf8e GIT binary patch literal 411 zcmZusF-`+95F7_boI(NAAfCV*E(NJ*=#e7JXGnz4j*J~BO~WgAj49w3@D_G+J`o{C z8n0((ckMm9PU;3iHnPD92DI9{7`(M6T-u~+mcwMqHB;hw&bVrUMJ`yBf)@zfW{B5z zj)IIgx_@MW&+;zF5?_G$roIdE8jA9i4n}eh&Of>3Civ~TGXavvGi=i9U$u&H134Qk z-1*RNl}!dx)|Bo$$fGKjq_{>!$b*{mkDw(7m}jF?G&{j7iOLDZzvTXr{I~ze{{G)o hjI;_*Iwp> d24-sanitizer-suppresses-report/input.luac:root@pc20:r2", "d24-sanitizer-same-suffix-off-chain-negative/input.luac:root@pc21 tonumber -> d24-sanitizer-same-suffix-off-chain-negative/input.luac:root@pc21:r3" ) + val genericSanitizerCalls = markerCodes(reopened, "lua.sanitizer.call") + .filter(_.contains("sanitizer-rule-generic/nested/controller.luac")) + withClue(s"generic sanitizer calls: ${genericSanitizerCalls.mkString(", ")}") { + genericSanitizerCalls.exists(code => code.contains("_cmdformat") && code.contains("->")) shouldBe true + } markerCodes(reopened, "lua.sanitizer.classification") should contain allOf ( "d24-sanitizer-suppresses-report/input.luac:root@pc17:r1 -> d24-sanitizer-suppresses-report/input.luac:root@pc24:r4 classification=sanitized sanitizer=tonumber", "d24-sanitizer-same-suffix-off-chain-negative/input.luac:root@pc17:r1 -> d24-sanitizer-same-suffix-off-chain-negative/input.luac:root@pc25:r4 classification=not-sanitized sanitizer=tonumber" @@ -68,16 +73,28 @@ class RulesSanitizerReportSmokeTest extends AnyWordSpec with Matchers { ruleCodes.exists(_.contains("executex")) shouldBe false markerCodes(reopened, "lua.sink.endpoint") .exists(_.contains("d24-rules-overmatch-constant-sink-negative")) shouldBe false + markerCodes(reopened, "lua.sanitizer.call") + .exists(_.contains("sanitizer-rule-lookalike")) shouldBe false markerCodes(reopened, "lua.report.vulnerability") .exists(_.contains("d24-sanitizer-suppresses-report")) shouldBe false + markerCodes(reopened, "lua.report.classification") + .exists(code => + code.contains("sanitizer-rule-generic") && code.contains("classification=sanitized") + ) shouldBe true + markerCodes(reopened, "lua.report.vulnerability") + .exists(_.contains("sanitizer-rule-generic")) shouldBe false + markerCodes(reopened, "lua.report.vulnerability") + .exists(_.contains("sanitizer-rule-lookalike")) shouldBe true markerCodes(reopened, "lua.report.vulnerability") .exists(_.contains("d24-report-no-report-without-path-negative")) shouldBe false markerCodes(reopened, "lua.report.vulnerability") .exists(code => code.contains("bc-kill-overwrite") || code.contains("bc-branch-negative")) shouldBe false val e5NodeCount = reopened.call - .name("lua\\.(rule\\.match|source\\.endpoint|sink\\.endpoint|sanitizer\\.call|sanitizer\\.classification|report\\.classification|report\\.vulnerability|e5\\.boundary)") + .name( + "lua\\.(rule\\.match|source\\.endpoint|sink\\.endpoint|sanitizer\\.call|sanitizer\\.classification|report\\.classification|report\\.vulnerability|e5\\.boundary)" + ) .size val reportCount = reopened.call.nameExact("lua.report.vulnerability").size info(s"e5_node_count=$e5NodeCount") From 6cde94400e09e6bb350fc08ee06e306d1305cfe4 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Fri, 17 Jul 2026 21:35:58 -0400 Subject: [PATCH 096/105] test(lua2cpg): remove fixture-driven semantics --- .../bc-taint-minimal-path/input.luac | Bin 398 -> 261 bytes .../bc-taint-minimal-path/source.lua | 2 + .../bc-taint-minimal-path/input.luac | Bin 398 -> 261 bytes .../bc-taint-minimal-path/source.lua | 2 + .../input.luac | Bin 762 -> 605 bytes .../source.lua | 16 ++++++ .../input.luac | Bin 730 -> 586 bytes .../source.lua | 15 ++++++ .../InterproceduralModuleTaintSmokeTest.scala | 6 +-- .../IntraproceduralSemanticsSmokeTest.scala | 47 +++++++++++++----- .../RealFirmwareEvidenceExportSmokeTest.scala | 4 +- .../RulesSanitizerReportSmokeTest.scala | 27 ++++------ 12 files changed, 82 insertions(+), 37 deletions(-) create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/bc-taint-minimal-path/source.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/bc-taint-minimal-path/source.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/d24-sanitizer-same-suffix-off-chain-negative/source.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/d24-sanitizer-suppresses-report/source.lua diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/bc-taint-minimal-path/input.luac b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/bc-taint-minimal-path/input.luac index 71021c22082e4152557d773429eff344edeacbac..39a8a1b6f4e9c620bfd0062dc293ade9e812e114 100644 GIT binary patch literal 261 zcmZvWK?=e!5Ji957^-*!#UprzxX_gw_ijRnC`4M?jJV9v^k&YOf+#-t$^6OWGmAaB z1C(xbgEVm^Qg|ClP=8c$?^iPS9WT30S;7m=EF1X% literal 398 zcmYjNOAdlC6nsTc^a>`f+#>M?9Kel7s8C}gprNf{$w54f2k|hzX=&ml?>C+INw+5_ z&m_`3&FM%1l~u-?@~TJc24%`iU0A7mTXeeDootJzw71ec+)%4S>*U;qkYqXgJzG-| zO&s6TL?2{=#J3``^DHa*Ekbaz2t|U3ddC4;NKlC4veat0Ro E0g#0zMgRZ+ diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/bc-taint-minimal-path/source.lua b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/bc-taint-minimal-path/source.lua new file mode 100644 index 000000000000..5442625f707f --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/bc-taint-minimal-path/source.lua @@ -0,0 +1,2 @@ +local value = luci.http.formvalue("value") +os.execute(value) diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/bc-taint-minimal-path/input.luac b/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/bc-taint-minimal-path/input.luac index 71021c22082e4152557d773429eff344edeacbac..39a8a1b6f4e9c620bfd0062dc293ade9e812e114 100644 GIT binary patch literal 261 zcmZvWK?=e!5Ji957^-*!#UprzxX_gw_ijRnC`4M?jJV9v^k&YOf+#-t$^6OWGmAaB z1C(xbgEVm^Qg|ClP=8c$?^iPS9WT30S;7m=EF1X% literal 398 zcmYjNOAdlC6nsTc^a>`f+#>M?9Kel7s8C}gprNf{$w54f2k|hzX=&ml?>C+INw+5_ z&m_`3&FM%1l~u-?@~TJc24%`iU0A7mTXeeDootJzw71ec+)%4S>*U;qkYqXgJzG-| zO&s6TL?2{=#J3``^DHa*Ekbaz2t|U3ddC4;NKlC4veat0Ro E0g#0zMgRZ+ diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/bc-taint-minimal-path/source.lua b/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/bc-taint-minimal-path/source.lua new file mode 100644 index 000000000000..5442625f707f --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/bc-taint-minimal-path/source.lua @@ -0,0 +1,2 @@ +local value = luci.http.formvalue("value") +os.execute(value) diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/d24-sanitizer-same-suffix-off-chain-negative/input.luac b/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/d24-sanitizer-same-suffix-off-chain-negative/input.luac index d22ecdb40c76ce779da86225485bd9131b713023..980a55dda0a5ab68d94428a9402abe1dcb511148 100644 GIT binary patch delta 289 zcmZvVJqp4=5QX0?n;7H2qF@v)yoSZ5un=tQZK9?KY6OX`1VQ_d*2Cl)7T!awoK*`E zAAHQ4kAZm@H`(k=_TCgEEoq^McT_TM3F#!kU~DU3$>bG|xePG)!c+=otl%m_HwL*e z;gk_F#JDKXiSN#EiN|a2vA2@zsv+}ibfRsz-^~3TL^`6GVX?B)JM|5HV5eQtCnp_> u!6B#j4+Wm3Ka|;3zNm8lMX=LfbRsVZBY%k+_cmi{$yuy@FGQW2wmMIZTN}dY+j#&TP)`#s;?#UeD{{617O9w9(@@nHyUwJ&sP#d>!P8NuE?mR;GMyV+#94 z9Q)xkNOC_{4}nQ$YOD)ehUz4>L6ePDr#qx`Ac1Sred*i==`LYiN<VyPnyx0^ehadq zntkGl7-1Gppb8Ku$EXxtD4qM@fba*jAD_t4o!5!Hq7pyWeB0bjw# zuyYR)A(TChcRjOSd+j^9bHP14zvFjsiOQ7K#+u70k`72swg(Zw*~7;AuKt}PnGC`_X4;n@Or%f zt^kbG0;6Jqx$^K(z!Mn@6nK;PQ*yrW;X%G{1+Opsy68TO)2415{m!1O&EWSc36nIB zpM$B>@YiZ8HIU{j(=$DGRz$xh#&L)ykrd-{9}lgT#C=o;phhFdwaoEqFAhl%YxetbQ!NG$kQ>01=%UBYJw5jW!sX;4v7^Nx9c3PU2e5n#u4EAQC|kzZ?1rzr`i;7 zKVny~_Yd}kYj7a(Uo^Euf=v?_KkB?Jek4TmI5vn9tHZJo`Q}ZVe>23PuvBP2#Y|02 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/d24-sanitizer-suppresses-report/source.lua b/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/d24-sanitizer-suppresses-report/source.lua new file mode 100644 index 000000000000..00fb754f1934 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/d24-sanitizer-suppresses-report/source.lua @@ -0,0 +1,15 @@ +luci = { http = {} } + +function luci.http.formvalue(name) + return name +end + +os = { + execute = function(cmd) + return cmd + end +} + +local tainted = luci.http.formvalue("cmd") +local sanitized = tonumber(tainted) +os.execute(sanitized) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/InterproceduralModuleTaintSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/InterproceduralModuleTaintSmokeTest.scala index ea2d3dd99a3a..878eadf285fd 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/InterproceduralModuleTaintSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/InterproceduralModuleTaintSmokeTest.scala @@ -59,7 +59,7 @@ class InterproceduralModuleTaintSmokeTest extends AnyWordSpec with Matchers { "d16-rf-webcmd-cross-module-popen/controller.luac:root.1@pc8 -> d16-rf-webcmd-cross-module-popen/mtkwifi.luac::root.1" ) markerCodes(reopened, "lua.taint.path") should contain( - "bc-taint-minimal-path/input.luac:root@pc3:r2 -> bc-taint-minimal-path/input.luac:root@pc6:r4 via bc-taint-minimal-path/input.luac:root@pc3:r2;bc-taint-minimal-path/input.luac:root@pc5:r4;bc-taint-minimal-path/input.luac:root@pc6:r4" + "bc-taint-minimal-path/input.luac:root@pc4:r0 -> bc-taint-minimal-path/input.luac:root@pc8:r2 via bc-taint-minimal-path/input.luac:root@pc4:r0;bc-taint-minimal-path/input.luac:root@pc7:r0;bc-taint-minimal-path/input.luac:root@pc7:r2;bc-taint-minimal-path/input.luac:root@pc8:r2" ) val genericBridgePaths = markerCodes(reopened, "lua.taint.path").filter(_.contains("bridge-flow-generic")) withClue(s"generic bridge paths: ${genericBridgePaths.mkString(", ")}") { @@ -89,9 +89,7 @@ class InterproceduralModuleTaintSmokeTest extends AnyWordSpec with Matchers { "d24-interproc-unresolved-callee-negative/input.luac:root.2@pc2 reason=unresolved-callee", "d24-module-ambiguous-unresolved-dynamic-negative/missing.luac:require:missing.module reason=unresolved-module", "d24-module-ambiguous-unresolved-dynamic-negative/controller.luac:require:dynamic reason=dynamic-require", - "d24-module-missing-field-negative/controller.luac:root.0@pc3 reason=missing-export-field", - "bc-kill-overwrite/input.luac:root@pc3:r2->root@pc7:r4 reason=killed-taint-path", - "bc-branch-negative/input.luac:root@pc3:r2->root@pc8:r5 reason=branch-negative-taint-path" + "d24-module-missing-field-negative/controller.luac:root.0@pc3 reason=missing-export-field" ) val e4NodeCount = reopened.call diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/IntraproceduralSemanticsSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/IntraproceduralSemanticsSmokeTest.scala index 50b9273528af..b09fe98d06e0 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/IntraproceduralSemanticsSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/IntraproceduralSemanticsSmokeTest.scala @@ -8,7 +8,7 @@ import io.shiftleft.semanticcpg.utils.FileUtil import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec -import java.nio.file.Paths +import java.nio.file.{Files, Paths} class IntraproceduralSemanticsSmokeTest extends AnyWordSpec with Matchers { @@ -73,19 +73,10 @@ class IntraproceduralSemanticsSmokeTest extends AnyWordSpec with Matchers { .code(".*source-function-name.*") .isEmpty shouldBe true - val expectedBoundaries = Seq( - "bc-kill-overwrite:no-tainted-source-after-overwrite", - "d24-defuse-unrelated-register-negative:no-defuse-cross-prototype-register-reuse", - "d24-table-dynamic-key-negative:no-table-field-flow-dynamic-key", - "d24-table-dynamic-key-negative:no-table-field-flow-missing-field", - "d24-global-dynamic-env-negative:no-global-flow-dynamic-env-write", - "d24-global-dynamic-env-negative:no-global-flow-missing-precise-name", - "d24-upvalue-mutation-negative:no-stale-upvalue-reuse-after-setupval", - "d24-upvalue-mutation-negative:upvalue-mutation-boundary", - "bc-call-candidate-unresolved:no-guessed-source-target" - ) val actualBoundaries = reopened.call.nameExact("lua.semantic.boundary").code.l.toSet - expectedBoundaries.diff(actualBoundaries.toSeq).toList.shouldBe(Nil) + actualBoundaries.exists(_.startsWith("no-stale-upvalue-reuse-after-setupval:")) shouldBe true + actualBoundaries.exists(_.startsWith("upvalue-mutation-boundary:")) shouldBe true + actualBoundaries.size shouldBe 2 val nodeCount = reopened.graph.allNodes.size val reachingDefCount = reopened.identifier.outE(EdgeTypes.REACHING_DEF).size @@ -97,6 +88,36 @@ class IntraproceduralSemanticsSmokeTest extends AnyWordSpec with Matchers { } } } + + "preserve positive and negative flow semantics under neutral fixture names" in { + val resourceRoot = Paths.get(getClass.getClassLoader.getResource("intraprocedural-semantics").toURI) + + FileUtil.usingTemporaryDirectory("lua2cpg-renamed-semantics-smoke") { tmpDir => + val inputRoot = tmpDir.resolve("input") + val positiveDir = inputRoot.resolve("alpha") + val negativeDir = inputRoot.resolve("beta") + Files.createDirectories(positiveDir) + Files.createDirectories(negativeDir) + Files.copy(resourceRoot.resolve("local-value-flow-generic/input.luac"), positiveDir.resolve("input.luac")) + Files.copy(resourceRoot.resolve("bc-kill-overwrite/input.luac"), negativeDir.resolve("input.luac")) + + val outputPath = tmpDir.resolve("renamed-semantics.cpg.bin").toString + val cpg = new Lua2Cpg() + .createCpg(Config().withInputPath(inputRoot.toString).withOutputPath(outputPath)) + .get + cpg.close() + + val reopened = CpgLoader.load(outputPath) + try { + hasReachingDef(reopened, "alpha", "root.0:r0", "root.0@pc6:r4") shouldBe true + hasReachingDef(reopened, "alpha", "root.1:r0", "root.1@pc6:r3") shouldBe false + hasReachingDef(reopened, "beta", "root@pc4:r2", "root@pc7:r4") shouldBe true + hasReachingDef(reopened, "beta", "root@pc3:r2", "root@pc7:r4") shouldBe false + } finally { + reopened.close() + } + } + } } private def hasReachingDef( diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 6adc2709f08f..c5898bd9a502 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -37,13 +37,13 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { val callRows = staging("call_name_resolution").arr.map(_.obj) callRows.exists(row => row("module_path").str.endsWith("d24-sanitizer-suppresses-report/input.luac") && - hasScopedCallsite(row, "root@pc20") && + hasScopedCallsite(row, "root@pc19") && row("resolved_name").str == "tonumber" ) shouldBe true val pathRows = staging("path_evidence").arr.map(_.obj) pathRows.exists(row => - row("path_steps").arr.exists(_.str.endsWith("d24-sanitizer-suppresses-report/input.luac::root@pc20:r2")) + row("path_steps").arr.exists(_.str.endsWith("d24-sanitizer-suppresses-report/input.luac::root@pc19:r1")) ) shouldBe true } } diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RulesSanitizerReportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RulesSanitizerReportSmokeTest.scala index 0bdd80b30abd..b218fa24d054 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RulesSanitizerReportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RulesSanitizerReportSmokeTest.scala @@ -38,8 +38,8 @@ class RulesSanitizerReportSmokeTest extends AnyWordSpec with Matchers { ) markerCodes(reopened, "lua.sanitizer.call") should contain allOf ( - "d24-sanitizer-suppresses-report/input.luac:root@pc20 tonumber -> d24-sanitizer-suppresses-report/input.luac:root@pc20:r2", - "d24-sanitizer-same-suffix-off-chain-negative/input.luac:root@pc21 tonumber -> d24-sanitizer-same-suffix-off-chain-negative/input.luac:root@pc21:r3" + "d24-sanitizer-suppresses-report/input.luac:root@pc19 tonumber -> d24-sanitizer-suppresses-report/input.luac:root@pc19:r1", + "d24-sanitizer-same-suffix-off-chain-negative/input.luac:root@pc20 tonumber -> d24-sanitizer-same-suffix-off-chain-negative/input.luac:root@pc20:r2" ) val genericSanitizerCalls = markerCodes(reopened, "lua.sanitizer.call") .filter(_.contains("sanitizer-rule-generic/nested/controller.luac")) @@ -47,27 +47,18 @@ class RulesSanitizerReportSmokeTest extends AnyWordSpec with Matchers { genericSanitizerCalls.exists(code => code.contains("_cmdformat") && code.contains("->")) shouldBe true } markerCodes(reopened, "lua.sanitizer.classification") should contain allOf ( - "d24-sanitizer-suppresses-report/input.luac:root@pc17:r1 -> d24-sanitizer-suppresses-report/input.luac:root@pc24:r4 classification=sanitized sanitizer=tonumber", - "d24-sanitizer-same-suffix-off-chain-negative/input.luac:root@pc17:r1 -> d24-sanitizer-same-suffix-off-chain-negative/input.luac:root@pc25:r4 classification=not-sanitized sanitizer=tonumber" + "d24-sanitizer-suppresses-report/input.luac:root@pc16:r0 -> d24-sanitizer-suppresses-report/input.luac:root@pc23:r3 classification=sanitized sanitizer=tonumber", + "d24-sanitizer-same-suffix-off-chain-negative/input.luac:root@pc16:r0 -> d24-sanitizer-same-suffix-off-chain-negative/input.luac:root@pc24:r3 classification=not-sanitized sanitizer=tonumber" ) markerCodes(reopened, "lua.report.classification") should contain allOf ( - "d24-sanitizer-suppresses-report/input.luac:root@pc17:r1 -> d24-sanitizer-suppresses-report/input.luac:root@pc24:r4 classification=sanitized reason=on-chain-sanitizer", - "d24-sanitizer-same-suffix-off-chain-negative/input.luac:root@pc17:r1 -> d24-sanitizer-same-suffix-off-chain-negative/input.luac:root@pc25:r4 classification=true-positive reason=no-on-chain-sanitizer" + "d24-sanitizer-suppresses-report/input.luac:root@pc16:r0 -> d24-sanitizer-suppresses-report/input.luac:root@pc23:r3 classification=sanitized reason=on-chain-sanitizer", + "d24-sanitizer-same-suffix-off-chain-negative/input.luac:root@pc16:r0 -> d24-sanitizer-same-suffix-off-chain-negative/input.luac:root@pc24:r3 classification=true-positive reason=no-on-chain-sanitizer" ) markerCodes(reopened, "lua.report.vulnerability") should contain allOf ( - "d16-rf-formvalue-os-execute-chain/input.luac:root@pc16:r0 -> d16-rf-formvalue-os-execute-chain/input.luac:root@pc20:r2 status=path-proven classification=true-positive path=d16-rf-formvalue-os-execute-chain/input.luac:root@pc16:r0;d16-rf-formvalue-os-execute-chain/input.luac:root@pc19:r2;d16-rf-formvalue-os-execute-chain/input.luac:root@pc20:r2", - "d16-rf-submit-dpp-uri-execute/input.luac:root.2@pc4:r0 -> d16-rf-submit-dpp-uri-execute/input.luac:root.2@pc8:r2 status=path-proven classification=true-positive path=d16-rf-submit-dpp-uri-execute/input.luac:root.2@pc4:r0;d16-rf-submit-dpp-uri-execute/input.luac:root.2@pc7:r2;d16-rf-submit-dpp-uri-execute/input.luac:root.2@pc8:r2", - "d16-rf-webcmd-cross-module-popen/controller.luac:root.1@pc4:r0 -> d16-rf-webcmd-cross-module-popen/mtkwifi.luac:root.1@pc3:r2 status=path-proven classification=true-positive path=d16-rf-webcmd-cross-module-popen/controller.luac:root.1@pc4:r0;d16-rf-webcmd-cross-module-popen/controller.luac:root.1@pc8:r2;d16-rf-webcmd-cross-module-popen/mtkwifi.luac:root.1@pc3:r2" + "d16-rf-formvalue-os-execute-chain/input.luac:root@pc16:r0 -> d16-rf-formvalue-os-execute-chain/input.luac:root@pc20:r2 status=path-proven classification=true-positive path=d16-rf-formvalue-os-execute-chain/input.luac:root@pc16:r0;d16-rf-formvalue-os-execute-chain/input.luac:root@pc19:r0;d16-rf-formvalue-os-execute-chain/input.luac:root@pc19:r2;d16-rf-formvalue-os-execute-chain/input.luac:root@pc20:r2", + "d16-rf-submit-dpp-uri-execute/input.luac:root.2@pc4:r0 -> d16-rf-submit-dpp-uri-execute/input.luac:root.2@pc8:r2 status=path-proven classification=true-positive path=d16-rf-submit-dpp-uri-execute/input.luac:root.2@pc4:r0;d16-rf-submit-dpp-uri-execute/input.luac:root.2@pc7:r0;d16-rf-submit-dpp-uri-execute/input.luac:root.2@pc7:r2;d16-rf-submit-dpp-uri-execute/input.luac:root.2@pc8:r2", + "d16-rf-webcmd-cross-module-popen/controller.luac:root.1@pc4:r0 -> d16-rf-webcmd-cross-module-popen/mtkwifi.luac:root.1@pc3:r2 status=path-proven classification=true-positive path=d16-rf-webcmd-cross-module-popen/controller.luac:root.1@pc4:r0;d16-rf-webcmd-cross-module-popen/controller.luac:root.1@pc7:r0;d16-rf-webcmd-cross-module-popen/controller.luac:root.1@pc7:r2;d16-rf-webcmd-cross-module-popen/controller.luac:root.1@pc8:r2;d16-rf-webcmd-cross-module-popen/mtkwifi.luac:root.1:r0;d16-rf-webcmd-cross-module-popen/mtkwifi.luac:root.1@pc2:r0;d16-rf-webcmd-cross-module-popen/mtkwifi.luac:root.1@pc2:r2;d16-rf-webcmd-cross-module-popen/mtkwifi.luac:root.1@pc3:r2" ) - markerCodes(reopened, "lua.e5.boundary") should contain allOf ( - "d24-rules-overmatch-constant-sink-negative/input.luac:root@pc4 reason=rule-overmatch-rejected", - "d24-rules-overmatch-constant-sink-negative/input.luac:root@pc8 reason=rule-overmatch-rejected", - "d24-rules-overmatch-constant-sink-negative/input.luac:root@pc12 reason=fixed-string-sink-suppressed", - "d24-report-no-report-without-path-negative/input.luac:source-to-sink reason=endpoint-only-no-path", - "bc-kill-overwrite/input.luac:root@pc3:r2->root@pc7:r4 reason=killed-taint-path", - "bc-branch-negative/input.luac:root@pc3:r2->root@pc8:r5 reason=branch-negative-taint-path" - ) - val ruleCodes = markerCodes(reopened, "lua.rule.match") ruleCodes.exists(_.contains("formvaluex")) shouldBe false ruleCodes.exists(_.contains("executex")) shouldBe false From ec7a540539191743677aff290e261c07fa4dcc24 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Fri, 17 Jul 2026 21:41:35 -0400 Subject: [PATCH 097/105] fix(lua2cpg): require real vulnerability endpoints --- .../RealFirmwareEvidenceExportSmokeTest.scala | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index c5898bd9a502..7d6625c2ff4e 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -1,5 +1,13 @@ package io.joern.lua2cpg +import io.joern.lua2cpg.bytecode.{ + LuaPairPerformanceProfile, + LuaPathSearchStats, + LuaPerformanceAttribution, + LuaProgramSemantics, + LuaRealFirmwareEvidenceExporter, + LuaTaintPath +} import io.shiftleft.semanticcpg.utils.FileUtil import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec @@ -48,6 +56,96 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { } } + "reject fixture paths without real endpoints before creating output" in { + val counterNames = Vector( + "source_reachability_check_count", + "source_reachability_accepted_count", + "prototype_unreachable_pair_count", + "source_specific_provenance_pruned_pair_count", + "parameter_position_check_count", + "parameter_position_accepted_count", + "parameter_position_pruned_count", + "path_constructor_check_count", + "path_constructor_accepted_count", + "path_constructor_pruned_count", + "bridge_argument_provenance_candidate_count", + "bridge_candidate_pc_pruned_count", + "bridge_candidate_reachability_pruned_count", + "bridge_local_path_attempt_count", + "bridge_local_path_success_count", + "local_path_search_count", + "distinct_local_path_query_count", + "local_path_cache_hit_count", + "local_path_cache_miss_count", + "local_path_graph_build_count", + "local_path_graph_cache_hit_count", + "local_path_graph_cache_miss_count", + "bridge_path_cache_hit_count", + "bridge_path_cache_miss_count", + "targeted_search_node_visit_count", + "targeted_search_edge_visit_count", + "early_candidate_short_circuit_count", + "taint_path_count", + "report_count" + ) + val counters = counterNames.map(_ -> 0L).toMap ++ Map( + "source_reachability_check_count" -> 1L, + "source_reachability_accepted_count" -> 1L, + "parameter_position_check_count" -> 1L, + "parameter_position_accepted_count" -> 1L, + "path_constructor_check_count" -> 1L, + "path_constructor_accepted_count" -> 1L, + "local_path_search_count" -> 1L, + "distinct_local_path_query_count" -> 1L, + "local_path_cache_miss_count" -> 1L, + "taint_path_count" -> 1L + ) + val sourceRef = "bc-endpoint-contract/input.luac:root@pc1:r0" + val sinkRef = "bc-endpoint-contract/input.luac:root@pc2:r1" + val pair = LuaPairPerformanceProfile( + sourceRef, + sinkRef, + "bc-endpoint-contract/input.luac::root@pc1", + "bc-endpoint-contract/input.luac::root@pc2", + "luci.http.formvalue", + "os.execute", + counters + ) + val semantics = LuaProgramSemantics( + moduleResolutions = Vector.empty, + moduleReturnTables = Vector.empty, + moduleFieldCallTargets = Vector.empty, + interproceduralArgFlows = Vector.empty, + interproceduralReturnFlows = Vector.empty, + crossBoundaryCallTargets = Vector.empty, + taintPaths = Vector(LuaTaintPath(sourceRef, sinkRef, Vector(sourceRef, sinkRef), "true-positive", "bytecode-only")), + boundaries = Vector.empty, + ruleMatches = Vector.empty, + sourceEndpoints = Vector.empty, + sinkEndpoints = Vector.empty, + sanitizerCalls = Vector.empty, + sanitizerClassifications = Vector.empty, + reportClassifications = Vector.empty, + vulnerabilityReports = Vector.empty, + e5Boundaries = Vector.empty, + pathSearchStats = LuaPathSearchStats(0, 0, 1, 1, 1, 1, 0), + performanceAttribution = LuaPerformanceAttribution(1, 0, 1, 0, Vector(pair), counters) + ) + + FileUtil.usingTemporaryDirectory("lua2cpg-endpoint-contract") { tmpDir => + val exportDir = tmpDir.resolve("must-not-exist") + val error = intercept[IllegalStateException] { + LuaRealFirmwareEvidenceExporter.write( + Config(realFirmwareOutputDir = Some(exportDir.toString)), + Vector.empty, + semantics + ) + } + error.getMessage should include("taint path lacks source endpoint") + Files.exists(exportDir) shouldBe false + } + } + "export OpenWrt-derived source and sink endpoints" in { withOpenWrtDerivedExportDir { exportDir => val rows = stagingRows(exportDir) From 51afbb413dcbfd8bbab4e39ddad62339dd5a5da1 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Fri, 17 Jul 2026 21:44:22 -0400 Subject: [PATCH 098/105] fix(lua2cpg): preserve call context in taint bridges --- .../bytecode/LuaProgramSemantics.scala | 89 +++++++++++------- .../call-context-negative/input.lua | 13 +++ .../call-context-negative/input.luac | Bin 0 -> 812 bytes .../InterproceduralModuleTaintSmokeTest.scala | 5 + 4 files changed, 71 insertions(+), 36 deletions(-) create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/call-context-negative/input.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/call-context-negative/input.luac diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaProgramSemantics.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaProgramSemantics.scala index 436bf0fa932e..64448d7fe955 100644 --- a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaProgramSemantics.scala +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaProgramSemantics.scala @@ -962,26 +962,29 @@ object LuaProgramSemantics { val sinkPrototype = prototypeRef(sink.modulePath, sink.prototypeId) val distanceToSink = representativeDistancesToSink(sinkPrototype) val pending = - scala.collection.mutable.Queue((sourceEndpoint.sourceRef, Vector(sourceEndpoint.sourceRef), sourcePrototype, 0)) - val seen = scala.collection.mutable.Set(sourceEndpoint.sourceRef) + scala.collection.mutable.Queue( + (sourceEndpoint.sourceRef, Vector(sourceEndpoint.sourceRef), sourcePrototype, List.empty[String], 0) + ) + val seen = scala.collection.mutable.Set((sourceEndpoint.sourceRef, List.empty[String])) val maxDepth = 4 var found: Option[Vector[String]] = None while (pending.nonEmpty && found.isEmpty) { - val (currentRef, pathPrefix, currentPrototype, depth) = pending.dequeue() - val currentPc = pcFromAnyValueRef(currentRef) - val currentIsEntryParameter = isPrototypeEntryParameterRef(currentRef) - val currentDistance = distanceToSink.getOrElse(currentPrototype, Int.MaxValue) + val (currentRef, pathPrefix, currentPrototype, callContext, depth) = pending.dequeue() + val currentPc = pcFromAnyValueRef(currentRef) + val currentIsEntryParameter = isPrototypeEntryParameterRef(currentRef) + val currentDistance = distanceToSink.getOrElse(currentPrototype, Int.MaxValue) val candidateFlows = (if (depth == 0) argumentFlowsBySourcePrototype else representativeFlowsBySourcePrototype) .getOrElse(currentPrototype, Vector.empty) + .flatMap(flow => advanceCallContext(flow, callContext).map(flow -> _)) .filter { flow => attribution.increment("bridge_argument_provenance_candidate_count") val pcAccepted = - if (depth == 0) flow.callsitePc > source.pc - else if (!flow.isArgumentFlow) true + if (depth == 0) flow._1.callsitePc > source.pc + else if (!flow._1.isArgumentFlow) true else { - val flowPc = flow.callsitePc + val flowPc = flow._1.callsitePc currentPc match { case Some(pc) if flowPc >= pc => true case Some(_) => false @@ -1001,34 +1004,36 @@ object LuaProgramSemantics { .filter { flow => val reachable = if (depth == 0 && sourcePrototype == sinkPrototype) - flow.targetPrototype != sinkPrototype && distanceToSink.contains(flow.targetPrototype) - else distanceToSink.get(flow.targetPrototype).contains(currentDistance - 1) + flow._1.targetPrototype != sinkPrototype && distanceToSink.contains(flow._1.targetPrototype) + else distanceToSink.get(flow._1.targetPrototype).contains(currentDistance - 1) if (!reachable) { attribution.increment("bridge_candidate_reachability_pruned_count") attribution.increment("early_candidate_short_circuit_count") } reachable } - .sortBy(flow => (flow.callsitePc, flow.targetPrototype, flow.sortIndex)) + .sortBy { case (flow, _) => (flow.callsitePc, flow.targetPrototype, flow.sortIndex) } val reachableBridgePrefixes = reachableRepresentativeBridgePrefixes( pathSearch, currentRef, - candidateFlows, + candidateFlows.map(_._1), includeArgumentRepresentativeValues = depth > 0 ) - val candidateIterator = candidateFlows.filter(flow => reachableBridgePrefixes.contains(flow.fromRef)).iterator + val candidateIterator = candidateFlows.filter { case (flow, _) => + reachableBridgePrefixes.contains(flow.fromRef) + }.iterator while (candidateIterator.hasNext && found.isEmpty) { - val flow = candidateIterator.next() - val bridgePrefix = pathPrefix ++ reachableBridgePrefixes(flow.fromRef).drop(1) :+ flow.toRef + val (flow, nextCallContext) = candidateIterator.next() + val bridgePrefix = pathPrefix ++ reachableBridgePrefixes(flow.fromRef).drop(1) :+ flow.toRef if (flow.targetPrototype == sinkPrototype) { found = preferredLocalPathForBridgeFlow(pathSearch, flow, sinkEndpoint.sinkRef) .map(targetToSink => bridgePrefix ++ targetToSink.drop(1)) - } else if (depth < maxDepth && !seen(flow.toRef)) { - seen += flow.toRef - pending.enqueue((flow.toRef, bridgePrefix, flow.targetPrototype, depth + 1)) + } else if (depth < maxDepth && !seen((flow.toRef, nextCallContext))) { + seen += ((flow.toRef, nextCallContext)) + pending.enqueue((flow.toRef, bridgePrefix, flow.targetPrototype, nextCallContext, depth + 1)) } } } @@ -1036,6 +1041,15 @@ object LuaProgramSemantics { found } + private def advanceCallContext(flow: BridgeFlow, callContext: List[String]): Option[List[String]] = + if (flow.isArgumentFlow) Some(flow.callsiteId :: callContext) + else + callContext match { + case callsiteId :: tail if callsiteId == flow.callsiteId => Some(tail) + case Nil => Some(Nil) + case _ => None + } + private def reachableRepresentativeBridgePrefixes( pathSearch: LocalPathSearch, currentRef: String, @@ -1092,28 +1106,31 @@ object LuaProgramSemantics { sourceEndpoint.sourceRef, { val source = parseQualifiedValueRef(sourceEndpoint.sourceRef) val sourcePrototype = prototypeRef(source.modulePath, source.prototypeId) - val pending = scala.collection.mutable.Queue((sourceEndpoint.sourceRef, sourcePrototype, 0, false)) - val seen = scala.collection.mutable.Set((sourceEndpoint.sourceRef, sourcePrototype, 0, false)) - val reachable = scala.collection.mutable.Set.empty[String] - val maxDepth = 4 + val pending = + scala.collection.mutable.Queue((sourceEndpoint.sourceRef, sourcePrototype, List.empty[String], 0, false)) + val seen = + scala.collection.mutable.Set((sourceEndpoint.sourceRef, sourcePrototype, List.empty[String], 0, false)) + val reachable = scala.collection.mutable.Set.empty[String] + val maxDepth = 4 while (pending.nonEmpty) { - val (currentRef, currentPrototype, depth, usedReturnBridge) = pending.dequeue() - val currentPc = pcFromAnyValueRef(currentRef) - val currentIsEntryParameter = isPrototypeEntryParameterRef(currentRef) + val (currentRef, currentPrototype, callContext, depth, usedReturnBridge) = pending.dequeue() + val currentPc = pcFromAnyValueRef(currentRef) + val currentIsEntryParameter = isPrototypeEntryParameterRef(currentRef) val candidateFlows = (if (depth == 0) argumentFlowsBySourcePrototype else representativeFlowsBySourcePrototype) .getOrElse(currentPrototype, Vector.empty) + .flatMap(flow => advanceCallContext(flow, callContext).map(flow -> _)) .filter { flow => attribution.increment("bridge_argument_provenance_candidate_count") val pcAccepted = - if (depth == 0) flow.callsitePc > source.pc - else if (!flow.isArgumentFlow) true + if (depth == 0) flow._1.callsitePc > source.pc + else if (!flow._1.isArgumentFlow) true else { currentPc match { - case Some(pc) if flow.callsitePc >= pc => true - case Some(_) => false - case None if currentIsEntryParameter => true + case Some(pc) if flow._1.callsitePc >= pc => true + case Some(_) => false + case None if currentIsEntryParameter => true case None => throw new IllegalStateException( s"missing pc provenance for cross-module bridge ref: current_ref=$currentRef" @@ -1131,17 +1148,17 @@ object LuaProgramSemantics { reachableRepresentativeBridgePrefixes( pathSearch, currentRef, - candidateFlows, + candidateFlows.map(_._1), includeArgumentRepresentativeValues = depth > 0 ) candidateFlows - .filter(flow => reachableBridgePrefixes.contains(flow.fromRef)) - .sortBy(flow => (flow.callsitePc, flow.targetPrototype, flow.sortIndex)) - .foreach { flow => + .filter { case (flow, _) => reachableBridgePrefixes.contains(flow.fromRef) } + .sortBy { case (flow, _) => (flow.callsitePc, flow.targetPrototype, flow.sortIndex) } + .foreach { case (flow, nextCallContext) => val nextUsedReturnBridge = usedReturnBridge || !flow.isArgumentFlow if (nextUsedReturnBridge) reachable += flow.targetPrototype if (depth < maxDepth) { - val state = (flow.toRef, flow.targetPrototype, depth + 1, nextUsedReturnBridge) + val state = (flow.toRef, flow.targetPrototype, nextCallContext, depth + 1, nextUsedReturnBridge) if (!seen(state)) { seen += state pending.enqueue(state) diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/call-context-negative/input.lua b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/call-context-negative/input.lua new file mode 100644 index 000000000000..798210e20e7f --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/call-context-negative/input.lua @@ -0,0 +1,13 @@ +local function identity(value) + return value +end + +function source_branch() + local tainted = luci.http.formvalue("command") + return identity(tainted) +end + +function safe_sink_branch() + local safe = identity("fixed") + return os.execute(safe) +end diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/call-context-negative/input.luac b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/call-context-negative/input.luac new file mode 100644 index 0000000000000000000000000000000000000000..d1e7763e454d0c14fc07495ac4bec6580930cfeb GIT binary patch literal 812 zcmZWm$xg#C5S$ErY%=%SGBclX~0w)Y-NP&e%3B;Nq69 zF*XlQmr2#+p|pwDcu0*d%fv9s$|tp5XkWZrd|3v6j+pC|6yxy}=NXu1IDlI)?{EOO znaE5!8RM>wXaV{Po}Qhq&5Nkx-YTu0+o!gu--eg1^#jESjQR)}ai77&zyoL_=|kTM zNWAMZ*m$FL6WdT&I_z6}kkltxCX+6c%TJNhPDWR-b>IWs9wC`gK`J^EWiZ8$_Tv3g z6oV1-##McKY^jH7s!G>+ROrSk|8fVmO|ENpEiL gc#ivy`x-j@*99M#nUdtx8HL>F?0@^531>)r0nh_zo&W#< literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/InterproceduralModuleTaintSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/InterproceduralModuleTaintSmokeTest.scala index 878eadf285fd..af971dd84896 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/InterproceduralModuleTaintSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/InterproceduralModuleTaintSmokeTest.scala @@ -65,6 +65,11 @@ class InterproceduralModuleTaintSmokeTest extends AnyWordSpec with Matchers { withClue(s"generic bridge paths: ${genericBridgePaths.mkString(", ")}") { genericBridgePaths.exists(_.contains("bridge-flow-generic/bridge.luac")) shouldBe true } + val callContextNegativePaths = markerCodes(reopened, "lua.taint.path") + .filter(_.contains("call-context-negative")) + withClue(s"call-context negative paths: ${callContextNegativePaths.mkString(", ")}") { + callContextNegativePaths shouldBe empty + } val unresolvedArgFlows = markerCodes(reopened, "lua.interproc.arg_flow") .filter(_.contains("d24-interproc-unresolved-callee-negative")) From 0155c0b32540ba17a433a7531f744f75acee1e89 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Fri, 17 Jul 2026 21:46:41 -0400 Subject: [PATCH 099/105] fix(lua2cpg): preserve fixed table field provenance --- .../bytecode/LuaProgramSemantics.scala | 29 +++++++++++++++--- .../SAMPLE-MANIFEST.md | 2 ++ .../table-field-provenance-negative/input.lua | 4 +++ .../input.luac | Bin 0 -> 410 bytes .../table-field-provenance-positive/input.lua | 4 +++ .../input.luac | Bin 0 -> 392 bytes .../InterproceduralModuleTaintSmokeTest.scala | 10 ++++++ 7 files changed, 44 insertions(+), 5 deletions(-) create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/table-field-provenance-negative/input.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/table-field-provenance-negative/input.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/table-field-provenance-positive/input.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/table-field-provenance-positive/input.luac diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaProgramSemantics.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaProgramSemantics.scala index 64448d7fe955..b8bbc70300a7 100644 --- a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaProgramSemantics.scala +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaProgramSemantics.scala @@ -1890,27 +1890,46 @@ object LuaProgramSemantics { private def representativeTableValueEdges(module: ModuleSummary): Vector[(String, String)] = module.prototypes.flatMap { prototype => val tableSourcesBySlot = scala.collection.mutable.Map.empty[Int, Set[String]].withDefaultValue(Set.empty) - val edges = Vector.newBuilder[(String, String)] + val fieldSourcesBySlotAndKey = + scala.collection.mutable.Map.empty[(Int, LuaConstantValue), Set[String]].withDefaultValue(Set.empty) + val edges = Vector.newBuilder[(String, String)] + def fixedKey(encoded: Int): Option[LuaConstantValue] = + Option.when(encoded >= RkConstantBase)(encoded - RkConstantBase).flatMap { index => + prototype.constants.collectFirst { case LuaConstant(`index`, _, value) => value } + } prototype.instructions.sortBy(_.pc).foreach { instruction => if (instruction.opcode == LuaOpcode.GetTable && tableSourcesBySlot(instruction.b).nonEmpty) { val tableRead = valueRef(prototype.prototypeId, instruction.pc, instruction.b) val result = valueRef(prototype.prototypeId, instruction.pc, instruction.a) edges += qualify(module.path, tableRead) -> qualify(module.path, result) } + if (instruction.opcode == LuaOpcode.GetTable) { + instruction.c.flatMap(fixedKey).foreach { fieldKey => + val sources = fieldSourcesBySlotAndKey((instruction.b, fieldKey)) + if (sources.nonEmpty) { + val tableRead = valueRef(prototype.prototypeId, instruction.pc, instruction.b) + val result = valueRef(prototype.prototypeId, instruction.pc, instruction.a) + sources.foreach { source => + edges += qualify(module.path, source) -> qualify(module.path, tableRead) + } + edges += qualify(module.path, tableRead) -> qualify(module.path, result) + } + } + } representativeReadSlots(prototype, instruction).foreach { slot => val read = valueRef(prototype.prototypeId, instruction.pc, slot) tableSourcesBySlot(slot).foreach { source => edges += qualify(module.path, source) -> qualify(module.path, read) } } - if (instruction.opcode == LuaOpcode.SetTable && instruction.b >= RkConstantBase) { + if (instruction.opcode == LuaOpcode.SetTable) { for { valueSlot <- instruction.c.filter(_ < RkConstantBase) + fieldKey <- fixedKey(instruction.b) } { - val tableRead = valueRef(prototype.prototypeId, instruction.pc, instruction.a) val valueRead = valueRef(prototype.prototypeId, instruction.pc, valueSlot) - edges += qualify(module.path, valueRead) -> qualify(module.path, tableRead) - tableSourcesBySlot += instruction.a -> (tableSourcesBySlot(instruction.a) + valueRead) + fieldSourcesBySlotAndKey += ((instruction.a, fieldKey) -> + (fieldSourcesBySlotAndKey((instruction.a, fieldKey)) + valueRead)) } } if (instruction.opcode == LuaOpcode.SetList && instruction.b > 0) { diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/SAMPLE-MANIFEST.md b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/SAMPLE-MANIFEST.md index 962aff881bf6..b1ed87ab3089 100644 --- a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/SAMPLE-MANIFEST.md +++ b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/SAMPLE-MANIFEST.md @@ -8,12 +8,14 @@ Positive samples: - d16-rf-webcmd-cross-module-popen/controller.luac and mtkwifi.luac: require/module/export/cross-module path. - d24-module-return-table-field-call/controller.luac and library.luac: returned table field call target. - bc-taint-minimal-path/input.luac: minimal same-artifact taint path. +- table-field-provenance-positive/input.luac: same fixed table key preserves taint provenance. Negative samples: - d24-interproc-unresolved-callee-negative/input.luac: unresolved callee boundary. - d24-module-ambiguous-unresolved-dynamic-negative/*.luac: missing, ambiguous, and dynamic require boundaries. - d24-module-missing-field-negative/*.luac: missing export field boundary. - bc-kill-overwrite/input.luac and bc-branch-negative/input.luac: killed/no-flow taint boundaries. +- table-field-provenance-negative/input.luac: distinct fixed table keys do not share taint provenance. Reviewer command: JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.InterproceduralModuleTaintSmokeTest' diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/table-field-provenance-negative/input.lua b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/table-field-provenance-negative/input.lua new file mode 100644 index 000000000000..18748818d03e --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/table-field-provenance-negative/input.lua @@ -0,0 +1,4 @@ +local box = {} +box.metadata = luci.http.formvalue("metadata") + +return os.execute(box.command) diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/table-field-provenance-negative/input.luac b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/table-field-provenance-negative/input.luac new file mode 100644 index 0000000000000000000000000000000000000000..1e7964f9ed21cf7b49907f361ca7a8c7a43a48e7 GIT binary patch literal 410 zcmZ`#OHRWu5S_F}vkeXwa>c#6AW? zHbr!07i@A=GK!%Yw{mY4lFc2CmWC%%>GKg)AiEU)Ce5N;}fdYqu1 zCisy2X@p{3!MK3}>go$V$9s4NC|<<7ff3Fns zd1cDpHOWvJ6&o_D9pBrIOjB~rNLE=*<={dWyOFqgNb!NEpO)vaTHo?{&D>T%J!$!Yfd`v3HLNmmK*g3$*KdWgCc#lVFnW&e?m614Q1E(K_Fc p$|QnVFA7{Z&!PkA6zS;X#}&-gKNPJ0#oUwijGa3iXWT3)egVhvNo@cC literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/InterproceduralModuleTaintSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/InterproceduralModuleTaintSmokeTest.scala index af971dd84896..a644000f79ff 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/InterproceduralModuleTaintSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/InterproceduralModuleTaintSmokeTest.scala @@ -70,6 +70,16 @@ class InterproceduralModuleTaintSmokeTest extends AnyWordSpec with Matchers { withClue(s"call-context negative paths: ${callContextNegativePaths.mkString(", ")}") { callContextNegativePaths shouldBe empty } + val tableFieldNegativePaths = markerCodes(reopened, "lua.taint.path") + .filter(_.contains("table-field-provenance-negative")) + withClue(s"table field provenance negative paths: ${tableFieldNegativePaths.mkString(", ")}") { + tableFieldNegativePaths shouldBe empty + } + val tableFieldPositivePaths = markerCodes(reopened, "lua.taint.path") + .filter(_.contains("table-field-provenance-positive")) + withClue(s"table field provenance positive paths: ${tableFieldPositivePaths.mkString(", ")}") { + tableFieldPositivePaths should not be empty + } val unresolvedArgFlows = markerCodes(reopened, "lua.interproc.arg_flow") .filter(_.contains("d24-interproc-unresolved-callee-negative")) From a4ed3dff493e95a5381e63f11f6378dd7cc6ac65 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Fri, 17 Jul 2026 21:52:04 -0400 Subject: [PATCH 100/105] fix(lua2cpg): merge reaching definitions at branches --- .../bytecode/LuaInstructionSemantics.scala | 65 ++++++++++-------- .../SAMPLE-MANIFEST.md | 2 + .../conditional-merge-positive/input.lua | 7 ++ .../conditional-merge-positive/input.luac | Bin 0 -> 441 bytes .../input.lua | 6 ++ .../input.luac | Bin 0 -> 443 bytes .../InterproceduralModuleTaintSmokeTest.scala | 10 +++ 7 files changed, 61 insertions(+), 29 deletions(-) create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/conditional-merge-positive/input.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/conditional-merge-positive/input.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/nested-branch-overwrite-negative/input.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/nested-branch-overwrite-negative/input.luac diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala index 064c8b47585b..4c48921b89fd 100644 --- a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala @@ -306,10 +306,13 @@ object LuaInstructionSemantics { private var closureTableWrites = Map.empty[(Int, String), LuaClosureValue] private var globalWrites = Map.empty[String, Set[String]] private var mutatedUpvalues = Set.empty[Int] - private var conditionalWriteUntilPc = Option.empty[Int] + private var conditionalReachingAtPc = Map.empty[Int, Map[Int, Set[String]]] def visit(instruction: LuaInstruction): Unit = { - conditionalWriteUntilPc = conditionalWriteUntilPc.filter(instruction.pc < _) + conditionalReachingAtPc.get(instruction.pc).foreach { bypassReaching => + reaching = mergeReaching(reaching, bypassReaching) + conditionalReachingAtPc -= instruction.pc + } instruction.opcode match { case LuaOpcode.Move => val source = readSlot(instruction, instruction.b) @@ -413,8 +416,9 @@ object LuaInstructionSemantics { rkRegister(c).foreach(readSlot(instruction, _)) } } - forwardJumpTargetPc(instruction).foreach { target => - conditionalWriteUntilPc = Some(math.max(conditionalWriteUntilPc.getOrElse(target), target)) + conditionalForwardJumpTargetPc(instruction).foreach { target => + val existing = conditionalReachingAtPc.getOrElse(target, Map.empty) + conditionalReachingAtPc += target -> mergeReaching(existing, reaching) } } @@ -602,42 +606,45 @@ object LuaInstructionSemantics { localFlows += LuaLocalFlow(source, write, "same-instruction-dependence", BytecodeProvenance) semanticSteps += LuaSemanticStep(source, write, kind) } - if (conditionalWriteUntilPc.isDefined && isConditionalDefaultWrite(instruction)) { - reaching += slot -> (reaching.getOrElse(slot, Set.empty) + write) - } else { - reaching.get(slot).foreach { prior => - if (prior.nonEmpty && !prior.contains(write)) { - prior.foreach { first => - killOverwrites += LuaKillOverwrite( - s"${prototype.prototypeId}:pc${instruction.pc}:r$slot:kills:$first", - prototype.prototypeId, - first, - write, - write, - write, - "same-slot-overwrite-kills-prior-definition" - ) - } + reaching.get(slot).foreach { prior => + if (prior.nonEmpty && !prior.contains(write)) { + prior.foreach { first => + killOverwrites += LuaKillOverwrite( + s"${prototype.prototypeId}:pc${instruction.pc}:r$slot:kills:$first", + prototype.prototypeId, + first, + write, + write, + write, + "same-slot-overwrite-kills-prior-definition" + ) } } - reaching += slot -> Set(write) - closuresBySlot -= slot - tableObjectsBySlot -= slot - closureTableWrites = closureTableWrites.filterNot { case ((tableSlot, _), _) => tableSlot == slot } } + reaching += slot -> Set(write) + closuresBySlot -= slot + tableObjectsBySlot -= slot + closureTableWrites = closureTableWrites.filterNot { case ((tableSlot, _), _) => tableSlot == slot } } - private def forwardJumpTargetPc(instruction: LuaInstruction): Option[Int] = - if (instruction.opcode == LuaOpcode.Jmp) { + private def conditionalForwardJumpTargetPc(instruction: LuaInstruction): Option[Int] = + if (instruction.opcode == LuaOpcode.Jmp && previousInstruction(instruction).exists(isConditionalBranch)) { val target = instruction.pc + 1 + instruction.b Option.when(target > instruction.pc + 1)(target) } else { None } - private def isConditionalDefaultWrite(instruction: LuaInstruction): Boolean = - instruction.opcode == LuaOpcode.LoadK || instruction.opcode == LuaOpcode.LoadBool || - instruction.opcode == LuaOpcode.LoadNil + private def previousInstruction(instruction: LuaInstruction): Option[LuaInstruction] = + prototype.instructions.find(_.pc == instruction.pc - 1) + + private def isConditionalBranch(instruction: LuaInstruction): Boolean = + Set(LuaOpcode.Eq, LuaOpcode.Lt, LuaOpcode.Le, LuaOpcode.Test, LuaOpcode.TestSet).contains(instruction.opcode) + + private def mergeReaching(left: Map[Int, Set[String]], right: Map[Int, Set[String]]): Map[Int, Set[String]] = + (left.keySet ++ right.keySet).iterator.map { slot => + slot -> (left.getOrElse(slot, Set.empty) ++ right.getOrElse(slot, Set.empty)) + }.toMap private def isParamDerived(slot: Int): Boolean = reachesParameter(reaching.getOrElse(slot, Set.empty), Set.empty) diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/SAMPLE-MANIFEST.md b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/SAMPLE-MANIFEST.md index b1ed87ab3089..ac338c29f6ee 100644 --- a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/SAMPLE-MANIFEST.md +++ b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/SAMPLE-MANIFEST.md @@ -9,6 +9,7 @@ Positive samples: - d24-module-return-table-field-call/controller.luac and library.luac: returned table field call target. - bc-taint-minimal-path/input.luac: minimal same-artifact taint path. - table-field-provenance-positive/input.luac: same fixed table key preserves taint provenance. +- conditional-merge-positive/input.luac: an optional overwrite preserves the bypassed source at branch merge. Negative samples: - d24-interproc-unresolved-callee-negative/input.luac: unresolved callee boundary. @@ -16,6 +17,7 @@ Negative samples: - d24-module-missing-field-negative/*.luac: missing export field boundary. - bc-kill-overwrite/input.luac and bc-branch-negative/input.luac: killed/no-flow taint boundaries. - table-field-provenance-negative/input.luac: distinct fixed table keys do not share taint provenance. +- nested-branch-overwrite-negative/input.luac: an entered branch overwrite kills the prior source before its sink. Reviewer command: JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.InterproceduralModuleTaintSmokeTest' diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/conditional-merge-positive/input.lua b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/conditional-merge-positive/input.lua new file mode 100644 index 000000000000..b90e178ae09f --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/conditional-merge-positive/input.lua @@ -0,0 +1,7 @@ +local value = luci.http.formvalue("command") + +if use_default then + value = "fixed" +end + +return os.execute(value) diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/conditional-merge-positive/input.luac b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/conditional-merge-positive/input.luac new file mode 100644 index 0000000000000000000000000000000000000000..f0e9aeee71f681904ff249d1818e4fe5c2a2daff GIT binary patch literal 441 zcmZ`!%TB{E5L{CeB81?^1;ihumiU5jC?_slkpNltrZsZx$d9VWd|LT4)+A9cFw%Iv zJ3DLd-Ak6QzVkuW9^F!~%6Mq#KW48beZ zIY`^|UT2F)j`*puli4QV)nYUUq@LBB`gj_E2UI!XuTmCaf3Wx#a=Ksq!{S|@oc>w1 Ix+WI<0F9qcbpQYW literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/nested-branch-overwrite-negative/input.lua b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/nested-branch-overwrite-negative/input.lua new file mode 100644 index 000000000000..112863c4636a --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/nested-branch-overwrite-negative/input.lua @@ -0,0 +1,6 @@ +local value = luci.http.formvalue("command") + +if enabled then + value = "fixed" + return os.execute(value) +end diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/nested-branch-overwrite-negative/input.luac b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/nested-branch-overwrite-negative/input.luac new file mode 100644 index 0000000000000000000000000000000000000000..cec5d8ab423c68d91609cd4c9368452b64470f02 GIT binary patch literal 443 zcmZutOHRWu5FOJHMJ!;)2E+}FB~D-#WyOXq5=gEyZH*i|@+V}Oqm`R6CXI>^PkJ8D z=go|L^PJTSu&SvV7-5F|@E9D|)`)HJE}?6quvtCmekWqkB4JD-VDvd?jKWY!=!4hT z=AbO^e48!uq$nXA)ndyxK{>tWe!%bxseB;R_~;y#cUyvUlV)WUJDkz-Dbu-ae%ss*5=VTOYbX z*^EFxFhl#UQ?5M)(5a0@3e=0pjKy6YT_1ndj;M3OpG?TV!=0u<4-66 literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/InterproceduralModuleTaintSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/InterproceduralModuleTaintSmokeTest.scala index a644000f79ff..64fa696bb6d1 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/InterproceduralModuleTaintSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/InterproceduralModuleTaintSmokeTest.scala @@ -80,6 +80,16 @@ class InterproceduralModuleTaintSmokeTest extends AnyWordSpec with Matchers { withClue(s"table field provenance positive paths: ${tableFieldPositivePaths.mkString(", ")}") { tableFieldPositivePaths should not be empty } + val nestedBranchOverwritePaths = markerCodes(reopened, "lua.taint.path") + .filter(_.contains("nested-branch-overwrite-negative")) + withClue(s"nested branch overwrite paths: ${nestedBranchOverwritePaths.mkString(", ")}") { + nestedBranchOverwritePaths shouldBe empty + } + val conditionalMergePaths = markerCodes(reopened, "lua.taint.path") + .filter(_.contains("conditional-merge-positive")) + withClue(s"conditional merge paths: ${conditionalMergePaths.mkString(", ")}") { + conditionalMergePaths should not be empty + } val unresolvedArgFlows = markerCodes(reopened, "lua.interproc.arg_flow") .filter(_.contains("d24-interproc-unresolved-callee-negative")) From 3d2e3940881f954e739aec5aaac918d5cf975314 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Fri, 17 Jul 2026 21:56:54 -0400 Subject: [PATCH 101/105] feat(lua2cpg): model conditional assignment dependence --- .../bytecode/LuaInstructionSemantics.scala | 21 +++++++++++++++++- .../SAMPLE-MANIFEST.md | 3 +++ .../control-overwrite-negative/input.lua | 10 +++++++++ .../control-overwrite-negative/input.luac | Bin 0 -> 533 bytes .../control-selection-positive/input.lua | 9 ++++++++ .../control-selection-positive/input.luac | Bin 0 -> 525 bytes .../control-unrelated-negative/input.lua | 9 ++++++++ .../control-unrelated-negative/input.luac | Bin 0 -> 514 bytes .../InterproceduralModuleTaintSmokeTest.scala | 15 +++++++++++++ 9 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/control-overwrite-negative/input.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/control-overwrite-negative/input.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/control-selection-positive/input.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/control-selection-positive/input.luac create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/control-unrelated-negative/input.lua create mode 100644 joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/control-unrelated-negative/input.luac diff --git a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala index 4c48921b89fd..523d9a62e5c7 100644 --- a/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala +++ b/joern-cli/frontends/lua2cpg/src/main/scala/io/joern/lua2cpg/bytecode/LuaInstructionSemantics.scala @@ -306,9 +306,11 @@ object LuaInstructionSemantics { private var closureTableWrites = Map.empty[(Int, String), LuaClosureValue] private var globalWrites = Map.empty[String, Set[String]] private var mutatedUpvalues = Set.empty[Int] - private var conditionalReachingAtPc = Map.empty[Int, Map[Int, Set[String]]] + private var conditionalReachingAtPc = Map.empty[Int, Map[Int, Set[String]]] + private var controlPredicatesUntilPc = Vector.empty[(Int, Set[String])] def visit(instruction: LuaInstruction): Unit = { + controlPredicatesUntilPc = controlPredicatesUntilPc.filter { case (targetPc, _) => instruction.pc < targetPc } conditionalReachingAtPc.get(instruction.pc).foreach { bypassReaching => reaching = mergeReaching(reaching, bypassReaching) conditionalReachingAtPc -= instruction.pc @@ -419,6 +421,9 @@ object LuaInstructionSemantics { conditionalForwardJumpTargetPc(instruction).foreach { target => val existing = conditionalReachingAtPc.getOrElse(target, Map.empty) conditionalReachingAtPc += target -> mergeReaching(existing, reaching) + previousInstruction(instruction).map(conditionalPredicateRefs).filter(_.nonEmpty).foreach { predicates => + controlPredicatesUntilPc :+= target -> predicates + } } } @@ -606,6 +611,9 @@ object LuaInstructionSemantics { localFlows += LuaLocalFlow(source, write, "same-instruction-dependence", BytecodeProvenance) semanticSteps += LuaSemanticStep(source, write, kind) } + controlPredicatesUntilPc.iterator.flatMap(_._2).toSet.filterNot(_ == write).foreach { predicate => + localFlows += LuaLocalFlow(predicate, write, "conditional-assignment-dependence", BytecodeProvenance) + } reaching.get(slot).foreach { prior => if (prior.nonEmpty && !prior.contains(write)) { prior.foreach { first => @@ -641,6 +649,17 @@ object LuaInstructionSemantics { private def isConditionalBranch(instruction: LuaInstruction): Boolean = Set(LuaOpcode.Eq, LuaOpcode.Lt, LuaOpcode.Le, LuaOpcode.Test, LuaOpcode.TestSet).contains(instruction.opcode) + private def conditionalPredicateRefs(instruction: LuaInstruction): Set[String] = + instruction.opcode match { + case LuaOpcode.Eq | LuaOpcode.Lt | LuaOpcode.Le => + Vector(rkRegister(instruction.b), instruction.c.flatMap(rkRegister)).flatten + .map(slotRef(instruction.pc, _)) + .toSet + case LuaOpcode.Test => Set(slotRef(instruction.pc, instruction.a)) + case LuaOpcode.TestSet => Set(slotRef(instruction.pc, instruction.b)) + case _ => Set.empty + } + private def mergeReaching(left: Map[Int, Set[String]], right: Map[Int, Set[String]]): Map[Int, Set[String]] = (left.keySet ++ right.keySet).iterator.map { slot => slot -> (left.getOrElse(slot, Set.empty) ++ right.getOrElse(slot, Set.empty)) diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/SAMPLE-MANIFEST.md b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/SAMPLE-MANIFEST.md index ac338c29f6ee..371209a0b0f6 100644 --- a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/SAMPLE-MANIFEST.md +++ b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/SAMPLE-MANIFEST.md @@ -10,6 +10,7 @@ Positive samples: - bc-taint-minimal-path/input.luac: minimal same-artifact taint path. - table-field-provenance-positive/input.luac: same fixed table key preserves taint provenance. - conditional-merge-positive/input.luac: an optional overwrite preserves the bypassed source at branch merge. +- control-selection-positive/input.luac: a tainted predicate controls selection of a value consumed by a sink. Negative samples: - d24-interproc-unresolved-callee-negative/input.luac: unresolved callee boundary. @@ -18,6 +19,8 @@ Negative samples: - bc-kill-overwrite/input.luac and bc-branch-negative/input.luac: killed/no-flow taint boundaries. - table-field-provenance-negative/input.luac: distinct fixed table keys do not share taint provenance. - nested-branch-overwrite-negative/input.luac: an entered branch overwrite kills the prior source before its sink. +- control-unrelated-negative/input.luac: a predicate does not taint assignments outside its controlled region. +- control-overwrite-negative/input.luac: a post-merge overwrite kills prior control-dependent selection. Reviewer command: JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.InterproceduralModuleTaintSmokeTest' diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/control-overwrite-negative/input.lua b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/control-overwrite-negative/input.lua new file mode 100644 index 000000000000..43605f131c9e --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/control-overwrite-negative/input.lua @@ -0,0 +1,10 @@ +local selector = luci.http.formvalue("selector") +local candidate = "selected-command" +local selected = "fixed" + +if selector == "chosen" then + selected = candidate +end + +selected = "fixed" +return os.execute(selected) diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/control-overwrite-negative/input.luac b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/control-overwrite-negative/input.luac new file mode 100644 index 0000000000000000000000000000000000000000..ce582b64f5fea13b7bedcec81fad72c50ad1e5dd GIT binary patch literal 533 zcmZutK~BRk5L_3x6i#q~1Bf34OS~YxbKwb=v!zB(9IfrrWB6Hx1iyf{u-imJh>o;g zjmNVd`{uFOCs2B+mq0M0eu+q3<$^bBie1L8NhTEg;QD8i$eD~O8^RO|IZVd$5$Y*A zYzo;>wXrDyt87^?jt5W-Roo)IlFwLm_-wP^BK!K1?|JZaue90xlGi!cqOM^v)UetE z+zy~tpx~`ma6PQx3SbyuIPj0Xf=_r?EUPa7_08Tp*3P(V86*hB`Cqolxd(kdO0bU9 zZfz(CT0DY;0bPy+TG9x{9c(HW+tzjs=*1|&+V7C*=|tToChS0;OgtvwuN{FqbS2|~ w$Y6D(X1pg}Nq+bS?h;GV9xFNfFSZyjCIdHcoY*&(ua?xJ{I&1{B)5*;Svc_j9c z95yA|P_@*QfK|3k7{?1Dg(_h{mpye=PswM$LDuyp-}B<>UMWYj3qEI@^SXxlP{VQu za65onf`Zpt!S%3&D}Z5u0p52CKHyC;r9J`F7whi0f67(M5{Xcp|3&MZd(dYi1uKf} z#)g8RMI$j9r3m_RY)l<&Do1T=y9V@pVpe{O3{NKL)|9XVeLP`Gz~4FsZqb#Ddmv9# s2l9w}q7~=+FW@S+4yDZfvn-->&Y$NwcIz9<*NS6d{z}-qco#zA8-)B`^Z)<= literal 0 HcmV?d00001 diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/control-unrelated-negative/input.lua b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/control-unrelated-negative/input.lua new file mode 100644 index 000000000000..051516ea9b27 --- /dev/null +++ b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/control-unrelated-negative/input.lua @@ -0,0 +1,9 @@ +local selector = luci.http.formvalue("selector") +local ignored = "fixed" + +if selector == "chosen" then + ignored = "ignored" +end + +local selected = "fixed" +return os.execute(selected) diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/control-unrelated-negative/input.luac b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/control-unrelated-negative/input.luac new file mode 100644 index 0000000000000000000000000000000000000000..87c9a2c7cba009fd6849f379081c7c923fe74e10 GIT binary patch literal 514 zcmZutK~4iP40IqZg$tbE0OAKrBwnz4=fV@BtlKV1la-THJuW}1kl+{a7HoE<3g}3a z#GZ^}=k}@CXHa^f7eFwgeu+q-a=tT5iXmfYlkvqqx_)I6Ig>GELzrSAhskt&g?fq( z+d|e?o7fhQRkkb`#|0?*ssuuBGqzR0mCfB2+1Ho+z=fxGWjmQ&@j7G9>l)@m4b2|l zZUEJQf}6U5Yk;8{6uff$zE|)8Z;FrV6F_~j?;Zv?=Uqz?fiJE*MC+V;&=;cw%SfB8 z^#wtTN088?%aK6O#{?|99k!s)rqHcp!T|hXBf3?HBo|JnL`(+}cj!vSA4MipM|i?H hJyDvL!qUw#${YVW71Jg2Z$~nK Date: Fri, 17 Jul 2026 22:12:05 -0400 Subject: [PATCH 102/105] test(lua2cpg): update neutral corpus report expectations --- .../joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index 7d6625c2ff4e..b9b543e84317 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -184,11 +184,11 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { "export OpenWrt-derived path report totals with scoped path steps" in { withOpenWrtDerivedExportDir { exportDir => val profile = ujson.read(Files.readString(exportDir.resolve("path-search-profile.json"))).obj - profile("taint_path_count").num.toInt shouldBe 18 - profile("report_count").num.toInt shouldBe 18 + profile("taint_path_count").num.toInt shouldBe 20 + profile("report_count").num.toInt shouldBe 20 val pathRows = stagingRows(exportDir).flatMap(_("path_evidence").arr.map(_.obj)) - pathRows.size shouldBe 18 + pathRows.size shouldBe 20 pathRows.foreach { row => row("source_module_path").str should not be empty row("sink_module_path").str should not be empty From b64d7d60f18f7c428d1320c34926feda35da2b69 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Sat, 18 Jul 2026 03:12:02 -0400 Subject: [PATCH 103/105] docs(lua2cpg): align reviewer guidance with sanitized state --- joern-cli/frontends/lua2cpg/README.md | 6 +++--- .../interprocedural-module-taint/SAMPLE-MANIFEST.md | 6 +++--- .../resources/rules-sanitizer-report/SAMPLE-MANIFEST.md | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/joern-cli/frontends/lua2cpg/README.md b/joern-cli/frontends/lua2cpg/README.md index befabac8eabf..89d1513a4719 100644 --- a/joern-cli/frontends/lua2cpg/README.md +++ b/joern-cli/frontends/lua2cpg/README.md @@ -12,7 +12,7 @@ file inventory. ## Prerequisites -- Use the JDK and `sbt` versions required by the Joern repository. +- Use JDK 21 and `sbt` 1.12.5, matching the Joern repository configuration. - Run the commands below from the Joern repository root. - Install `luac5.1` when starting from Lua source files. @@ -218,5 +218,5 @@ Full frontend test suite: sbt 'lua2cpg/test' ``` -The full `lua2cpg/test` suite is intentionally broader and can take about an -hour in this development environment. +The full suite covers the decoder, CPG modeling, program semantics, evidence +export, and self-contained corpus regression tests. diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/SAMPLE-MANIFEST.md b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/SAMPLE-MANIFEST.md index 371209a0b0f6..b91465ae54c4 100644 --- a/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/SAMPLE-MANIFEST.md +++ b/joern-cli/frontends/lua2cpg/src/test/resources/interprocedural-module-taint/SAMPLE-MANIFEST.md @@ -1,4 +1,4 @@ -# E4 Interprocedural Module Taint Samples +# Interprocedural Module Taint Samples Source family: committed Lua 5.1 bytecode fixtures covering interprocedural module and taint behavior. @@ -27,5 +27,5 @@ JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lu JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/stage' git status --short -This subset is sufficient for reviewer smoke of E4 semantics and is consumed -entirely from the `lua2cpg` test resources. +This subset is sufficient for reviewer smoke of interprocedural module and +taint semantics and is consumed entirely from the `lua2cpg` test resources. diff --git a/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/SAMPLE-MANIFEST.md b/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/SAMPLE-MANIFEST.md index 6932b0217d01..a09756150a6b 100644 --- a/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/SAMPLE-MANIFEST.md +++ b/joern-cli/frontends/lua2cpg/src/test/resources/rules-sanitizer-report/SAMPLE-MANIFEST.md @@ -7,7 +7,7 @@ sanitizer classification, report construction, and negative taint boundaries. | Fixture | Fixture role | Capability reason | Consuming reviewer command | | --- | --- | --- | --- | -| `bc-taint-minimal-path/input.luac` | Focused committed fixture | Minimal source-to-sink path for rule, endpoint, report, and E5 boundary smoke coverage. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | +| `bc-taint-minimal-path/input.luac` | Focused committed fixture | Minimal source-to-sink path for rule, endpoint, report, and report-boundary smoke coverage. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | | `d16-rf-formvalue-os-execute-chain/input.luac` | Focused committed fixture | Final-segment `*.formvalue` source and `*.execute` sink positive with a constructed report. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | | `d16-rf-submit-dpp-uri-execute/input.luac` | Focused committed fixture | Independent same-module formvalue-to-execute report positive. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | | `d16-rf-webcmd-cross-module-popen/controller.luac` | Focused committed fixture | Cross-module source side for final-segment source/sink and report construction. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | @@ -16,5 +16,5 @@ sanitizer classification, report construction, and negative taint boundaries. | `d24-rules-overmatch-constant-sink-negative/input.luac` | Focused committed fixture | Rejects `formvaluex`, `executex`, and fixed-string sink arguments. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | | `d24-sanitizer-same-suffix-off-chain-negative/input.luac` | Focused committed fixture | Same-suffix sanitizer call not on the path remains `not-sanitized` and does not suppress the report. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | | `d24-report-no-report-without-path-negative/input.luac` | Focused committed fixture | Endpoint-only source/sink evidence does not create a vulnerability report. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | -| `bc-kill-overwrite/input.luac` | Focused committed fixture | Killed taint path negative boundary; no E5 report should be emitted. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | -| `bc-branch-negative/input.luac` | Focused committed fixture | Branch-negative no-path boundary; no E5 report should be emitted. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | +| `bc-kill-overwrite/input.luac` | Focused committed fixture | Killed taint path negative boundary; no vulnerability report should be emitted. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | +| `bc-branch-negative/input.luac` | Focused committed fixture | Branch-negative no-path boundary; no vulnerability report should be emitted. | `JAVA_TOOL_OPTIONS='-Dsbt.watch.mode=polling -Dsbt.io.jdktimestamps=true' sbt 'lua2cpg/testOnly io.joern.lua2cpg.RulesSanitizerReportSmokeTest'` | From 9fbfd7ee203c1fc6427317991c66fbe76b5e16e7 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Sat, 18 Jul 2026 03:37:04 -0400 Subject: [PATCH 104/105] style(lua2cpg): format test sources --- .../RealFirmwareEvidenceExportSmokeTest.scala | 21 ++++++++++--------- .../bytecode/LuaBytecodeDecoderTest.scala | 10 ++++----- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala index b9b543e84317..073224dd31e5 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/RealFirmwareEvidenceExportSmokeTest.scala @@ -89,16 +89,16 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { "report_count" ) val counters = counterNames.map(_ -> 0L).toMap ++ Map( - "source_reachability_check_count" -> 1L, + "source_reachability_check_count" -> 1L, "source_reachability_accepted_count" -> 1L, - "parameter_position_check_count" -> 1L, - "parameter_position_accepted_count" -> 1L, - "path_constructor_check_count" -> 1L, - "path_constructor_accepted_count" -> 1L, - "local_path_search_count" -> 1L, - "distinct_local_path_query_count" -> 1L, - "local_path_cache_miss_count" -> 1L, - "taint_path_count" -> 1L + "parameter_position_check_count" -> 1L, + "parameter_position_accepted_count" -> 1L, + "path_constructor_check_count" -> 1L, + "path_constructor_accepted_count" -> 1L, + "local_path_search_count" -> 1L, + "distinct_local_path_query_count" -> 1L, + "local_path_cache_miss_count" -> 1L, + "taint_path_count" -> 1L ) val sourceRef = "bc-endpoint-contract/input.luac:root@pc1:r0" val sinkRef = "bc-endpoint-contract/input.luac:root@pc2:r1" @@ -118,7 +118,8 @@ class RealFirmwareEvidenceExportSmokeTest extends AnyWordSpec with Matchers { interproceduralArgFlows = Vector.empty, interproceduralReturnFlows = Vector.empty, crossBoundaryCallTargets = Vector.empty, - taintPaths = Vector(LuaTaintPath(sourceRef, sinkRef, Vector(sourceRef, sinkRef), "true-positive", "bytecode-only")), + taintPaths = + Vector(LuaTaintPath(sourceRef, sinkRef, Vector(sourceRef, sinkRef), "true-positive", "bytecode-only")), boundaries = Vector.empty, ruleMatches = Vector.empty, sourceEndpoints = Vector.empty, diff --git a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/bytecode/LuaBytecodeDecoderTest.scala b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/bytecode/LuaBytecodeDecoderTest.scala index b1587c8df5c5..305133d72b47 100644 --- a/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/bytecode/LuaBytecodeDecoderTest.scala +++ b/joern-cli/frontends/lua2cpg/src/test/scala/io/joern/lua2cpg/bytecode/LuaBytecodeDecoderTest.scala @@ -44,11 +44,11 @@ class LuaBytecodeDecoderTest extends AnyWordSpec with Matchers { "return diagnostics without accepted prototype models for malformed inputs" in { val cases = Seq( - "not-lua-bytecode.luac" -> "not-lua-bytecode", - "truncated.luac" -> "truncated-bytecode", - "unsupported-version.luac" -> "unsupported-bytecode-version", - "unsupported-profile.luac" -> "unsupported-bytecode-profile", - "malformed-constant.luac" -> "malformed-constant" + "not-lua-bytecode.luac" -> "not-lua-bytecode", + "truncated.luac" -> "truncated-bytecode", + "unsupported-version.luac" -> "unsupported-bytecode-version", + "unsupported-profile.luac" -> "unsupported-bytecode-profile", + "malformed-constant.luac" -> "malformed-constant" ) cases.foreach { case (fileName, expectedKind) => From e81be43ae07d568ca1265b0fd15cfe7fc72fec52 Mon Sep 17 00:00:00 2001 From: prankster009 Date: Wed, 22 Jul 2026 02:53:50 -0400 Subject: [PATCH 105/105] chore(lua2cpg): keep generated analysis reports untracked --- .gitignore | 1 + joern-cli/frontends/lua2cpg/README.md | 14 +++++--------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index e705ba6f7fd1..d9a01a3322ce 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,4 @@ flake.lock ############## user.bazelrc bazel-* +/joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua-report/ diff --git a/joern-cli/frontends/lua2cpg/README.md b/joern-cli/frontends/lua2cpg/README.md index 89d1513a4719..8568a9f975fb 100644 --- a/joern-cli/frontends/lua2cpg/README.md +++ b/joern-cli/frontends/lua2cpg/README.md @@ -141,13 +141,7 @@ The corpus preserves the original `usr/lib/lua` layout and contains: - 42 `.lua` source files recorded in the CPG file inventory. - 42 Lua 5.1 `.luac` bytecode files analyzed by the bytecode pipeline. -A generated native JSON analysis report is committed for quick inspection: - -```text -joern-cli/frontends/lua2cpg/src/test/resources/openwrt-derived-firmware-lua-report -``` - -To regenerate that report from the Joern repository root: +To generate a native JSON analysis report from the Joern repository root: ```bash joern-cli/frontends/lua2cpg/target/universal/stage/bin/lua2cpg \ @@ -156,8 +150,10 @@ joern-cli/frontends/lua2cpg/target/universal/stage/bin/lua2cpg \ --lua-real-firmware-output-dir /tmp/openwrt-derived-firmware-lua-report ``` -The committed report contains native JSON evidence only. The generated CPG -binary is not committed. +The generated report and CPG are reproducible outputs and are intentionally not +tracked by Git. A successful corpus run decodes all 42 bytecode inputs with no +diagnostics and emits 164 source endpoints, 94 sink endpoints, 20 taint paths, +and 20 reports. ## Supported Analysis