From e2fc078beef7ec64297122b98eda9297aa3d4554 Mon Sep 17 00:00:00 2001 From: n4o847 <22975590+n4o847@users.noreply.github.com> Date: Fri, 27 Nov 2020 02:48:33 +0900 Subject: [PATCH 01/20] =?UTF-8?q?=E6=94=BB=E6=92=83=E6=96=87=E5=AD=97?= =?UTF-8?q?=E5=88=97=E3=81=AE=E7=B5=8C=E8=B7=AF=E6=8E=A2=E7=B4=A2=E3=82=92?= =?UTF-8?q?=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/attack.ts | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/attack.ts diff --git a/src/attack.ts b/src/attack.ts new file mode 100644 index 0000000..ecefddf --- /dev/null +++ b/src/attack.ts @@ -0,0 +1,49 @@ +import { NonEpsilonNFA, State, Char } from './types'; + +export function buildExponentialAttack(): void { + // unimplemented +} + +export function buildPolynomialAttack(): void { + // unimplemented +} + +/** + * NFA 上である状態からある状態までの経路 (Char の配列) を幅優先探索で復元する。 + */ +function restorePath( + nfa: NonEpsilonNFA, + source: State, + destination: State, +): Char[] { + /** その状態にどの状態からどの遷移でたどり着けるか */ + const referrer = new Map(); + + const queue: State[] = []; + queue.push(source); + + while (queue.length !== 0) { + const q = queue.shift()!; + if (q === destination) { + break; + } + for (const char of nfa.alphabet) { + for (const d of nfa.transitions.get(q, char)) { + if (referrer.has(d)) { + continue; + } + referrer.set(d, [q, char]); + queue.push(d); + } + } + } + + const path: Char[] = []; + for (let q = destination; q !== source; ) { + const [prevState, char] = referrer.get(q)!; + path.push(char); + q = prevState; + } + path.reverse(); + return path; +} From 5c92b183739cad5f5b5dcf152f3bcafc26217ca1 Mon Sep 17 00:00:00 2001 From: n4o847 <22975590+n4o847@users.noreply.github.com> Date: Fri, 27 Nov 2020 03:08:58 +0900 Subject: [PATCH 02/20] =?UTF-8?q?=E6=8C=87=E6=95=B0=E7=9A=84=E6=94=BB?= =?UTF-8?q?=E6=92=83=E6=96=87=E5=AD=97=E5=88=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/attack.ts | 97 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 87 insertions(+), 10 deletions(-) diff --git a/src/attack.ts b/src/attack.ts index ecefddf..158a212 100644 --- a/src/attack.ts +++ b/src/attack.ts @@ -1,20 +1,92 @@ -import { NonEpsilonNFA, State, Char } from './types'; +import { getLeftState, getRightState } from './directProduct'; +import { + NonEpsilonNFA, + StronglyConnectedComponentGraph, + DirectProductGraph, + State, + Char, +} from './types'; -export function buildExponentialAttack(): void { - // unimplemented +export function buildExponentialAttack( + nfa: NonEpsilonNFA, + dp: DirectProductGraph, + sccs: StronglyConnectedComponentGraph[], +): string | null { + for (const scc of sccs) { + for (const source of scc.stateList) { + const sourceLeft = getLeftState(source); + const souceRight = getRightState(source); + + if (sourceLeft !== souceRight) { + continue; + } + + // 多重自己ループを検出 + for (const char of scc.alphabet) { + let visited = false; + for (const destination of scc.transitions.get(source, char)) { + if (destination !== source) { + continue; + } + if (visited) { + let attack = ''; + attack += pathString(nfa, nfa.initialState, new Set([sourceLeft])); + if (char !== null) { + attack += char.repeat(20); + } else { + // TODO: その他の文字の場合 + } + attack += pathString(nfa, sourceLeft, nfa.acceptingStateSet); + return attack; + } + visited = true; + } + } + + // (n, n) → (m, k) → (n, n) (m ≠ k) を検出 + for (const viaPair of scc.stateList) { + const viaLeft = getLeftState(viaPair); + const viaRight = getRightState(viaPair); + if (viaLeft !== viaRight) { + let attack = ''; + attack += pathString(nfa, nfa.initialState, new Set([sourceLeft])); + { + let loop = ''; + loop += pathString(nfa, sourceLeft, new Set([viaLeft])); + loop += pathString(nfa, viaLeft, new Set([sourceLeft])); + attack += loop.repeat(20); + } + attack += pathString(nfa, sourceLeft, nfa.acceptingStateSet); + return attack; + } + } + } + } + + // EDA 検出なし + return null; } export function buildPolynomialAttack(): void { // unimplemented } +function pathString( + nfa: NonEpsilonNFA, + source: State, + destinations: Set, +): string { + // TODO: その他の文字の場合 + return restorePath(nfa, source, destinations).join(''); +} + /** * NFA 上である状態からある状態までの経路 (Char の配列) を幅優先探索で復元する。 */ function restorePath( nfa: NonEpsilonNFA, source: State, - destination: State, + destinations: Set, ): Char[] { /** その状態にどの状態からどの遷移でたどり着けるか */ const referrer = new Map(); @@ -22,9 +94,12 @@ function restorePath( const queue: State[] = []; queue.push(source); + let foundDestination: State | null = null; + while (queue.length !== 0) { const q = queue.shift()!; - if (q === destination) { + if (destinations.has(q)) { + foundDestination = q; break; } for (const char of nfa.alphabet) { @@ -39,11 +114,13 @@ function restorePath( } const path: Char[] = []; - for (let q = destination; q !== source; ) { - const [prevState, char] = referrer.get(q)!; - path.push(char); - q = prevState; + if (foundDestination !== null) { + for (let q = foundDestination; q !== source; ) { + const [prevState, char] = referrer.get(q)!; + path.push(char); + q = prevState; + } + path.reverse(); } - path.reverse(); return path; } From 3986d97c78de0bd28b7b9bfa1d8888075ebf180c Mon Sep 17 00:00:00 2001 From: n4o847 <22975590+n4o847@users.noreply.github.com> Date: Fri, 27 Nov 2020 19:00:29 +0900 Subject: [PATCH 03/20] =?UTF-8?q?=E6=8C=87=E6=95=B0=E6=99=82=E9=96=93?= =?UTF-8?q?=E6=94=BB=E6=92=83=E6=96=87=E5=AD=97=E5=88=97=EF=BC=88=E6=9C=AA?= =?UTF-8?q?=E7=B5=B1=E5=90=88=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/attack.ts | 77 +++++++++++++++++++++++++++++++++------------------ src/eda.ts | 20 +++++++++---- src/ida.ts | 2 +- src/index.ts | 2 +- src/test.ts | 2 +- src/types.ts | 18 +++++++++--- 6 files changed, 82 insertions(+), 39 deletions(-) diff --git a/src/attack.ts b/src/attack.ts index 158a212..6c0aa0a 100644 --- a/src/attack.ts +++ b/src/attack.ts @@ -1,17 +1,17 @@ import { getLeftState, getRightState } from './directProduct'; import { NonEpsilonNFA, - StronglyConnectedComponentGraph, - DirectProductGraph, State, Char, + StronglyConnectedComponentGraph, } from './types'; export function buildExponentialAttack( nfa: NonEpsilonNFA, - dp: DirectProductGraph, sccs: StronglyConnectedComponentGraph[], ): string | null { + const nullChar = getCharForNull(nfa.alphabet); + for (const scc of sccs) { for (const source of scc.stateList) { const sourceLeft = getLeftState(source); @@ -30,33 +30,52 @@ export function buildExponentialAttack( } if (visited) { let attack = ''; - attack += pathString(nfa, nfa.initialState, new Set([sourceLeft])); - if (char !== null) { - attack += char.repeat(20); - } else { - // TODO: その他の文字の場合 + attack += pathString( + nfa, + nfa.initialState, + new Set([sourceLeft]), + nullChar, + ); + { + const loop = char ?? nullChar; + attack += loop.repeat(20); } - attack += pathString(nfa, sourceLeft, nfa.acceptingStateSet); + attack += pathString( + nfa, + sourceLeft, + nfa.acceptingStateSet, + nullChar, + ); return attack; } visited = true; } } - // (n, n) → (m, k) → (n, n) (m ≠ k) を検出 + // (q1, q1) → (q2, q3) → (q1, q1) where q2 ≠ q3 を検出 for (const viaPair of scc.stateList) { const viaLeft = getLeftState(viaPair); const viaRight = getRightState(viaPair); - if (viaLeft !== viaRight) { + if (viaLeft !== viaRight && viaLeft !== sourceLeft) { let attack = ''; - attack += pathString(nfa, nfa.initialState, new Set([sourceLeft])); + attack += pathString( + nfa, + nfa.initialState, + new Set([sourceLeft]), + nullChar, + ); { let loop = ''; - loop += pathString(nfa, sourceLeft, new Set([viaLeft])); - loop += pathString(nfa, viaLeft, new Set([sourceLeft])); + loop += pathString(nfa, sourceLeft, new Set([viaLeft]), nullChar); + loop += pathString(nfa, viaLeft, new Set([sourceLeft]), nullChar); attack += loop.repeat(20); } - attack += pathString(nfa, sourceLeft, nfa.acceptingStateSet); + attack += pathString( + nfa, + sourceLeft, + nfa.acceptingStateSet, + nullChar, + ); return attack; } } @@ -71,23 +90,27 @@ export function buildPolynomialAttack(): void { // unimplemented } -function pathString( - nfa: NonEpsilonNFA, - source: State, - destinations: Set, -): string { - // TODO: その他の文字の場合 - return restorePath(nfa, source, destinations).join(''); +/** + * null に対応する文字を探す。 + */ +function getCharForNull(alphabet: Set): string { + for (let c = 0x00; ; c++) { + const s = String.fromCharCode(c); + if (!alphabet.has(s)) { + return s; + } + } } /** * NFA 上である状態からある状態までの経路 (Char の配列) を幅優先探索で復元する。 */ -function restorePath( +function pathString( nfa: NonEpsilonNFA, source: State, destinations: Set, -): Char[] { + nullChar: string, +): string { /** その状態にどの状態からどの遷移でたどり着けるか */ const referrer = new Map(); @@ -113,14 +136,14 @@ function restorePath( } } - const path: Char[] = []; + const path: string[] = []; if (foundDestination !== null) { for (let q = foundDestination; q !== source; ) { const [prevState, char] = referrer.get(q)!; - path.push(char); + path.push(char ?? nullChar); q = prevState; } path.reverse(); } - return path; + return path.join(''); } diff --git a/src/eda.ts b/src/eda.ts index 6462732..e60f94f 100644 --- a/src/eda.ts +++ b/src/eda.ts @@ -1,21 +1,31 @@ +import { buildExponentialAttack } from './attack'; import { getLeftState, getRightState } from './directProduct'; import { buildStronglyConnectedComponents } from './scc'; -import { DirectProductGraph, Message } from './types'; +import { NonEpsilonNFA, DirectProductGraph, Message } from './types'; /** * 強連結成分を一つ一つ見ていき、EDAを持つかメッセージを返す */ -export function showMessageEDA(dps: DirectProductGraph[]): Message { +export function showMessageEDA( + nfa: NonEpsilonNFA, + dps: DirectProductGraph[], +): Message { // 別の経路で同じ文字で移動して自身に戻れたらEDA - if (dps.some((dp) => isEDA(dp))) { - return { status: 'Vulnerable', message: 'Detected EDA.' }; + if (dps.some((dp) => isEDA(nfa, dp))) { + return { status: 'Vulnerable', message: 'Detected EDA.', attack: '' }; } else { return { status: 'Safe', message: "Don't have EDA." }; } } -function isEDA(dp: DirectProductGraph): boolean { +function isEDA(nfa: NonEpsilonNFA, dp: DirectProductGraph): boolean { const sccs = buildStronglyConnectedComponents(dp); + + const attack = buildExponentialAttack(nfa, sccs); + if (attack !== null) { + console.log({ attack }); + } + return sccs.some((scc) => { // 遷移元と遷移先が同じ遷移が複数存在するかを検出 for (const source of scc.stateList) { diff --git a/src/ida.ts b/src/ida.ts index 1300ccf..92a9310 100644 --- a/src/ida.ts +++ b/src/ida.ts @@ -3,7 +3,7 @@ import { TripleDirectProductGraph, Message, State } from './types'; export function showMessageIDA(tdps: TripleDirectProductGraph[]): Message { if (tdps.some((tdp) => isIDA(tdp))) { - return { status: 'Vulnerable', message: 'Detected IDA.' }; + return { status: 'Vulnerable', message: 'Detected IDA.', attack: '' }; } else { return { status: 'Safe', message: "Don't have IDA." }; } diff --git a/src/index.ts b/src/index.ts index 80ab02f..fe60f2b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,7 +12,7 @@ export function detectEDA(src: string, flags?: string): Message { const nfa = eliminateEpsilonTransitions(enfa); const sccs = buildStronglyConnectedComponents(nfa); const dps = buildDirectProductGraphs(sccs); - return showMessageEDA(dps); + return showMessageEDA(nfa, dps); } catch (e) { if (e instanceof Error) { return { status: 'Error', message: e.message }; diff --git a/src/test.ts b/src/test.ts index e63df19..9c6fa23 100644 --- a/src/test.ts +++ b/src/test.ts @@ -45,7 +45,7 @@ function main(): void { for (const dp of dps) { console.log(toDOT(dp)); } - console.log(`//`, src, `has EDA?: `, showMessageEDA(dps)); + console.log(`//`, src, `has EDA?: `, showMessageEDA(nfa, dps)); console.log('//', src, `triple direct product`); const tdps = buildTripleDirectProductGraphs(sccs, nfa); for (const tdp of tdps) { diff --git a/src/types.ts b/src/types.ts index cbff561..ee5dbf0 100644 --- a/src/types.ts +++ b/src/types.ts @@ -111,7 +111,17 @@ export type NullableTransition = destination: State; }; -export type Message = { - status: 'Safe' | 'Vulnerable' | 'Error'; - message: string; -}; +export type Message = + | { + status: 'Safe'; + message: string; + } + | { + status: 'Vulnerable'; + message: string; + attack: string; + } + | { + status: 'Error'; + message: string; + }; From 3e3884e8e92d9f7a747eb82d3dd997262429c8f9 Mon Sep 17 00:00:00 2001 From: n4o847 <22975590+n4o847@users.noreply.github.com> Date: Fri, 27 Nov 2020 19:10:09 +0900 Subject: [PATCH 04/20] =?UTF-8?q?State=20=E3=81=AE=E6=96=B0=E5=AE=9A?= =?UTF-8?q?=E7=BE=A9=E3=82=92=E7=B5=B1=E5=90=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/attack.ts | 8 ++------ src/ida.ts | 3 ++- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/attack.ts b/src/attack.ts index 6c0aa0a..e3342d4 100644 --- a/src/attack.ts +++ b/src/attack.ts @@ -1,10 +1,6 @@ import { getLeftState, getRightState } from './directProduct'; -import { - NonEpsilonNFA, - State, - Char, - StronglyConnectedComponentGraph, -} from './types'; +import { State } from './state'; +import { NonEpsilonNFA, Char, StronglyConnectedComponentGraph } from './types'; export function buildExponentialAttack( nfa: NonEpsilonNFA, diff --git a/src/ida.ts b/src/ida.ts index 92a9310..e24db66 100644 --- a/src/ida.ts +++ b/src/ida.ts @@ -1,5 +1,6 @@ import { buildStronglyConnectedComponents } from './scc'; -import { TripleDirectProductGraph, Message, State } from './types'; +import { State } from './state'; +import { TripleDirectProductGraph, Message } from './types'; export function showMessageIDA(tdps: TripleDirectProductGraph[]): Message { if (tdps.some((tdp) => isIDA(tdp))) { From 5b86967bdefe274c3555ae177c05a82d435a42fa Mon Sep 17 00:00:00 2001 From: n4o847 <22975590+n4o847@users.noreply.github.com> Date: Fri, 27 Nov 2020 19:56:13 +0900 Subject: [PATCH 05/20] =?UTF-8?q?=E6=94=BB=E6=92=83=E6=96=87=E5=AD=97?= =?UTF-8?q?=E5=88=97=E6=9C=AB=E5=B0=BE=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/attack.ts | 63 ++++++++++++++++++++------------------------------- 1 file changed, 25 insertions(+), 38 deletions(-) diff --git a/src/attack.ts b/src/attack.ts index e3342d4..a85ea0e 100644 --- a/src/attack.ts +++ b/src/attack.ts @@ -26,22 +26,17 @@ export function buildExponentialAttack( } if (visited) { let attack = ''; - attack += pathString( - nfa, - nfa.initialState, - new Set([sourceLeft]), - nullChar, - ); + attack += pathString(nfa, nfa.initialState, sourceLeft, nullChar); { const loop = char ?? nullChar; attack += loop.repeat(20); } - attack += pathString( - nfa, - sourceLeft, - nfa.acceptingStateSet, - nullChar, - ); + for (const char of [...nfa.alphabet, null]) { + if (nfa.transitions.get(sourceLeft, char).length === 0) { + attack += char ?? nullChar; + break; + } + } return attack; } visited = true; @@ -54,24 +49,19 @@ export function buildExponentialAttack( const viaRight = getRightState(viaPair); if (viaLeft !== viaRight && viaLeft !== sourceLeft) { let attack = ''; - attack += pathString( - nfa, - nfa.initialState, - new Set([sourceLeft]), - nullChar, - ); + attack += pathString(nfa, nfa.initialState, sourceLeft, nullChar); { let loop = ''; - loop += pathString(nfa, sourceLeft, new Set([viaLeft]), nullChar); - loop += pathString(nfa, viaLeft, new Set([sourceLeft]), nullChar); + loop += pathString(nfa, sourceLeft, viaLeft, nullChar); + loop += pathString(nfa, viaLeft, sourceLeft, nullChar); attack += loop.repeat(20); } - attack += pathString( - nfa, - sourceLeft, - nfa.acceptingStateSet, - nullChar, - ); + for (const char of [...nfa.alphabet, null]) { + if (nfa.transitions.get(sourceLeft, char).length === 0) { + attack += char ?? nullChar; + break; + } + } return attack; } } @@ -104,7 +94,7 @@ function getCharForNull(alphabet: Set): string { function pathString( nfa: NonEpsilonNFA, source: State, - destinations: Set, + destination: State, nullChar: string, ): string { /** その状態にどの状態からどの遷移でたどり着けるか */ @@ -113,12 +103,9 @@ function pathString( const queue: State[] = []; queue.push(source); - let foundDestination: State | null = null; - while (queue.length !== 0) { const q = queue.shift()!; - if (destinations.has(q)) { - foundDestination = q; + if (q === destination) { break; } for (const char of nfa.alphabet) { @@ -133,13 +120,13 @@ function pathString( } const path: string[] = []; - if (foundDestination !== null) { - for (let q = foundDestination; q !== source; ) { - const [prevState, char] = referrer.get(q)!; - path.push(char ?? nullChar); - q = prevState; - } - path.reverse(); + + for (let q = destination; q !== source; ) { + const [prevState, char] = referrer.get(q)!; + path.push(char ?? nullChar); + q = prevState; } + path.reverse(); + return path.join(''); } From c7f3001b816ff9e55e34f3b6d9c5ea1fd3007e8f Mon Sep 17 00:00:00 2001 From: n4o847 <22975590+n4o847@users.noreply.github.com> Date: Fri, 27 Nov 2020 22:38:33 +0900 Subject: [PATCH 06/20] =?UTF-8?q?eda.ts=20=E3=81=A8=20attack.ts=20?= =?UTF-8?q?=E3=82=92=E7=B5=B1=E5=90=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/attack.ts | 142 +++++++++++++++++++++++++++++++------------------- src/eda.ts | 44 ++++------------ 2 files changed, 97 insertions(+), 89 deletions(-) diff --git a/src/attack.ts b/src/attack.ts index a85ea0e..fd5b603 100644 --- a/src/attack.ts +++ b/src/attack.ts @@ -2,13 +2,19 @@ import { getLeftState, getRightState } from './directProduct'; import { State } from './state'; import { NonEpsilonNFA, Char, StronglyConnectedComponentGraph } from './types'; -export function buildExponentialAttack( - nfa: NonEpsilonNFA, - sccs: StronglyConnectedComponentGraph[], -): string | null { - const nullChar = getCharForNull(nfa.alphabet); +export class Attacker { + private nfa: NonEpsilonNFA; + private nullChar: string; + + constructor(nfa: NonEpsilonNFA) { + this.nfa = nfa; + this.nullChar = this.getCharForNull(); + } - for (const scc of sccs) { + /** + * EDA 構造を見つけ、それに対する攻撃文字列を生成する。 + */ + findExponentialAttack(scc: StronglyConnectedComponentGraph): string | null { for (const source of scc.stateList) { const sourceLeft = getLeftState(source); const souceRight = getRightState(source); @@ -19,27 +25,19 @@ export function buildExponentialAttack( // 多重自己ループを検出 for (const char of scc.alphabet) { - let visited = false; - for (const destination of scc.transitions.get(source, char)) { - if (destination !== source) { - continue; - } - if (visited) { - let attack = ''; - attack += pathString(nfa, nfa.initialState, sourceLeft, nullChar); - { - const loop = char ?? nullChar; - attack += loop.repeat(20); - } - for (const char of [...nfa.alphabet, null]) { - if (nfa.transitions.get(sourceLeft, char).length === 0) { - attack += char ?? nullChar; - break; - } - } - return attack; + const selfLoops = scc.transitions + .get(source, char) + .filter((q) => q === source); + + if (selfLoops.length >= 2) { + let attack = ''; + attack += this.pathString(this.nfa.initialState, sourceLeft); + { + const loop = char ?? this.nullChar; + attack += loop.repeat(20); } - visited = true; + attack += this.getSuffix(sourceLeft); + return attack; } } @@ -49,54 +47,88 @@ export function buildExponentialAttack( const viaRight = getRightState(viaPair); if (viaLeft !== viaRight && viaLeft !== sourceLeft) { let attack = ''; - attack += pathString(nfa, nfa.initialState, sourceLeft, nullChar); + attack += this.pathString(this.nfa.initialState, sourceLeft); { let loop = ''; - loop += pathString(nfa, sourceLeft, viaLeft, nullChar); - loop += pathString(nfa, viaLeft, sourceLeft, nullChar); + loop += this.pathString(sourceLeft, viaLeft); + loop += this.pathString(viaLeft, sourceLeft); attack += loop.repeat(20); } - for (const char of [...nfa.alphabet, null]) { - if (nfa.transitions.get(sourceLeft, char).length === 0) { - attack += char ?? nullChar; - break; - } - } + attack += this.getSuffix(sourceLeft); return attack; } } } + + // EDA 検出なし + return null; } - // EDA 検出なし - return null; -} + /** + * IDA 構造を見つけ、それに対する攻撃文字列を生成する。 + */ + findPolynomialAttack(scc: StronglyConnectedComponentGraph): string | null { + // (lq, lq, rq) と (lq, rq, rq) が存在するか + for (const [lq, cq, rq] of scc.stateList.map((s) => s.split('_'))) { + if (lq === cq && scc.stateList.includes(`${lq}_${rq}_${rq}` as State)) { + let attack = ''; + attack += this.pathString(this.nfa.initialState, lq as State); + { + const loop = this.pathString(lq as State, rq as State); + attack += loop.repeat(20); + } + attack += this.getSuffix(rq as State); + return attack; + } + } -export function buildPolynomialAttack(): void { - // unimplemented -} + // IDA 検出なし + return null; + } -/** - * null に対応する文字を探す。 - */ -function getCharForNull(alphabet: Set): string { - for (let c = 0x00; ; c++) { - const s = String.fromCharCode(c); - if (!alphabet.has(s)) { - return s; + /** + * null に対応する文字を探す。 + */ + private getCharForNull(): string { + for (let c = 0x00; ; c++) { + const s = String.fromCharCode(c); + if (!this.nfa.alphabet.has(s)) { + return s; + } + } + } + + /** + * ある状態からある状態まで遷移する文字列を探す。 + */ + private pathString(source: State, destination: State): string { + return findPath(this.nfa, source, destination) + .map((char) => char || this.nullChar) + .join(''); + } + + /** + * (TODO) + * EDA/IDA 構造からバックトラックの起こるような状態へ遷移する⽂字列を探す。 + */ + private getSuffix(source: State): string { + for (const char of [...this.nfa.alphabet, null]) { + if (this.nfa.transitions.get(source, char).length === 0) { + return char ?? this.nullChar; + } } + return ''; } } /** * NFA 上である状態からある状態までの経路 (Char の配列) を幅優先探索で復元する。 */ -function pathString( +function findPath( nfa: NonEpsilonNFA, source: State, destination: State, - nullChar: string, -): string { +): Char[] { /** その状態にどの状態からどの遷移でたどり着けるか */ const referrer = new Map(); @@ -119,14 +151,14 @@ function pathString( } } - const path: string[] = []; + const path: Char[] = []; for (let q = destination; q !== source; ) { const [prevState, char] = referrer.get(q)!; - path.push(char ?? nullChar); + path.push(char); q = prevState; } path.reverse(); - return path.join(''); + return path; } diff --git a/src/eda.ts b/src/eda.ts index e60f94f..a1e7051 100644 --- a/src/eda.ts +++ b/src/eda.ts @@ -1,5 +1,4 @@ -import { buildExponentialAttack } from './attack'; -import { getLeftState, getRightState } from './directProduct'; +import { Attacker } from './attack'; import { buildStronglyConnectedComponents } from './scc'; import { NonEpsilonNFA, DirectProductGraph, Message } from './types'; @@ -10,40 +9,17 @@ export function showMessageEDA( nfa: NonEpsilonNFA, dps: DirectProductGraph[], ): Message { - // 別の経路で同じ文字で移動して自身に戻れたらEDA - if (dps.some((dp) => isEDA(nfa, dp))) { - return { status: 'Vulnerable', message: 'Detected EDA.', attack: '' }; - } else { - return { status: 'Safe', message: "Don't have EDA." }; - } -} + const attacker = new Attacker(nfa); -function isEDA(nfa: NonEpsilonNFA, dp: DirectProductGraph): boolean { - const sccs = buildStronglyConnectedComponents(dp); - - const attack = buildExponentialAttack(nfa, sccs); - if (attack !== null) { - console.log({ attack }); - } + for (const dp of dps) { + const sccs = buildStronglyConnectedComponents(dp); - return sccs.some((scc) => { - // 遷移元と遷移先が同じ遷移が複数存在するかを検出 - for (const source of scc.stateList) { - for (const char of scc.alphabet) { - const destinationArray = scc.transitions - .get(source, char) - .filter((d) => d === source); - - if (destinationArray.length >= 2) { - return true; - } + for (const scc of sccs) { + const attack = attacker.findExponentialAttack(scc); + if (attack !== null) { + return { status: 'Vulnerable', message: 'Detected EDA.', attack }; } } - - const lrSame = scc.stateList.filter( - (state) => getLeftState(state) === getRightState(state), - ); - // (n, n), (m, k) (m !== k)が存在(すべて同じじゃないが全て異なるわけではない) - return lrSame.length < scc.stateList.length && lrSame.length > 0; - }); + } + return { status: 'Safe', message: "Don't have EDA." }; } From a8c7ad08591184b60b2d4a5d4c82a0229985b882 Mon Sep 17 00:00:00 2001 From: n4o847 <22975590+n4o847@users.noreply.github.com> Date: Fri, 27 Nov 2020 22:42:27 +0900 Subject: [PATCH 07/20] =?UTF-8?q?ida.ts=20=E3=81=A8=E7=B5=B1=E5=90=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/eda.ts | 4 ++-- src/ida.ts | 35 ++++++++++++++++------------------- src/test.ts | 2 +- 3 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/eda.ts b/src/eda.ts index a1e7051..ba7f138 100644 --- a/src/eda.ts +++ b/src/eda.ts @@ -1,6 +1,6 @@ -import { Attacker } from './attack'; import { buildStronglyConnectedComponents } from './scc'; import { NonEpsilonNFA, DirectProductGraph, Message } from './types'; +import { Attacker } from './attack'; /** * 強連結成分を一つ一つ見ていき、EDAを持つかメッセージを返す @@ -13,7 +13,6 @@ export function showMessageEDA( for (const dp of dps) { const sccs = buildStronglyConnectedComponents(dp); - for (const scc of sccs) { const attack = attacker.findExponentialAttack(scc); if (attack !== null) { @@ -21,5 +20,6 @@ export function showMessageEDA( } } } + return { status: 'Safe', message: "Don't have EDA." }; } diff --git a/src/ida.ts b/src/ida.ts index e24db66..7a903ca 100644 --- a/src/ida.ts +++ b/src/ida.ts @@ -1,25 +1,22 @@ import { buildStronglyConnectedComponents } from './scc'; -import { State } from './state'; -import { TripleDirectProductGraph, Message } from './types'; +import { NonEpsilonNFA, TripleDirectProductGraph, Message } from './types'; +import { Attacker } from './attack'; -export function showMessageIDA(tdps: TripleDirectProductGraph[]): Message { - if (tdps.some((tdp) => isIDA(tdp))) { - return { status: 'Vulnerable', message: 'Detected IDA.', attack: '' }; - } else { - return { status: 'Safe', message: "Don't have IDA." }; - } -} +export function showMessageIDA( + nfa: NonEpsilonNFA, + tdps: TripleDirectProductGraph[], +): Message { + const attacker = new Attacker(nfa); -function isIDA(tdp: TripleDirectProductGraph): boolean { - const sccs = buildStronglyConnectedComponents(tdp); - return sccs.some((scc) => { - // (lq, lq, rq) と (lq, rq, rq) が存在するか - for (const [lq, cq, rq] of scc.stateList.map((s) => s.split('_'))) { - const state = `${lq}_${rq}_${rq}` as State; - if (lq === cq && scc.stateList.includes(state)) { - return true; + for (const tdp of tdps) { + const sccs = buildStronglyConnectedComponents(tdp); + for (const scc of sccs) { + const attack = attacker.findPolynomialAttack(scc); + if (attack !== null) { + return { status: 'Vulnerable', message: 'Detected IDA.', attack }; } } - return false; - }); + } + + return { status: 'Safe', message: "Don't have IDA." }; } diff --git a/src/test.ts b/src/test.ts index 9c6fa23..f76fb6f 100644 --- a/src/test.ts +++ b/src/test.ts @@ -51,7 +51,7 @@ function main(): void { for (const tdp of tdps) { console.log(toDOT(tdp)); } - console.log(`//`, src, `has IDA?: `, showMessageIDA(tdps)); + console.log(`//`, src, `has IDA?: `, showMessageIDA(nfa, tdps)); console.log(`//`, src, `reversed`); const rnfa = reverseNFA(nfa); console.log(toDOT(rnfa)); From f9279517e23b10ac368b8ef32eb993fc172a3a73 Mon Sep 17 00:00:00 2001 From: n4o847 <22975590+n4o847@users.noreply.github.com> Date: Fri, 27 Nov 2020 23:17:21 +0900 Subject: [PATCH 08/20] =?UTF-8?q?=E7=B4=B0=E3=81=8B=E3=81=84=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/attack.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/attack.ts b/src/attack.ts index fd5b603..b079d86 100644 --- a/src/attack.ts +++ b/src/attack.ts @@ -31,7 +31,7 @@ export class Attacker { if (selfLoops.length >= 2) { let attack = ''; - attack += this.pathString(this.nfa.initialState, sourceLeft); + attack += this.getPathString(this.nfa.initialState, sourceLeft); { const loop = char ?? this.nullChar; attack += loop.repeat(20); @@ -47,11 +47,11 @@ export class Attacker { const viaRight = getRightState(viaPair); if (viaLeft !== viaRight && viaLeft !== sourceLeft) { let attack = ''; - attack += this.pathString(this.nfa.initialState, sourceLeft); + attack += this.getPathString(this.nfa.initialState, sourceLeft); { let loop = ''; - loop += this.pathString(sourceLeft, viaLeft); - loop += this.pathString(viaLeft, sourceLeft); + loop += this.getPathString(sourceLeft, viaLeft); + loop += this.getPathString(viaLeft, sourceLeft); attack += loop.repeat(20); } attack += this.getSuffix(sourceLeft); @@ -72,9 +72,9 @@ export class Attacker { for (const [lq, cq, rq] of scc.stateList.map((s) => s.split('_'))) { if (lq === cq && scc.stateList.includes(`${lq}_${rq}_${rq}` as State)) { let attack = ''; - attack += this.pathString(this.nfa.initialState, lq as State); + attack += this.getPathString(this.nfa.initialState, lq as State); { - const loop = this.pathString(lq as State, rq as State); + const loop = this.getPathString(lq as State, rq as State); attack += loop.repeat(20); } attack += this.getSuffix(rq as State); @@ -101,9 +101,9 @@ export class Attacker { /** * ある状態からある状態まで遷移する文字列を探す。 */ - private pathString(source: State, destination: State): string { + private getPathString(source: State, destination: State): string { return findPath(this.nfa, source, destination) - .map((char) => char || this.nullChar) + .map((char) => char ?? this.nullChar) .join(''); } From 93d698be32821f12605f6c59a491d1c3f46c847b Mon Sep 17 00:00:00 2001 From: n4o847 <22975590+n4o847@users.noreply.github.com> Date: Sat, 28 Nov 2020 15:15:25 +0900 Subject: [PATCH 09/20] =?UTF-8?q?=E6=94=BB=E6=92=83=E6=96=87=E5=AD=97?= =?UTF-8?q?=E5=88=97=E3=81=AE=20suffix=20=E3=81=AE=E6=8E=A2=E7=B4=A2?= =?UTF-8?q?=E3=82=92=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/attack.ts | 63 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/src/attack.ts b/src/attack.ts index b079d86..69c9005 100644 --- a/src/attack.ts +++ b/src/attack.ts @@ -1,6 +1,7 @@ import { getLeftState, getRightState } from './directProduct'; import { State } from './state'; import { NonEpsilonNFA, Char, StronglyConnectedComponentGraph } from './types'; +import { intersect } from './util'; export class Attacker { private nfa: NonEpsilonNFA; @@ -108,21 +109,17 @@ export class Attacker { } /** - * (TODO) * EDA/IDA 構造からバックトラックの起こるような状態へ遷移する⽂字列を探す。 */ private getSuffix(source: State): string { - for (const char of [...this.nfa.alphabet, null]) { - if (this.nfa.transitions.get(source, char).length === 0) { - return char ?? this.nullChar; - } - } - return ''; + return findUnacceptablePath(this.nfa, source) + .map((char) => char ?? this.nullChar) + .join(''); } } /** - * NFA 上である状態からある状態までの経路 (Char の配列) を幅優先探索で復元する。 + * NFA 上である状態からある状態までの経路 (Char の配列) を幅優先探索する。 */ function findPath( nfa: NonEpsilonNFA, @@ -162,3 +159,53 @@ function findPath( return path; } + +/** + * NFA 上である状態から非受理状態までの経路 (Char の配列) を幅優先探索する。 + */ +function findUnacceptablePath(nfa: NonEpsilonNFA, source: State): Char[] { + /** その状態にどの状態からどの遷移でたどり着けるか */ + const referrer = new Map(); + + const queue: [State, Set][] = []; + queue.push([source, new Set([source])]); + + let foundDestination: State | null = null; + + while (queue.length !== 0) { + const [q0, qs0] = queue.shift()!; + + if (intersect(qs0, nfa.acceptingStateSet).size === 0) { + foundDestination = q0; + break; + } + + // その他の文字も含める + for (const char of [...nfa.alphabet, null]) { + /** qs0 の各状態から char で到達可能な状態の集合の和集合 */ + const qs1 = new Set( + Array.from(qs0).flatMap((q) => nfa.transitions.get(q, char)), + ); + + const q1 = State.fromSet(qs1); + + if (!referrer.has(q1)) { + referrer.set(q1, [q0, char]); + queue.push([q1, qs1]); + } + } + } + + const path: Char[] = []; + + if (foundDestination !== null) { + for (let q = foundDestination; q !== source; ) { + const [prevState, char] = referrer.get(q)!; + path.push(char); + q = prevState; + } + path.reverse(); + } + + return path; +} From 586369afedecce1e56aa5df1358e1c058d025bdd Mon Sep 17 00:00:00 2001 From: n4o847 <22975590+n4o847@users.noreply.github.com> Date: Sat, 28 Nov 2020 15:30:40 +0900 Subject: [PATCH 10/20] =?UTF-8?q?=E7=B5=8C=E8=B7=AF=E6=8E=A2=E7=B4=A2?= =?UTF-8?q?=E3=81=AE=E7=B4=B0=E3=81=8B=E3=81=84=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/attack.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/attack.ts b/src/attack.ts index 69c9005..cb110b2 100644 --- a/src/attack.ts +++ b/src/attack.ts @@ -168,7 +168,7 @@ function findUnacceptablePath(nfa: NonEpsilonNFA, source: State): Char[] { const referrer = new Map(); const queue: [State, Set][] = []; - queue.push([source, new Set([source])]); + queue.push([State.fromSet(new Set([source])), new Set([source])]); let foundDestination: State | null = null; From 2db5a4963a73b78e60f488fbafabe57a48b6477f Mon Sep 17 00:00:00 2001 From: n4o847 <22975590+n4o847@users.noreply.github.com> Date: Sun, 29 Nov 2020 14:15:40 +0900 Subject: [PATCH 11/20] =?UTF-8?q?findUnacceptablePath=20=E3=81=AE=E7=B5=82?= =?UTF-8?q?=E4=BA=86=E6=9D=A1=E4=BB=B6=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/attack.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/attack.ts b/src/attack.ts index cb110b2..a626258 100644 --- a/src/attack.ts +++ b/src/attack.ts @@ -168,10 +168,14 @@ function findUnacceptablePath(nfa: NonEpsilonNFA, source: State): Char[] { const referrer = new Map(); const queue: [State, Set][] = []; - queue.push([State.fromSet(new Set([source])), new Set([source])]); + + const initialStateSet = new Set([source]); + const initialState = State.fromSet(initialStateSet); let foundDestination: State | null = null; + queue.push([initialState, initialStateSet]); + while (queue.length !== 0) { const [q0, qs0] = queue.shift()!; @@ -199,7 +203,7 @@ function findUnacceptablePath(nfa: NonEpsilonNFA, source: State): Char[] { const path: Char[] = []; if (foundDestination !== null) { - for (let q = foundDestination; q !== source; ) { + for (let q = foundDestination; q !== initialState; ) { const [prevState, char] = referrer.get(q)!; path.push(char); q = prevState; From 85289e880636ad2f2d060ca4f45614a85af540d1 Mon Sep 17 00:00:00 2001 From: n4o847 <22975590+n4o847@users.noreply.github.com> Date: Tue, 1 Dec 2020 22:44:29 +0900 Subject: [PATCH 12/20] =?UTF-8?q?=E7=8A=B6=E6=85=8B=E3=82=92=E6=96=B0?= =?UTF-8?q?=E3=81=97=E3=81=84=E5=AE=9A=E7=BE=A9=E3=81=AB=E7=A7=BB=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/attack.ts | 30 +++++++++++++++++------------- src/eda.ts | 2 +- src/ida.ts | 2 +- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/attack.ts b/src/attack.ts index a626258..8880c38 100644 --- a/src/attack.ts +++ b/src/attack.ts @@ -1,4 +1,3 @@ -import { getLeftState, getRightState } from './directProduct'; import { State } from './state'; import { NonEpsilonNFA, Char, StronglyConnectedComponentGraph } from './types'; import { intersect } from './util'; @@ -15,12 +14,14 @@ export class Attacker { /** * EDA 構造を見つけ、それに対する攻撃文字列を生成する。 */ - findExponentialAttack(scc: StronglyConnectedComponentGraph): string | null { + findExponentialAttack( + scc: StronglyConnectedComponentGraph, + table: Map, + ): string | null { for (const source of scc.stateList) { - const sourceLeft = getLeftState(source); - const souceRight = getRightState(source); + const [sourceLeft, sourceRight] = table.get(source)!; - if (sourceLeft !== souceRight) { + if (sourceLeft !== sourceRight) { continue; } @@ -44,8 +45,8 @@ export class Attacker { // (q1, q1) → (q2, q3) → (q1, q1) where q2 ≠ q3 を検出 for (const viaPair of scc.stateList) { - const viaLeft = getLeftState(viaPair); - const viaRight = getRightState(viaPair); + const [viaLeft, viaRight] = table.get(viaPair)!; + if (viaLeft !== viaRight && viaLeft !== sourceLeft) { let attack = ''; attack += this.getPathString(this.nfa.initialState, sourceLeft); @@ -68,17 +69,20 @@ export class Attacker { /** * IDA 構造を見つけ、それに対する攻撃文字列を生成する。 */ - findPolynomialAttack(scc: StronglyConnectedComponentGraph): string | null { + findPolynomialAttack( + scc: StronglyConnectedComponentGraph, + table: Map, + ): string | null { // (lq, lq, rq) と (lq, rq, rq) が存在するか - for (const [lq, cq, rq] of scc.stateList.map((s) => s.split('_'))) { - if (lq === cq && scc.stateList.includes(`${lq}_${rq}_${rq}` as State)) { + for (const [lq, cq, rq] of scc.stateList.map((s) => table.get(s)!)) { + if (lq === cq && scc.stateList.includes(State.fromTriple([lq, rq, rq]))) { let attack = ''; - attack += this.getPathString(this.nfa.initialState, lq as State); + attack += this.getPathString(this.nfa.initialState, lq); { - const loop = this.getPathString(lq as State, rq as State); + const loop = this.getPathString(lq, rq); attack += loop.repeat(20); } - attack += this.getSuffix(rq as State); + attack += this.getSuffix(rq); return attack; } } diff --git a/src/eda.ts b/src/eda.ts index ba7f138..7e993ee 100644 --- a/src/eda.ts +++ b/src/eda.ts @@ -14,7 +14,7 @@ export function showMessageEDA( for (const dp of dps) { const sccs = buildStronglyConnectedComponents(dp); for (const scc of sccs) { - const attack = attacker.findExponentialAttack(scc); + const attack = attacker.findExponentialAttack(scc, dp.table); if (attack !== null) { return { status: 'Vulnerable', message: 'Detected EDA.', attack }; } diff --git a/src/ida.ts b/src/ida.ts index 7a903ca..8e52269 100644 --- a/src/ida.ts +++ b/src/ida.ts @@ -11,7 +11,7 @@ export function showMessageIDA( for (const tdp of tdps) { const sccs = buildStronglyConnectedComponents(tdp); for (const scc of sccs) { - const attack = attacker.findPolynomialAttack(scc); + const attack = attacker.findPolynomialAttack(scc, tdp.table); if (attack !== null) { return { status: 'Vulnerable', message: 'Detected IDA.', attack }; } From 9638cfa193d869825f926ee7015e836cfebcb785 Mon Sep 17 00:00:00 2001 From: n4o847 <22975590+n4o847@users.noreply.github.com> Date: Tue, 1 Dec 2020 22:47:24 +0900 Subject: [PATCH 13/20] =?UTF-8?q?=E6=94=BB=E6=92=83=E6=96=87=E5=AD=97?= =?UTF-8?q?=E5=88=97=E7=94=9F=E6=88=90=E3=81=AB=20NonEpsilonNFA=20?= =?UTF-8?q?=E3=81=A7=E3=81=AF=E3=81=AA=E3=81=8F=20PrunedNFA=20=E3=82=92?= =?UTF-8?q?=E4=BD=BF=E3=81=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/attack.ts | 48 +++++++++++++++++++++++++++--------------------- src/eda.ts | 6 +++--- src/ida.ts | 6 +++--- src/test.ts | 4 ++-- 4 files changed, 35 insertions(+), 29 deletions(-) diff --git a/src/attack.ts b/src/attack.ts index 8880c38..3aa1052 100644 --- a/src/attack.ts +++ b/src/attack.ts @@ -1,13 +1,13 @@ import { State } from './state'; -import { NonEpsilonNFA, Char, StronglyConnectedComponentGraph } from './types'; +import { Char, PrunedNFA, StronglyConnectedComponentGraph } from './types'; import { intersect } from './util'; export class Attacker { - private nfa: NonEpsilonNFA; + private pnfa: PrunedNFA; private nullChar: string; - constructor(nfa: NonEpsilonNFA) { - this.nfa = nfa; + constructor(pnfa: PrunedNFA) { + this.pnfa = pnfa; this.nullChar = this.getCharForNull(); } @@ -33,7 +33,7 @@ export class Attacker { if (selfLoops.length >= 2) { let attack = ''; - attack += this.getPathString(this.nfa.initialState, sourceLeft); + attack += this.getPathString(this.pnfa.initialStateSet, sourceLeft); { const loop = char ?? this.nullChar; attack += loop.repeat(20); @@ -49,7 +49,7 @@ export class Attacker { if (viaLeft !== viaRight && viaLeft !== sourceLeft) { let attack = ''; - attack += this.getPathString(this.nfa.initialState, sourceLeft); + attack += this.getPathString(this.pnfa.initialStateSet, sourceLeft); { let loop = ''; loop += this.getPathString(sourceLeft, viaLeft); @@ -77,7 +77,7 @@ export class Attacker { for (const [lq, cq, rq] of scc.stateList.map((s) => table.get(s)!)) { if (lq === cq && scc.stateList.includes(State.fromTriple([lq, rq, rq]))) { let attack = ''; - attack += this.getPathString(this.nfa.initialState, lq); + attack += this.getPathString(this.pnfa.initialStateSet, lq); { const loop = this.getPathString(lq, rq); attack += loop.repeat(20); @@ -97,7 +97,7 @@ export class Attacker { private getCharForNull(): string { for (let c = 0x00; ; c++) { const s = String.fromCharCode(c); - if (!this.nfa.alphabet.has(s)) { + if (!this.pnfa.alphabet.has(s)) { return s; } } @@ -106,8 +106,11 @@ export class Attacker { /** * ある状態からある状態まで遷移する文字列を探す。 */ - private getPathString(source: State, destination: State): string { - return findPath(this.nfa, source, destination) + private getPathString( + source: State | Set, + destination: State, + ): string { + return findPath(this.pnfa, source, destination) .map((char) => char ?? this.nullChar) .join(''); } @@ -116,7 +119,7 @@ export class Attacker { * EDA/IDA 構造からバックトラックの起こるような状態へ遷移する⽂字列を探す。 */ private getSuffix(source: State): string { - return findUnacceptablePath(this.nfa, source) + return findUnacceptablePath(this.pnfa, source) .map((char) => char ?? this.nullChar) .join(''); } @@ -126,23 +129,26 @@ export class Attacker { * NFA 上である状態からある状態までの経路 (Char の配列) を幅優先探索する。 */ function findPath( - nfa: NonEpsilonNFA, - source: State, + pnfa: PrunedNFA, + source: State | Set, destination: State, ): Char[] { /** その状態にどの状態からどの遷移でたどり着けるか */ const referrer = new Map(); const queue: State[] = []; - queue.push(source); + + const initialStateSet = new Set(source instanceof Set ? source : [source]); + + queue.push(...initialStateSet); while (queue.length !== 0) { const q = queue.shift()!; if (q === destination) { break; } - for (const char of nfa.alphabet) { - for (const d of nfa.transitions.get(q, char)) { + for (const char of pnfa.alphabet) { + for (const d of pnfa.transitions.get(q, char)) { if (referrer.has(d)) { continue; } @@ -154,7 +160,7 @@ function findPath( const path: Char[] = []; - for (let q = destination; q !== source; ) { + for (let q = destination; !initialStateSet.has(q); ) { const [prevState, char] = referrer.get(q)!; path.push(char); q = prevState; @@ -167,7 +173,7 @@ function findPath( /** * NFA 上である状態から非受理状態までの経路 (Char の配列) を幅優先探索する。 */ -function findUnacceptablePath(nfa: NonEpsilonNFA, source: State): Char[] { +function findUnacceptablePath(pnfa: PrunedNFA, source: State): Char[] { /** その状態にどの状態からどの遷移でたどり着けるか */ const referrer = new Map(); @@ -183,16 +189,16 @@ function findUnacceptablePath(nfa: NonEpsilonNFA, source: State): Char[] { while (queue.length !== 0) { const [q0, qs0] = queue.shift()!; - if (intersect(qs0, nfa.acceptingStateSet).size === 0) { + if (intersect(qs0, pnfa.acceptingStateSet).size === 0) { foundDestination = q0; break; } // その他の文字も含める - for (const char of [...nfa.alphabet, null]) { + for (const char of [...pnfa.alphabet, null]) { /** qs0 の各状態から char で到達可能な状態の集合の和集合 */ const qs1 = new Set( - Array.from(qs0).flatMap((q) => nfa.transitions.get(q, char)), + Array.from(qs0).flatMap((q) => pnfa.transitions.get(q, char)), ); const q1 = State.fromSet(qs1); diff --git a/src/eda.ts b/src/eda.ts index 7e993ee..056b89c 100644 --- a/src/eda.ts +++ b/src/eda.ts @@ -1,15 +1,15 @@ import { buildStronglyConnectedComponents } from './scc'; -import { NonEpsilonNFA, DirectProductGraph, Message } from './types'; +import { PrunedNFA, DirectProductGraph, Message } from './types'; import { Attacker } from './attack'; /** * 強連結成分を一つ一つ見ていき、EDAを持つかメッセージを返す */ export function showMessageEDA( - nfa: NonEpsilonNFA, + pnfa: PrunedNFA, dps: DirectProductGraph[], ): Message { - const attacker = new Attacker(nfa); + const attacker = new Attacker(pnfa); for (const dp of dps) { const sccs = buildStronglyConnectedComponents(dp); diff --git a/src/ida.ts b/src/ida.ts index 8e52269..deded9e 100644 --- a/src/ida.ts +++ b/src/ida.ts @@ -1,12 +1,12 @@ import { buildStronglyConnectedComponents } from './scc'; -import { NonEpsilonNFA, TripleDirectProductGraph, Message } from './types'; +import { PrunedNFA, TripleDirectProductGraph, Message } from './types'; import { Attacker } from './attack'; export function showMessageIDA( - nfa: NonEpsilonNFA, + pnfa: PrunedNFA, tdps: TripleDirectProductGraph[], ): Message { - const attacker = new Attacker(nfa); + const attacker = new Attacker(pnfa); for (const tdp of tdps) { const sccs = buildStronglyConnectedComponents(tdp); diff --git a/src/test.ts b/src/test.ts index c898ddc..e2ea9d4 100644 --- a/src/test.ts +++ b/src/test.ts @@ -52,9 +52,9 @@ function main(): void { console.log(toDOT(lcnfa)); const sccs = buildStronglyConnectedComponents(lcnfa); const dps = buildDirectProductGraphs(sccs); - console.log(`//`, src, `has EDA?: `, showMessageEDA(nfa, dps)); + console.log(`//`, src, `has EDA?: `, showMessageEDA(lcnfa, dps)); const tdps = buildTripleDirectProductGraphs(sccs, lcnfa); - console.log(`//`, src, `has IDA?: `, showMessageIDA(nfa, tdps)); + console.log(`//`, src, `has IDA?: `, showMessageIDA(lcnfa, tdps)); } } From 6343bed3d4f1334469b0e2debf9ddd9ff620caa3 Mon Sep 17 00:00:00 2001 From: n4o847 <22975590+n4o847@users.noreply.github.com> Date: Tue, 1 Dec 2020 22:49:03 +0900 Subject: [PATCH 14/20] =?UTF-8?q?=E5=A4=89=E6=95=B0=E5=90=8D=E5=A4=89?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/test.ts b/src/test.ts index e2ea9d4..8451bb5 100644 --- a/src/test.ts +++ b/src/test.ts @@ -48,13 +48,13 @@ function main(): void { const dfa = determinize(rnfa); console.log(toDOT(dfa)); console.log(`//`, src, `pruned`); - const lcnfa = prune(nfa, dfa); - console.log(toDOT(lcnfa)); - const sccs = buildStronglyConnectedComponents(lcnfa); + const pnfa = prune(nfa, dfa); + console.log(toDOT(pnfa)); + const sccs = buildStronglyConnectedComponents(pnfa); const dps = buildDirectProductGraphs(sccs); - console.log(`//`, src, `has EDA?: `, showMessageEDA(lcnfa, dps)); - const tdps = buildTripleDirectProductGraphs(sccs, lcnfa); - console.log(`//`, src, `has IDA?: `, showMessageIDA(lcnfa, tdps)); + console.log(`//`, src, `has EDA?: `, showMessageEDA(pnfa, dps)); + const tdps = buildTripleDirectProductGraphs(sccs, pnfa); + console.log(`//`, src, `has IDA?: `, showMessageIDA(pnfa, tdps)); } } From a69600718c7081d5229ffb9e8fae5eb6fb99c367 Mon Sep 17 00:00:00 2001 From: n4o847 <22975590+n4o847@users.noreply.github.com> Date: Tue, 1 Dec 2020 22:57:09 +0900 Subject: [PATCH 15/20] =?UTF-8?q?index.ts=20=E3=82=92=20test.ts=20?= =?UTF-8?q?=E3=81=AB=E6=8F=83=E3=81=88=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.ts | 18 ++++++++++++------ src/test.ts | 6 +++--- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/index.ts b/src/index.ts index 315bb17..082aa1b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,13 @@ import { Parser } from 'rerejs'; +import { buildEpsilonNFA } from './enfa'; +import { eliminateEpsilonTransitions } from './nfa'; +import { reverseNFA, determinize } from './dfa'; +import { prune } from './pruning'; +import { buildStronglyConnectedComponents } from './scc'; import { buildDirectProductGraphs } from './directProduct'; import { showMessageEDA } from './eda'; import { buildTripleDirectProductGraphs } from './tripleDirectProduct'; import { showMessageIDA } from './ida'; -import { buildEpsilonNFA, eliminateEpsilonTransitions } from './lib'; -import { buildStronglyConnectedComponents } from './scc'; import { Message } from './types'; export function detectReDoS(src: string, flags?: string): Message { @@ -12,14 +15,17 @@ export function detectReDoS(src: string, flags?: string): Message { const pat = new Parser(src, flags).parse(); const enfa = buildEpsilonNFA(pat); const nfa = eliminateEpsilonTransitions(enfa); - const sccs = buildStronglyConnectedComponents(nfa); + const rnfa = reverseNFA(nfa); + const dfa = determinize(rnfa); + const pnfa = prune(nfa, dfa); + const sccs = buildStronglyConnectedComponents(pnfa); const dps = buildDirectProductGraphs(sccs); - const messageEDA = showMessageEDA(nfa, dps); + const messageEDA = showMessageEDA(pnfa, dps); if (messageEDA.status === 'Vulnerable') { return messageEDA; } - const tdps = buildTripleDirectProductGraphs(sccs, nfa); - const messageIDA = showMessageIDA(nfa, tdps); + const tdps = buildTripleDirectProductGraphs(sccs, pnfa); + const messageIDA = showMessageIDA(pnfa, tdps); if (messageIDA.status === 'Vulnerable') { return messageIDA; } diff --git a/src/test.ts b/src/test.ts index 8451bb5..8114703 100644 --- a/src/test.ts +++ b/src/test.ts @@ -2,13 +2,13 @@ import { Parser } from 'rerejs'; import { buildEpsilonNFA } from './enfa'; import { eliminateEpsilonTransitions } from './nfa'; import { reverseNFA, determinize } from './dfa'; -import { toDOT } from './viz'; -import { buildDirectProductGraphs } from './directProduct'; +import { prune } from './pruning'; import { buildStronglyConnectedComponents } from './scc'; +import { buildDirectProductGraphs } from './directProduct'; import { showMessageEDA } from './eda'; import { buildTripleDirectProductGraphs } from './tripleDirectProduct'; import { showMessageIDA } from './ida'; -import { prune } from './pruning'; +import { toDOT } from './viz'; function main(): void { const sources: [source: string, flags?: string][] = [ From 9337480b03cb5fa3bee60f95f12ab7d624dd7035 Mon Sep 17 00:00:00 2001 From: n4o847 <22975590+n4o847@users.noreply.github.com> Date: Tue, 1 Dec 2020 22:57:59 +0900 Subject: [PATCH 16/20] =?UTF-8?q?NonEpsilonNFA=20=E3=82=92=20SCCPossibleAu?= =?UTF-8?q?tomaton=20=E3=81=8B=E3=82=89=E5=A4=96=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index 2d7a527..9b5c429 100644 --- a/src/types.ts +++ b/src/types.ts @@ -18,7 +18,6 @@ export type Automaton = | TripleDirectProductGraph; export type SCCPossibleAutomaton = - | NonEpsilonNFA | PrunedNFA | DirectProductGraph | TripleDirectProductGraph; From 6254e5c0142d0bad44cd20f4c064a4b95e6f10f6 Mon Sep 17 00:00:00 2001 From: n4o847 <22975590+n4o847@users.noreply.github.com> Date: Wed, 2 Dec 2020 18:20:01 +0900 Subject: [PATCH 17/20] =?UTF-8?q?suffix=20=E7=94=9F=E6=88=90=E3=81=AB=20DF?= =?UTF-8?q?A=20=E3=82=92=E4=BD=BF=E3=81=86=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/attack.ts | 15 +++++++++++---- src/eda.ts | 5 +++-- src/ida.ts | 5 +++-- src/index.ts | 4 ++-- src/test.ts | 4 ++-- 5 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/attack.ts b/src/attack.ts index 3aa1052..19cf74d 100644 --- a/src/attack.ts +++ b/src/attack.ts @@ -1,13 +1,15 @@ import { State } from './state'; -import { Char, PrunedNFA, StronglyConnectedComponentGraph } from './types'; +import { Char, DFA, PrunedNFA, StronglyConnectedComponentGraph } from './types'; import { intersect } from './util'; export class Attacker { private pnfa: PrunedNFA; + private dfa: DFA; private nullChar: string; - constructor(pnfa: PrunedNFA) { + constructor(pnfa: PrunedNFA, dfa: DFA) { this.pnfa = pnfa; + this.dfa = dfa; this.nullChar = this.getCharForNull(); } @@ -119,8 +121,13 @@ export class Attacker { * EDA/IDA 構造からバックトラックの起こるような状態へ遷移する⽂字列を探す。 */ private getSuffix(source: State): string { - return findUnacceptablePath(this.pnfa, source) + return findPath( + this.dfa, + this.dfa.initialState, + this.pnfa.table.get(source)![1], + ) .map((char) => char ?? this.nullChar) + .reverse() .join(''); } } @@ -129,7 +136,7 @@ export class Attacker { * NFA 上である状態からある状態までの経路 (Char の配列) を幅優先探索する。 */ function findPath( - pnfa: PrunedNFA, + pnfa: PrunedNFA | DFA, source: State | Set, destination: State, ): Char[] { diff --git a/src/eda.ts b/src/eda.ts index 056b89c..fe6ba4c 100644 --- a/src/eda.ts +++ b/src/eda.ts @@ -1,5 +1,5 @@ import { buildStronglyConnectedComponents } from './scc'; -import { PrunedNFA, DirectProductGraph, Message } from './types'; +import { PrunedNFA, DFA, DirectProductGraph, Message } from './types'; import { Attacker } from './attack'; /** @@ -7,9 +7,10 @@ import { Attacker } from './attack'; */ export function showMessageEDA( pnfa: PrunedNFA, + dfa: DFA, dps: DirectProductGraph[], ): Message { - const attacker = new Attacker(pnfa); + const attacker = new Attacker(pnfa, dfa); for (const dp of dps) { const sccs = buildStronglyConnectedComponents(dp); diff --git a/src/ida.ts b/src/ida.ts index deded9e..c31196e 100644 --- a/src/ida.ts +++ b/src/ida.ts @@ -1,12 +1,13 @@ import { buildStronglyConnectedComponents } from './scc'; -import { PrunedNFA, TripleDirectProductGraph, Message } from './types'; +import { PrunedNFA, DFA, TripleDirectProductGraph, Message } from './types'; import { Attacker } from './attack'; export function showMessageIDA( pnfa: PrunedNFA, + dfa: DFA, tdps: TripleDirectProductGraph[], ): Message { - const attacker = new Attacker(pnfa); + const attacker = new Attacker(pnfa, dfa); for (const tdp of tdps) { const sccs = buildStronglyConnectedComponents(tdp); diff --git a/src/index.ts b/src/index.ts index 082aa1b..7c81e48 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,12 +20,12 @@ export function detectReDoS(src: string, flags?: string): Message { const pnfa = prune(nfa, dfa); const sccs = buildStronglyConnectedComponents(pnfa); const dps = buildDirectProductGraphs(sccs); - const messageEDA = showMessageEDA(pnfa, dps); + const messageEDA = showMessageEDA(pnfa, dfa, dps); if (messageEDA.status === 'Vulnerable') { return messageEDA; } const tdps = buildTripleDirectProductGraphs(sccs, pnfa); - const messageIDA = showMessageIDA(pnfa, tdps); + const messageIDA = showMessageIDA(pnfa, dfa, tdps); if (messageIDA.status === 'Vulnerable') { return messageIDA; } diff --git a/src/test.ts b/src/test.ts index 8114703..fe346b0 100644 --- a/src/test.ts +++ b/src/test.ts @@ -52,9 +52,9 @@ function main(): void { console.log(toDOT(pnfa)); const sccs = buildStronglyConnectedComponents(pnfa); const dps = buildDirectProductGraphs(sccs); - console.log(`//`, src, `has EDA?: `, showMessageEDA(pnfa, dps)); + console.log(`//`, src, `has EDA?: `, showMessageEDA(pnfa, dfa, dps)); const tdps = buildTripleDirectProductGraphs(sccs, pnfa); - console.log(`//`, src, `has IDA?: `, showMessageIDA(pnfa, tdps)); + console.log(`//`, src, `has IDA?: `, showMessageIDA(pnfa, dfa, tdps)); } } From 62af9d9893f831c909c01e2a6a55509d92b19474 Mon Sep 17 00:00:00 2001 From: n4o847 <22975590+n4o847@users.noreply.github.com> Date: Wed, 2 Dec 2020 18:31:21 +0900 Subject: [PATCH 18/20] =?UTF-8?q?=E4=B8=8D=E8=A6=81=E3=81=AA=E9=96=A2?= =?UTF-8?q?=E6=95=B0=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/attack.ts | 54 --------------------------------------------------- 1 file changed, 54 deletions(-) diff --git a/src/attack.ts b/src/attack.ts index 19cf74d..345c5a5 100644 --- a/src/attack.ts +++ b/src/attack.ts @@ -176,57 +176,3 @@ function findPath( return path; } - -/** - * NFA 上である状態から非受理状態までの経路 (Char の配列) を幅優先探索する。 - */ -function findUnacceptablePath(pnfa: PrunedNFA, source: State): Char[] { - /** その状態にどの状態からどの遷移でたどり着けるか */ - const referrer = new Map(); - - const queue: [State, Set][] = []; - - const initialStateSet = new Set([source]); - const initialState = State.fromSet(initialStateSet); - - let foundDestination: State | null = null; - - queue.push([initialState, initialStateSet]); - - while (queue.length !== 0) { - const [q0, qs0] = queue.shift()!; - - if (intersect(qs0, pnfa.acceptingStateSet).size === 0) { - foundDestination = q0; - break; - } - - // その他の文字も含める - for (const char of [...pnfa.alphabet, null]) { - /** qs0 の各状態から char で到達可能な状態の集合の和集合 */ - const qs1 = new Set( - Array.from(qs0).flatMap((q) => pnfa.transitions.get(q, char)), - ); - - const q1 = State.fromSet(qs1); - - if (!referrer.has(q1)) { - referrer.set(q1, [q0, char]); - queue.push([q1, qs1]); - } - } - } - - const path: Char[] = []; - - if (foundDestination !== null) { - for (let q = foundDestination; q !== initialState; ) { - const [prevState, char] = referrer.get(q)!; - path.push(char); - q = prevState; - } - path.reverse(); - } - - return path; -} From 3160f4c5895a5a518ab0e9aabedc06f944012c19 Mon Sep 17 00:00:00 2001 From: n4o847 <22975590+n4o847@users.noreply.github.com> Date: Wed, 2 Dec 2020 18:35:20 +0900 Subject: [PATCH 19/20] =?UTF-8?q?=E4=B8=8D=E8=A6=81=E3=81=AA=E3=82=A4?= =?UTF-8?q?=E3=83=B3=E3=83=9D=E3=83=BC=E3=83=88=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/attack.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/attack.ts b/src/attack.ts index 345c5a5..fa20fc4 100644 --- a/src/attack.ts +++ b/src/attack.ts @@ -1,6 +1,5 @@ import { State } from './state'; import { Char, DFA, PrunedNFA, StronglyConnectedComponentGraph } from './types'; -import { intersect } from './util'; export class Attacker { private pnfa: PrunedNFA; From a2af5c1e3af36900dddbe357863de1f9c8e6da21 Mon Sep 17 00:00:00 2001 From: n4o847 <22975590+n4o847@users.noreply.github.com> Date: Wed, 2 Dec 2020 18:46:22 +0900 Subject: [PATCH 20/20] =?UTF-8?q?=E5=A4=89=E6=95=B0=E5=90=8D=E3=82=92?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/attack.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/attack.ts b/src/attack.ts index fa20fc4..625e7ff 100644 --- a/src/attack.ts +++ b/src/attack.ts @@ -132,10 +132,10 @@ export class Attacker { } /** - * NFA 上である状態からある状態までの経路 (Char の配列) を幅優先探索する。 + * オートマトン上である状態からある状態までの経路 (Char の配列) を幅優先探索する。 */ function findPath( - pnfa: PrunedNFA | DFA, + automaton: PrunedNFA | DFA, source: State | Set, destination: State, ): Char[] { @@ -144,17 +144,17 @@ function findPath( const queue: State[] = []; - const initialStateSet = new Set(source instanceof Set ? source : [source]); + const sourceSet = new Set(source instanceof Set ? source : [source]); - queue.push(...initialStateSet); + queue.push(...sourceSet); while (queue.length !== 0) { const q = queue.shift()!; if (q === destination) { break; } - for (const char of pnfa.alphabet) { - for (const d of pnfa.transitions.get(q, char)) { + for (const char of automaton.alphabet) { + for (const d of automaton.transitions.get(q, char)) { if (referrer.has(d)) { continue; } @@ -166,7 +166,7 @@ function findPath( const path: Char[] = []; - for (let q = destination; !initialStateSet.has(q); ) { + for (let q = destination; !sourceSet.has(q); ) { const [prevState, char] = referrer.get(q)!; path.push(char); q = prevState;