Skip to content

Commit 194d2e8

Browse files
committed
Merge branch 'master' into develop
2 parents 1ffc8fe + 577b946 commit 194d2e8

44 files changed

Lines changed: 455 additions & 225 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

NEWS.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,17 @@ Breaking changes:
9292
uses 1,000 for the base instead of 1,024.
9393

9494
Bug Fixes:
95+
* A PRQL query can now start with `let` in interactive
96+
mode.
9597
* Fix a bug in file loading that could cause a short
9698
read and crash in some situations.
99+
* Fix a lockup when viewing a file that contained log
100+
messages and lots of binary data.
101+
* More hardening for some diabolical inputs:
102+
- Unsupported escape-sequences were ignored before,
103+
but they are displayed now.
104+
- Checks for archives with file paths that could
105+
escape containment.
97106

98107

99108
## lnav v0.14.0

src/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,6 @@ add_custom_command(
366366
list(APPEND GEN_SRCS builtin-scripts.h builtin-scripts.cc)
367367

368368
set(BUILTIN_SH_SCRIPTS
369-
scripts/com.vmware.btresolver.py
370369
scripts/dump-pid.sh
371370
scripts/pcap_log-converter.sh
372371
scripts/zookeeper.sql

src/archive_manager.cc

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@ extract(const std::string& filename, const extract_cb& cb)
302302
filename,
303303
archive_error_string(arc)));
304304
}
305+
auto tmp_base = tmp_path.lexically_normal();
305306

