Skip to content

Commit 079e01b

Browse files
committed
external: normalize unambiguous file id lookup
external_lookup_id() uses strict string comparison, so file[0xa:0xa] and file[10:10] (the same file) wont match when one side emits hex and the other decimal. Add a numeric comparison path, but only for unambiguous forms to preserve backward compatibility. The key insight from avagins review: a component like 11 is ambiguous it could mean decimal 11 or hex (0x11 = 17 decimal). Without the explicit 0x prefix, there is no way to know the users intent. file[11:17] and file[17:23] could silently match incorrectly. Parse rules: - 0x... => unambiguous hex (explicit prefix) - contains hex digits (a-f/A-F) => unambiguous hex - digits only => ambiguous, do NOT normalize-match Only normalize when BOTH the mnt_id AND inode components are unambiguous. Ambiguous IDs retain their literal/exact semantics. This narrow fix addresses backward compatibility concerns: - CRIU emits bare hex from %x (e.g., file[a:a]): unambiguous, normalizes - CRIU emits bare digits from %x (e.g., file[10:10]): ambiguous, doesnt normalize - User provides file[0xa:0xa]: unambiguous, matches to stored file[0xa:0xa] - User provides file[10:10]: ambiguous, matches to stored file[10:10] - file[10:10] will NOT normalize-match file[0xa:0xa] because the latter is unambiguous Fixes: #2951 Signed-off-by: Farzan Aman Khan <farzanaman99@gmail.com>
1 parent c180188 commit 079e01b

1 file changed

Lines changed: 112 additions & 2 deletions

File tree

criu/external.c

Lines changed: 112 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
#include <errno.h>
2+
#include <limits.h>
3+
#include <stdint.h>
4+
#include <stdlib.h>
5+
16
#include "common/err.h"
27
#include "common/list.h"
38
#include "cr_options.h"
@@ -8,7 +13,91 @@
813

914
#include "net.h"
1015

11-
int add_external(char *key)
16+
static bool
17+
token_has_hex_letters(const char *start, const char *end)
18+
{
19+
const char *p;
20+
21+
for (p = start; p < end; p++) {
22+
if ((*p >= 'a' && *p <= 'f') || (*p >= 'A' && *p <= 'F'))
23+
return true;
24+
}
25+
26+
return false;
27+
}
28+
29+
static bool
30+
token_has_hex_prefix(const char *start, const char *end)
31+
{
32+
return (end - start) > 2 && start[0] == '0' &&
33+
(start[1] == 'x' || start[1] == 'X';
34+
}
35+
36+
static bool
37+
parse_external_id_component(const char *id, char delim, char **end,
38+
unsigned long long *val, bool *unambiguous)
39+
{
40+
char *tmp;
41+
42+
/*
43+
* First try decimal. If it fully consumes the component, this token is
44+
* ambiguous (digits-only can be decimal or hex).
45+
*/
46+
errno = 0;
47+
*val = strtoull(id, &tmp, 10);
48+
if (!errno && tmp != id && *tmp == delim) {
49+
*end = tmp;
50+
*unambiguous = false;
51+
return true;
52+
}
53+
54+
/*
55+
* Fallback to hex for forms decimal parser can't consume up to delim.
56+
* This covers 0x-prefixed values and bare-hex values with a-f/A-F.
57+
*/
58+
errno = 0;
59+
*val = strtoull(id, &tmp, 16);
60+
if (errno || tmp == id || *tmp != delim)
61+
return false;
62+
63+
*end = tmp;
64+
*unambiguous = token_has_hex_prefix(id, tmp) ||
65+
token_has_hex_letters(id, tmp);
66+
return true;
67+
}
68+
69+
static bool
70+
parse_external_file_id(const char *id, unsigned int *mnt_id, uint64_t *inode,
71+
bool *unambiguous)
72+
{
73+
char *end = NULL;
74+
unsigned long long val;
75+
bool mnt_unambiguous, inode_unambiguous;
76+
77+
if (!strstartswith(id, "file["))
78+
return false;
79+
id += strlen("file[");
80+
81+
if (!parse_external_id_component(id, ':', &end, &val, &mnt_unambiguous))
82+
return false;
83+
if (val > UINT_MAX)
84+
return false;
85+
*mnt_id = (unsigned int)val;
86+
87+
id = end + 1;
88+
if (!parse_external_id_component(id, ']', &end, &val, &inode_unambiguous))
89+
return false;
90+
end++;
91+
if (*end != '\0')
92+
return false;
93+
94+
*inode = val;
95+
*unambiguous = mnt_unambiguous && inode_unambiguous;
96+
return true;
97+
}
98+
99+
int
100+
add_external(char *key)
12101
{
13102
struct external *ext;
14103

@@ -39,10 +128,31 @@ int add_external(char *key)
39128
bool external_lookup_id(char *id)
40129
{
41130
struct external *ext;
131+
unsigned int id_mnt_id, ext_mnt_id;
132+
uint64_t id_inode, ext_inode;
133+
bool id_unambiguous, ext_unambiguous;
134+
135+
if (!parse_external_file_id(id, &id_mnt_id, &id_inode, &id_unambiguous))
136+
id_unambiguous = false;
42137

43-
list_for_each_entry(ext, &opts.external, node)
138+
list_for_each_entry(ext, &opts.external, node) {
44139
if (!strcmp(ext->id, id))
45140
return true;
141+
142+
/*
143+
* Only normalize when BOTH the queried ID and the stored ID
144+
* are unambiguous (explicit 0x prefix or explicit hex digits).
145+
* Ambiguous IDs (digit-only like 11) retain their literal semantics
146+
* and are not matched by normalization to avoid silent miscorrection.
147+
* This preserves backward compatibility with existing checkpoint
148+
* images while enabling proper matching for unambiguous forms.
149+
*/
150+
if (id_unambiguous &&
151+
parse_external_file_id(ext->id, &ext_mnt_id, &ext_inode, &ext_unambiguous) &&
152+
ext_unambiguous &&
153+
ext_mnt_id == id_mnt_id && ext_inode == id_inode)
154+
return true;
155+
}
46156
return false;
47157
}
48158

0 commit comments

Comments
 (0)