Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

--rules-stack broken (now fixed but may still need some TLC) #5722

Open
solardiz opened this issue Mar 27, 2025 · 35 comments · Fixed by #5723
Open

--rules-stack broken (now fixed but may still need some TLC) #5722

solardiz opened this issue Mar 27, 2025 · 35 comments · Fixed by #5723

Comments

@solardiz
Copy link
Member

Looks like we (perhaps I) broke it in the recent week. Before recent changes:

$ echo password > w
$ ./john- -w=w --rules-stack=':[lu]' -stdo
Using default input encoding: UTF-8
password
PASSWORD
2p 0:00:00:00 100.00% (2025-03-27 04:13) 40.00p/s PASSWORD

Now:

$ ./john -w=w --rules-stack=':[lu]' -stdo | head
Using default input encoding: UTF-8
Press 'q' or Ctrl-C to abort, 'h' for help, almost any other key for status
password
password
password
password
password
password
password
password
password
password
@solardiz solardiz added bug regression Bug introduced with certain change(s), causing performance degradation or feature loss labels Mar 27, 2025
@solardiz solardiz added this to the Definitely 2.0.0 milestone Mar 27, 2025
@solardiz
Copy link
Member Author

Reverting d265b3c repairs it.

@magnumripper
Copy link
Member

magnumripper commented Mar 27, 2025

Happens with PRINCE mode too (indeed any mode).

@solardiz
Copy link
Member Author

This hack repairs it (just to narrow down where the problem is):