306307
log_info("extracting %s to %s", filename.c_str(), tmp_path.c_str());
307308
while (true) {
@@ -332,7 +333,12 @@ extract(const std::string& filename, const extract_cb& cb)
332333
if (strcmp(format_name, "raw") == 0 && filter_count >= 2) {
333334
desired_pathname = fs::path(filename).filename();
334335
}
335-
auto entry_path = tmp_path / desired_pathname;
336+
auto entry_path = (tmp_path / desired_pathname).lexically_normal();
337+
auto rel = entry_path.lexically_relative(tmp_base);
338+
if (rel.empty() || lnav::filesystem::contains_dotdot(rel)) {
339+
log_warning("ignoring naughty path: %s", entry_path_str);
340+
continue;
341+
}
336342
auto* prog = cb(
337343
entry_path,
338344
archive_entry_size_is_set(entry) ? archive_entry_size(entry) : -1);
@@ -341,6 +347,44 @@ extract(const std::string& filename, const extract_cb& cb)
341347

342348
archive_entry_set_perm(
343349
wentry, S_IRUSR | (S_ISDIR(entry_mode) ? S_IXUSR | S_IWUSR : 0));
350+
if (S_ISLNK(entry_mode)) {
351+
auto* target_path_str = archive_entry_symlink(wentry);
352+
if (target_path_str == nullptr) {
353+
log_warning("symlink is null: %s", entry_path_str);
354+
continue;
355+
}
356+
auto link_target = fs::path(target_path_str);
357+
if (link_target.is_absolute()) {
358+
// Confine to tmp_path: strip root, rejoin under tmp_path,
359+
// then express as relative from the symlink's own directory.
360+
auto confined = (tmp_path / link_target.relative_path())
361+
.lexically_normal();
362+
auto rewritten
363+
= confined.lexically_relative(entry_path.parent_path());
364+
if (rewritten.empty()) {
365+
log_warning(
366+
"ignoring symlink with unrepresentable target: %s",
367+
entry_path_str);
368+
continue;
369+
}
370+
archive_entry_set_symlink(wentry, rewritten.c_str());
371+
} else {
372+
// Relative target: verify it lands inside tmp_base, but leave
373+
// the literal target alone so kernel resolution matches.
374+
auto resolved = (entry_path.parent_path() / link_target)
375+
.lexically_normal();
376+
auto target_rel = resolved.lexically_relative(tmp_base);
377+
if (target_rel.empty()
378+
|| lnav::filesystem::contains_dotdot(target_rel))
379+
{
380+
log_warning(
381+
"ignoring naughty symlink '%s' with target '%s'",
382+
entry_path_str,
383+
target_path_str);
384+
continue;
385+
}
386+
}
387+
}
344388
r = archive_write_header(ext, wentry);
345389
if (r < ARCHIVE_OK) {
346390
return Err(

src/base/ansi_scrubber.cc

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,20 @@
4040
#include "pcrepp/pcre2pp.hh"
4141
#include "scn/scan.h"
4242

43+
using std::string_literals::operator""s;
44+
4345
static const lnav::pcre2pp::code&
4446
ansi_regex()
4547
{
4648
static const auto retval = lnav::pcre2pp::code::from_const(
47-
R"(\x00|\x1b\[([\d=;:\?]*)([a-zA-Z])|\x1b\](\d+);(.*?)(?:\x07|\x1b\\)|(?:\X\x08\X)+|(\x16+))");
49+
R"(
50+
\x00 # NUL
51+
| \x1b \[ ([\d=;:\?]*) ([a-zA-Z]) # CSI: ESC [ <params> <final>
52+
| \x1b \] (\d+) ; (.*?) (?:\x07 | \x1b\\) # OSC: ESC ] <code> ; <payload> (BEL|ST)
53+
| (?: \X \x08 \X )+ # Overstrike: glyph BS glyph
54+
| (\x16+) # SYN run
55+
)",
56+
PCRE2_EXTENDED);
4857

4958
return retval;
5059
}
@@ -290,7 +299,7 @@ scrub_ansi_string(std::string& str, string_attrs_t* sa)
290299

291300
if (osc_id) {
292301
switch (osc_id->value()) {
293-
case 8:
302+
case 8: {
294303
auto split_res = md[4]->split_pair(semi_pred);
295304
if (split_res) {
296305
// auto params = split_res->first;
@@ -313,6 +322,20 @@ scrub_ansi_string(std::string& str, string_attrs_t* sa)
313322
}
314323
}
315324
break;
325+
}
326+
default: {
327+
if (sa != nullptr) {
328+
lr.lr_start = cp_dst;
329+
lr.lr_end = cp_dst;
330+
tmp_sa.emplace_back(
331+
lr,
332+
SA_UNSUPPORTED.value(fmt::format(
333+
FMT_STRING("ANSI sequence: OSC {} {}"),
334+
md[3],
335+
md[4])));
336+
}
337+
break;
338+
}
316339
}
317340
}
318341
} else if (md[1]) {
@@ -468,6 +491,19 @@ scrub_ansi_string(std::string& str, string_attrs_t* sa)
468491
}
469492
break;
470493
}
494+
default: {
495+
if (sa != nullptr) {
496+
lr.lr_start = cp_dst;
497+
lr.lr_end = cp_dst;
498+
tmp_sa.emplace_back(
499+
lr,
500+
SA_UNSUPPORTED.value(fmt::format(
501+
FMT_STRING("ANSI sequence: ESC [ {} {}"),
502+
seq,
503+
terminator)));
504+
}
505+
break;
506+
}
471507
}
472508
}
473509
if (md[1] || md[3] || md[5]) {
@@ -485,6 +521,7 @@ scrub_ansi_string(std::string& str, string_attrs_t* sa)
485521
sa.sa_range.lr_end = cp_dst;
486522
if (sa.sa_range.empty()) {
487523
sa.sa_type = &SA_INVALID;
524+
sa.sa_value = "zero-length attribute"s;
488525
}
489526
}
490527
if (sf.sf_end < str.size()) {

src/base/color_spaces.cc

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -324,14 +324,28 @@ lab_color::readable(const lab_color& bg) const
324324
constexpr double CONTRAST_BOOST = 20.0;
325325
constexpr double CHROMA_HEADROOM = 30.0;
326326

327+
constexpr double L_FLOOR = 25.0;
328+
constexpr double L_CEIL = 90.0;
329+
327330
bool changed = false;
328331
double new_l = this->lc_l;
329332
if (std::abs(this->lc_l - bg.lc_l) < CONTRAST_BOOST) {
333+
// Push fg away from bg by 2x the boost in fg's current direction;
334+
// if that side is out of headroom against [L_FLOOR, L_CEIL], flip
335+
// to the other side instead. Targets are pre-clamped so that when
336+
// both sides are blocked we still land on a valid L*.
330337
const double delta = CONTRAST_BOOST * 2.0;
331-
const bool go_up = (this->lc_l > bg.lc_l) ? (bg.lc_l + delta <= 90.0)
332-
: (bg.lc_l - delta < 25.0);
333-
new_l = go_up ? std::min(bg.lc_l + delta, 90.0)
334-
: std::max(bg.lc_l - delta, 25.0);
338+
const double up_target = std::min(bg.lc_l + delta, L_CEIL);
339+
const double down_target = std::max(bg.lc_l - delta, L_FLOOR);
340+
const bool up_has_room = bg.lc_l + delta <= L_CEIL;
341+
const bool down_has_room = bg.lc_l - delta >= L_FLOOR;
342+
const bool prefer_up = this->lc_l > bg.lc_l;
343+
344+
if (prefer_up) {
345+
new_l = up_has_room ? up_target : down_target;
346+
} else {
347+
new_l = down_has_room ? down_target : up_target;
348+
}
335349
changed = true;
336350
}
337351

src/base/fs_util.cc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,18 @@ escape_path(const std::filesystem::path& p, path_type pt)
196196
return retval;
197197
}
198198

199+
bool
200+
contains_dotdot(const std::filesystem::path& p)
201+
{
202+
for (const auto& part : p) {
203+
if (part == "..") {
204+
return true;
205+
}
206+
}
207+
208+
return false;
209+
}
210+
199211
bool
200212
is_url(const std::string& fn)
201213
{

src/base/fs_util.hh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ enum class path_type {
7979
std::string escape_path(const std::filesystem::path& p,
8080
path_type pt = path_type::normal);
8181

82+
bool contains_dotdot(const std::filesystem::path& p);
83+
8284
path_type determine_path_type(const std::string& arg);
8385

8486
struct path_transcoder {

src/base/fs_util.tests.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,13 @@ TEST_CASE("fs_util::escape_path")
100100

101101
CHECK("\\$abc" == lnav::filesystem::escape_path(p2));
102102
}
103+
104+
TEST_CASE("fs_util::contains_dotdot")
105+
{
106+
CHECK_FALSE(
107+
lnav::filesystem::contains_dotdot(std::filesystem::path{"/abc/def"}));
108+
CHECK(lnav::filesystem::contains_dotdot(
109+
std::filesystem::path{"/abc/../def"}));
110+
CHECK_FALSE(lnav::filesystem::contains_dotdot(
111+
std::filesystem::path{"/abc..def"}));
112+
}

src/base/intern_string.hh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,6 +1023,20 @@ struct formatter<std::error_code> : formatter<string_view> {
10231023
return formatter<string_view>::format(ec.message(), ctx);
10241024
}
10251025
};
1026+
1027+
template<typename I>
1028+
struct formatter<std::optional<I>> : formatter<I> {
1029+
template<typename FormatContext>
1030+
auto format(const std::optional<I>& opt, FormatContext& ctx) const
1031+
{
1032+
if (!opt) {
1033+
return formatter<string_view>::format("\u2205", ctx);
1034+
}
1035+
1036+
return formatter<I>::format(opt.value(), ctx);
1037+
}
1038+
};
1039+
10261040
} // namespace fmt
10271041

10281042
namespace std {

src/base/lnav.console.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,8 @@ println(FILE* file, const attr_line_t& al)
633633
if (color_opt) {
634634
fg_style = fmt::fg(color_opt.value());
635635
}
636+
} else if (attr.sa_type == &SA_UNSUPPORTED) {
637+
line_style |= fmt::fg(fmt::terminal_color::yellow);
636638
} else if (attr.sa_type == &VC_STYLE) {
637639
auto saw = string_attr_wrapper<text_attrs>(&attr);
638640
auto style = saw.get();

0 commit comments

Comments
 (0)