+++ b/src/rules.c
@@ -1863,8 +1863,11 @@ char *rules_process_stack_all(char *key, rule_stack *ctx)
        rules_stacked_after = 0;
 
        while (ctx->rule) {
-               if ((word = rules_apply(key, ctx->rule->data, -1)))
+               static char last[99];
+               if ((word = rules_apply(key, ctx->rule->data, -1)) && strcmp(word, last)) {
+                       strcpy(last, word);
                        return word;
+               }
                if ((ctx->rule = ctx->rule->next)) {
                        rules_stacked_number++;
                        if (!stack_rules_mute)

@solardiz
Copy link
Member Author

So somehow rules_process_stack_all depended on this check against last word, which rules_apply no longer has. We should remove this dependency. @magnumripper I'm very tired now and I think stacked rules is your code, so maybe you could take over?

@magnumripper
Copy link
Member

I'll look into it right away

@solardiz
Copy link
Member Author

Thank you! I'm sorry I didn't test this feature before merging - I should have, as it was obviously touched by the changes.

@solardiz
Copy link
Member Author

This almost works:

+++ b/src/rules.c
@@ -1863,9 +1863,11 @@ char *rules_process_stack_all(char *key, rule_stack *ctx)
        rules_stacked_after = 0;
 
        while (ctx->rule) {
-               if ((word = rules_apply(key, ctx->rule->data, -1)))
+               char *rule = ctx->rule->data;
+               ctx->rule = ctx->rule->next;
+               if ((word = rules_apply(key, rule, -1)))
                        return word;
-               if ((ctx->rule = ctx->rule->next)) {
+               if (ctx->rule) {
                        rules_stacked_number++;
                        if (!stack_rules_mute)
                            log_event("+ Stacked Rule #%u: '%.100s' accepted",

but doesn't terminate when it should:

$ ./john -w=w --rules-stack=':[ul]' -stdo | head
Using default input encoding: UTF-8
Press 'q' or Ctrl-C to abort, 'h' for help, almost any other key for status
PASSWORD
password
PASSWORD
password
PASSWORD
password
PASSWORD
password
PASSWORD
password

@solardiz
Copy link
Member Author

solardiz commented Mar 27, 2025

This may actually be correct (passes my test):

+++ b/src/rules.c
@@ -1852,25 +1852,19 @@ char *rules_process_stack_all(char *key, rule_stack *ctx)
 {
        char *word;
 
-       if (!ctx->rule) {
+       if (!ctx->rule && !rules_stacked_number)
                ctx->rule = ctx->stack_rule->head;
-               rules_stacked_number = 0;
-               if (!stack_rules_mute)
-                       log_event("+ Stacked Rule #%u: '%.100s' accepted",
-                                 rules_stacked_number + 1, ctx->rule->data);
-       }
 
        rules_stacked_after = 0;
 
        while (ctx->rule) {
-               if ((word = rules_apply(key, ctx->rule->data, -1)))
+               char *rule = ctx->rule->data;
+               rules_stacked_number++;
+               if (!stack_rules_mute)
+                       log_event("+ Stacked Rule #%u: '%.100s' accepted", rules_stacked_number, rule);
+               ctx->rule = ctx->rule->next;
+               if ((word = rules_apply(key, rule, -1)))
                        return word;
-               if ((ctx->rule = ctx->rule->next)) {
-                       rules_stacked_number++;
-                       if (!stack_rules_mute)
-                           log_event("+ Stacked Rule #%u: '%.100s' accepted",
-                                 rules_stacked_number + 1, ctx->rule->data);
-               }
        }
 
        rules_stacked_after = !!(options.flags & (FLG_RULES_CHK | FLG_SINGLE_CHK | FLG_BATCH_CHK));

Edit: correct the logged rule number.

It's also simpler code than we had before. But it relies on two things:

  1. That rules_stacked_number was initialized to 0.
  2. That we increment it at least once (there's at least one stacked rule).

@solardiz
Copy link
Member Author

Also seem to have found a bug we had before, where single mode would log stacked rule number 1 twice. Fix:

+++ b/src/rules.c
@@ -1831,9 +1831,9 @@ char *rules_process_stack(char *key, rule_stack *ctx)
 
        if (!ctx->rule) {
                ctx->rule = ctx->stack_rule->head;
-               rules_stacked_number = 0;
+               rules_stacked_number = 1;
                log_event("+ Stacked Rule #%u: '%.100s' accepted",
-                         rules_stacked_number + 1, ctx->rule->data);
+                         rules_stacked_number, ctx->rule->data);
        }
 
        rules_stacked_after = 0;

magnumripper added a commit to magnumripper/john that referenced this issue Mar 27, 2025
…ssion

Apparently --rules-stack was b0rken all the time, relying on the dupe
suppression to proceed to next rule.  This musy have been ineffective.

Closes openwall#5722
@magnumripper
Copy link
Member

I came up with this

diff --git a/src/rules.c b/src/rules.c
index 5ea260326..78509e546 100644
--- a/src/rules.c
+++ b/src/rules.c
@@ -1858,6 +1858,13 @@ char *rules_process_stack_all(char *key, rule_stack *ctx)
                if (!stack_rules_mute)
                        log_event("+ Stacked Rule #%u: '%.100s' accepted",
                                  rules_stacked_number + 1, ctx->rule->data);
+       } else {
+               if ((ctx->rule = ctx->rule->next)) {
+                       rules_stacked_number++;
+                       if (!stack_rules_mute)
+                               log_event("+ Stacked Rule #%u: '%.100s' accepted",
+                                         rules_stacked_number + 1, ctx->rule->data);
+               }
        }
 
        rules_stacked_after = 0;

@magnumripper
Copy link
Member

This was b0rken all the time - it must have produced one consecutive dupe for each call. Not very effective.

@solardiz
Copy link
Member Author

I came up with this

This is certainly a less invasive change. If it works, let's get it in first, and then think of my code cleanup ideas. Thank you!

@solardiz
Copy link
Member Author

Also seem to have found a bug we had before, where single mode would log stacked rule number 1 twice.

Fixing this may expose something else, as single.c looks at the stacked rule number in a few places. So please review that first.

magnumripper added a commit to magnumripper/john that referenced this issue Mar 27, 2025
Apparently --rules-stack was b0rken all the time, relying on the dupe
suppression to proceed to next rule.  This musy have been ineffective.

Closes openwall#5722
@solardiz
Copy link
Member Author

single.c looks at the stacked rule number in a few places. So please review that first.

At least restore_rule_number would need to be consistent with that fix.

magnumripper added a commit to magnumripper/john that referenced this issue Mar 27, 2025
Apparently --rules-stack was b0rken all the time, relying on the dupe
suppression to proceed to next rule.  This must have been ineffective.

Closes openwall#5722
@magnumripper
Copy link
Member

Added a PR. My code is very likely to just restore functionality to what it was (actually way better than it was) so I think we can merge it as a quick fix. Meanwhile I'll try to wrap my head around your version.

@solardiz
Copy link
Member Author

This may actually be correct (passes my test):

It did pass my test as posted in here, but it failed with actual stacking on top of normal rules. So it's not ready.

@solardiz
Copy link
Member Author

This passes my current tests:

diff --git a/src/cracker.c b/src/cracker.c
index da2c9fc5c..a7db9c7f0 100644
--- a/src/cracker.c
+++ b/src/cracker.c
@@ -1234,6 +1234,7 @@ static int process_key_stack_rules(char *key)
        int ret = 0;
        char *word;
 
+       rules_stacked_number = 0;
        while ((word = rules_process_stack_all(key, &crk_rule_stack)))
                if ((ret = crk_direct_process_key(word)))
                        break;
diff --git a/src/rules.c b/src/rules.c
index 5ea260326..e1378616f 100644
--- a/src/rules.c
+++ b/src/rules.c
@@ -1831,9 +1831,9 @@ char *rules_process_stack(char *key, rule_stack *ctx)
 
        if (!ctx->rule) {
                ctx->rule = ctx->stack_rule->head;
-               rules_stacked_number = 0;
+               rules_stacked_number = 1;
                log_event("+ Stacked Rule #%u: '%.100s' accepted",
-                         rules_stacked_number + 1, ctx->rule->data);
+                         rules_stacked_number, ctx->rule->data);
        }
 
        rules_stacked_after = 0;
@@ -1852,25 +1852,19 @@ char *rules_process_stack_all(char *key, rule_stack *ctx)
 {
        char *word;
 
-       if (!ctx->rule) {
+       if (!ctx->rule && !rules_stacked_number)
                ctx->rule = ctx->stack_rule->head;
-               rules_stacked_number = 0;
-               if (!stack_rules_mute)
-                       log_event("+ Stacked Rule #%u: '%.100s' accepted",
-                                 rules_stacked_number + 1, ctx->rule->data);
-       }
 
        rules_stacked_after = 0;
 
        while (ctx->rule) {
-               if ((word = rules_apply(key, ctx->rule->data, -1)))
+               char *rule = ctx->rule->data;
+               rules_stacked_number++;
+               if (!stack_rules_mute)
+                       log_event("+ Stacked Rule #%u: '%.100s' accepted", rules_stacked_number, rule);
+               ctx->rule = ctx->rule->next;
+               if ((word = rules_apply(key, rule, -1)))
                        return word;
-               if ((ctx->rule = ctx->rule->next)) {
-                       rules_stacked_number++;
-                       if (!stack_rules_mute)
-                           log_event("+ Stacked Rule #%u: '%.100s' accepted",
-                                 rules_stacked_number + 1, ctx->rule->data);
-               }
        }
 
        rules_stacked_after = !!(options.flags & (FLG_RULES_CHK | FLG_SINGLE_CHK | FLG_BATCH_CHK));
diff --git a/src/single.c b/src/single.c
index 4568cf60e..7652604eb 100644
--- a/src/single.c
+++ b/src/single.c
@@ -74,12 +74,12 @@ static int restore_rule_number(void)
 
        if (options.rule_stack) {
                single_rule_stack.rule = single_rule_stack.stack_rule->head;
-               rules_stacked_number = 0;
+               rules_stacked_number = 1;
                while (rules_stacked_number < rec_rule[1])
                        if (!rules_advance_stack(&single_rule_stack, 1))
                                return 1;
                log_event("+ Stacked Rule #%u: '%.100s' accepted",
-                         rules_stacked_number + 1, single_rule_stack.rule->data);
+                         rules_stacked_number, single_rule_stack.rule->data);
        }
 
        return 0;

@magnumripper
Copy link
Member

That looks right to me

@solardiz
Copy link
Member Author

There's room for optimization here, as we check ctx->rule for non-NULL twice on most calls. Also we reset rules_stacked_after on every call. This is written as if it actually looped over rules, but the loop merely skips rules that couldn't apply anything, so we return and re-enter the function frequently.

@magnumripper
Copy link
Member

I'm unsure about the changes to rules_stacked_number. I think I recall having "struggled" with it in the past back and forth between zero-based and one-based.

@solardiz
Copy link
Member Author

Yes, need more review/testing of zero- vs. one-based, especially as it relates to single.c storing those numbers (for what use?)

@magnumripper
Copy link
Member

  •   if (!ctx->rule) {
    
  •   if (!ctx->rule && !rules_stacked_number)
    

Why isn't (!ctx->rule) enough here?

@solardiz
Copy link
Member Author

Why isn't (!ctx->rule) enough here?

We'd end up going over the rules again, infinitely.

@solardiz
Copy link
Member Author

We'd end up going over the rules again, infinitely.

Oh, could use ctx->done for that, like the single mode version of this function uses.

@magnumripper
Copy link
Member

Not sure I understand. The stacked rules are supposed to rewind for each and every candidate. The old code had no such check.

@solardiz
Copy link
Member Author

The old code had no such check.

The old code returned with ctx->rule pointing at the just used rule. The new code returns with it pointing at the next rule, which can be NULL, yet we need to record somewhere that we don't want to repeat for the same input word after that.

magnumripper added a commit to magnumripper/john that referenced this issue Mar 27, 2025
Apparently --rules-stack was b0rken all the time, relying on the dupe
suppression to proceed to next rule.  This must have been ineffective.

Closes openwall#5722
@magnumripper
Copy link
Member

Reopening for considering better code

@magnumripper magnumripper reopened this Mar 27, 2025
@solardiz
Copy link
Member Author

We also should not forget to fix "bug we had before, where single mode would log stacked rule number 1 twice".

@magnumripper
Copy link
Member

Maybe I'm too tired but this sounds backwards to me

/*
 * Advance stacked rules. We iterate main rules first and only then we
 * advance the stacked rules (and rewind the main rules). Repeat until
 * main rules are done with the last stacked rule.
 */
int rules_advance_stack(rule_stack *ctx, int quiet)

The actual operation is we iterate over all stacked rules, then advance the main rules (if any) and rewind the stacked ones.

@magnumripper
Copy link
Member

This is also weird

/*
 * Return next word from stacked rules, calling rules_advance_stack() as
 * necessary. Once there's no rules left in stack, return NULL-
 */
extern char *rules_process_stack_all(char *key, rule_stack *ctx);

The actual code does not call rules_advance_stack(). Maybe it should?

@solardiz
Copy link
Member Author

The actual code does not call rules_advance_stack(). Maybe it should?

I actually thought of unifying this. I'm not sure we need rules_process_stack_all at all - could it be better to use the same functions that we use in single.c also in cracker.c? I'd rather not make the call chain deeper, because we'd be returning and re-entering on almost every candidate.

@solardiz
Copy link
Member Author

Anyway, I'm done with this for right now - can't afford to unexpectedly spend a lot of time on it.

@magnumripper magnumripper changed the title --rules-stack broken --rules-stack broken (now fixed but may still need some TLC) Mar 27, 2025
@magnumripper magnumripper added enhancement and removed bug regression Bug introduced with certain change(s), causing performance degradation or feature loss labels Mar 27, 2025
magnumripper added a commit to magnumripper/john that referenced this issue Mar 27, 2025
Just drop a couple of redundant log_event and move the last one so it
covers all three

See openwall#5722
magnumripper added a commit to magnumripper/john that referenced this issue Mar 27, 2025
Just drop a couple of redundant log_event and move the last one so it
covers all three

See openwall#5722
@magnumripper
Copy link
Member

I had time to test this further today and it seems 100% OK. Added an innocent cleanup in #5725, also well tested. That PR also contains a log call just before session end, that says how many candidates were processed. Good to have with verifications like this one.

magnumripper added a commit to magnumripper/john that referenced this issue Mar 27, 2025
Just drop a couple of redundant log_event and move the last one so it
covers all three.

See openwall#5722
magnumripper added a commit to magnumripper/john that referenced this issue Mar 27, 2025
Just drop a couple of redundant log_event and move the last one so it
covers all three.

See openwall#5722
magnumripper added a commit that referenced this issue Mar 27, 2025
Just drop a couple of redundant log_event and move the last one so it
covers all three.

See #5722
@solardiz
Copy link
Member Author

Also seem to have found a bug we had before, where single mode would log stacked rule number 1 twice. Fix:

Opened #5729 for this. The fix I had posted in here was on top of my other changes here, which we didn't merge, and it may create other issues. So we'll need to come up with a different fix. We also need to choose what we fix first - simplify and optimize the code, or fix this bug.

@solardiz
Copy link
Member Author

I'd rather not make the call chain deeper, because we'd be returning and re-entering on almost every candidate.

In fact, we may want to remove a level - move the logic directly into cracker.c or into an inline function in rules.h.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants